From 4cad9a83326146b28b98c6dec4aa08c75841551b Mon Sep 17 00:00:00 2001 From: David Brunow <> Date: Sat, 17 Sep 2022 17:47:17 -0500 Subject: [PATCH 001/332] Fix more postfix pound if scenarios --- .../TokenStreamCreator.swift | 38 ++++++++--- .../IfConfigTests.swift | 64 +++++++++++++++++++ 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index adad4054f..1ce756aed 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1307,7 +1307,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(tokenToOpenWith.nextToken(viewMode: .all), tokens: .break(breakKindClose, newlines: .soft), .close) } - if isNestedInPostfixIfConfig(node: Syntax(node)) { + if node.parent?.parent?.parent?.is(PostfixIfConfigExprSyntax.self) == true { before( node.firstToken, tokens: [ @@ -3481,7 +3481,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let calledMemberAccessExpr = calledExpression.as(MemberAccessExprSyntax.self) { if calledMemberAccessExpr.base != nil { - if isNestedInPostfixIfConfig(node: Syntax(calledMemberAccessExpr)) { + if isNestedInPostfixIfConfig(node: calledMemberAccessExpr) { before(calledMemberAccessExpr.dot, tokens: [.break(.same, size: 0)]) } else { before(calledMemberAccessExpr.dot, tokens: [.break(.contextual, size: 0)]) @@ -3510,18 +3510,40 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } -private func isNestedInPostfixIfConfig(node: Syntax) -> Bool { - var this: Syntax? = node +private func isNestedInPostfixIfConfig(node: MemberAccessExprSyntax) -> Bool { + func containsDescendent(ancestor: Syntax, node: MemberAccessExprSyntax) -> Bool { + if ancestor.children(viewMode: .sourceAccurate).contains(Syntax(node)) { + return true + } - while this?.parent != nil { - if this?.parent?.is(PostfixIfConfigExprSyntax.self) == true { + for child in ancestor.children(viewMode: .sourceAccurate) { + if containsDescendent(ancestor: child, node: node) { return true } - - this = this?.parent } return false + } + + var this: Syntax? = Syntax(node) + + while this?.parent != nil { + if this?.is(TupleExprElementSyntax.self) == true { + return false + } + + if let postfixIfConfig = this?.as(PostfixIfConfigExprSyntax.self) { + if let ifConfigListSyntax = postfixIfConfig.config.children(viewMode: .sourceAccurate).first?.as(IfConfigClauseListSyntax.self) { + if containsDescendent(ancestor: Syntax(ifConfigListSyntax), node: node) { + return true + } + } + } + + this = this?.parent + } + + return false } extension Syntax { diff --git a/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift b/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift index 9547816f0..e1ad9e913 100644 --- a/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift @@ -390,4 +390,68 @@ final class IfConfigTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) } + + func testPostfixPoundIfBetweenOtherModifiers() { + let input = + """ + EmptyView() + .padding([.vertical]) + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() + """ + + let expected = + """ + EmptyView() + .padding([.vertical]) + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } + + func testPostfixPoundIfWithTypeInModifier() { + let input = + """ + EmptyView() + .padding([.vertical]) + #if os(iOS) + .iOSSpecificModifier( + SpecificType() + .onChanged { _ in + // do things + } + .onEnded { _ in + // do things + } + ) + #endif + """ + + let expected = + """ + EmptyView() + .padding([.vertical]) + #if os(iOS) + .iOSSpecificModifier( + SpecificType() + .onChanged { _ in + // do things + } + .onEnded { _ in + // do things + } + ) + #endif + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } } From fdc708f2a02ad08bbca4f0d2d38e858b5b487d73 Mon Sep 17 00:00:00 2001 From: David Brunow <> Date: Sat, 17 Sep 2022 18:43:00 -0500 Subject: [PATCH 002/332] Simplify the implementation --- .../TokenStreamCreator.swift | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 1ce756aed..21e966422 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -3511,20 +3511,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } private func isNestedInPostfixIfConfig(node: MemberAccessExprSyntax) -> Bool { - func containsDescendent(ancestor: Syntax, node: MemberAccessExprSyntax) -> Bool { - if ancestor.children(viewMode: .sourceAccurate).contains(Syntax(node)) { - return true - } - - for child in ancestor.children(viewMode: .sourceAccurate) { - if containsDescendent(ancestor: child, node: node) { - return true - } - } - - return false - } - var this: Syntax? = Syntax(node) while this?.parent != nil { @@ -3532,12 +3518,9 @@ private func isNestedInPostfixIfConfig(node: MemberAccessExprSyntax) -> Bool { return false } - if let postfixIfConfig = this?.as(PostfixIfConfigExprSyntax.self) { - if let ifConfigListSyntax = postfixIfConfig.config.children(viewMode: .sourceAccurate).first?.as(IfConfigClauseListSyntax.self) { - if containsDescendent(ancestor: Syntax(ifConfigListSyntax), node: node) { - return true - } - } + if this?.is(IfConfigDeclSyntax.self) == true && + this?.parent?.is(PostfixIfConfigExprSyntax.self) == true { + return true } this = this?.parent From 1361dd51dde7280b8e8cf23c1a12e8c300e9d9f7 Mon Sep 17 00:00:00 2001 From: David Brunow <> Date: Sun, 18 Sep 2022 11:46:15 -0500 Subject: [PATCH 003/332] Simplify again --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 21e966422..953d0661a 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1307,7 +1307,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(tokenToOpenWith.nextToken(viewMode: .all), tokens: .break(breakKindClose, newlines: .soft), .close) } - if node.parent?.parent?.parent?.is(PostfixIfConfigExprSyntax.self) == true { + if isNestedInPostfixIfConfig(node: Syntax(node)) { before( node.firstToken, tokens: [ @@ -3481,7 +3481,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let calledMemberAccessExpr = calledExpression.as(MemberAccessExprSyntax.self) { if calledMemberAccessExpr.base != nil { - if isNestedInPostfixIfConfig(node: calledMemberAccessExpr) { + if isNestedInPostfixIfConfig(node: Syntax(calledMemberAccessExpr)) { before(calledMemberAccessExpr.dot, tokens: [.break(.same, size: 0)]) } else { before(calledMemberAccessExpr.dot, tokens: [.break(.contextual, size: 0)]) @@ -3510,8 +3510,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } -private func isNestedInPostfixIfConfig(node: MemberAccessExprSyntax) -> Bool { - var this: Syntax? = Syntax(node) +private func isNestedInPostfixIfConfig(node: Syntax) -> Bool { + var this: Syntax? = node while this?.parent != nil { if this?.is(TupleExprElementSyntax.self) == true { From bc852932c0eb9027f999a911e3bfb3529b67dca2 Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Mon, 19 Dec 2022 16:56:46 -0800 Subject: [PATCH 004/332] Remove `prefixPeriod` Removed by swift-syntax in https://github.com/apple/swift-syntax/pull/1077. --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index fa592e4cf..4890c3771 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -3321,7 +3321,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if case .postfixOperator? = token.previousToken(viewMode: .all)?.tokenKind { return true } switch token.nextToken(viewMode: .all)?.tokenKind { - case .prefixOperator?, .prefixPeriod?: return true + case .prefixOperator?, .period?: return true default: return false } } From c13baec6c602e2c63a0b30f0e2c679afc9f6d2f4 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 3 Jan 2023 14:45:59 +0100 Subject: [PATCH 005/332] Adjustments for explicit enumeration of contextual keywords in SwiftSyntax --- .../SwiftFormatPrettyPrint/TokenStreamCreator.swift | 2 +- Sources/SwiftFormatRules/UseEarlyExits.swift | 2 +- Sources/SwiftFormatRules/UseShorthandTypeNames.swift | 12 ++++++------ .../UseSingleLinePropertyGetter.swift | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 4890c3771..d539562b3 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1722,7 +1722,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // this special exception for `async let` statements to avoid breaking prematurely between the // `async` and `let` keywords. let breakOrSpace: Token - if node.name.tokenKind == .contextualKeyword("async") { + if node.name.tokenKind == .contextualKeyword(.async) { breakOrSpace = .space } else { breakOrSpace = .break diff --git a/Sources/SwiftFormatRules/UseEarlyExits.swift b/Sources/SwiftFormatRules/UseEarlyExits.swift index c2c0f62ac..5f27a327e 100644 --- a/Sources/SwiftFormatRules/UseEarlyExits.swift +++ b/Sources/SwiftFormatRules/UseEarlyExits.swift @@ -65,7 +65,7 @@ public final class UseEarlyExits: SyntaxFormatRule { diagnose(.useGuardStatement, on: ifStatement.elseKeyword) - let trueBlock = ifStatement.body.withLeftBrace(nil).withRightBrace(nil) + let trueBlock = ifStatement.body let guardKeyword = TokenSyntax.guardKeyword( leadingTrivia: ifStatement.ifKeyword.leadingTrivia, diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 1a57c415a..ee83ee469 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -520,12 +520,12 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // Look for accessors that indicate that this is a computed property. If none are found, then // it is a stored property (e.g., having only observers like `willSet/didSet`). switch accessorDecl.accessorKind.tokenKind { - case .contextualKeyword("get"), - .contextualKeyword("set"), - .contextualKeyword("unsafeAddress"), - .contextualKeyword("unsafeMutableAddress"), - .contextualKeyword("_read"), - .contextualKeyword("_modify"): + case .contextualKeyword(.get), + .contextualKeyword(.set), + .contextualKeyword(.unsafeAddress), + .contextualKeyword(.unsafeMutableAddress), + .contextualKeyword(._read), + .contextualKeyword(._modify): return false default: return true diff --git a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift index 3cb217026..87bdf9b84 100644 --- a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift @@ -26,7 +26,7 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { let acc = accessorBlock.accessors.first, let body = acc.body, accessorBlock.accessors.count == 1, - acc.accessorKind.tokenKind == .contextualKeyword("get"), + acc.accessorKind.tokenKind == .contextualKeyword(.get), acc.attributes == nil, acc.modifier == nil, acc.asyncKeyword == nil, From fcbe608cc46840756df8e96922184763735af0ae Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 3 Jan 2023 17:51:17 +0100 Subject: [PATCH 006/332] Remove unused nodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These nodes will be removed from SwiftSyntax and thus also shouldn’t be used by swift-format. --- .../TokenStreamCreator.swift | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 4890c3771..6e254b9d3 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1428,10 +1428,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: AccessLevelModifierSyntax) -> SyntaxVisitorContinueKind { - return .visitChildren - } - override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -1969,10 +1965,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: AsTypePatternSyntax) -> SyntaxVisitorContinueKind { - return .visitChildren - } - override func visit(_ node: InheritedTypeSyntax) -> SyntaxVisitorContinueKind { after(node.trailingComma, tokens: .break(.same)) return .visitChildren @@ -2009,10 +2001,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: ExpressionStmtSyntax) -> SyntaxVisitorContinueKind { - return .visitChildren - } - override func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -2040,14 +2028,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: DeclarationStmtSyntax) -> SyntaxVisitorContinueKind { - return .visitChildren - } - - override func visit(_ node: EnumCasePatternSyntax) -> SyntaxVisitorContinueKind { - return .visitChildren - } - override func visit(_ node: FallthroughStmtSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -2066,10 +2046,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: OptionalPatternSyntax) -> SyntaxVisitorContinueKind { - return .visitChildren - } - override func visit(_ node: WildcardPatternSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -2249,10 +2225,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: SymbolicReferenceExprSyntax) -> SyntaxVisitorContinueKind { - return .visitChildren - } - override func visit(_ node: TypeInheritanceClauseSyntax) -> 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 @@ -2364,11 +2336,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .skipChildren } - override func visit(_ node: NonEmptyTokenListSyntax) -> SyntaxVisitorContinueKind { - verbatimToken(Syntax(node)) - return .skipChildren - } - // MARK: - Token handling override func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind { From 38619997ef591ca200c497f11dddd2ad6bd2c74a Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 5 Jan 2023 10:45:56 -0800 Subject: [PATCH 007/332] Pin dependencies other than swift-syntax to more specific versions. * swift-argument-parser is pinned to the same version range used by swift-syntax, and these should be kept in sync. * swift-tools-support-core is pinned to a specific version (not a range). --- Package.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 9ab3fa332..9762b661c 100644 --- a/Package.swift +++ b/Package.swift @@ -215,7 +215,9 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { package.dependencies += [ .package( url: "https://github.com/apple/swift-argument-parser.git", - branch: "main" + // This should be kept in sync with the same dependency used by + // swift-syntax. + Version("1.0.1").. Date: Tue, 10 Jan 2023 22:40:26 +0100 Subject: [PATCH 008/332] Adjustments for merging `AttributeSyntax` and `CustomAttributeSyntax` --- .../TokenStreamCreator.swift | 29 ++++++++----------- ...NeverUseImplicitlyUnwrappedOptionals.swift | 2 +- .../AttributeTests.swift | 3 -- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 6e254b9d3..76d0e849c 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1584,29 +1584,24 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AttributeSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken, tokens: .open) - if node.argument != nil { + switch node.argument { + case .argumentList(let argumentList)?: + if let leftParen = node.leftParen, let rightParen = node.rightParen { + arrangeFunctionCallArgumentList( + argumentList, + leftDelimiter: leftParen, + rightDelimiter: rightParen, + forcesBreakBeforeRightDelimiter: false) + } + case .some: // Wrap the attribute's arguments in their own group, so arguments stay together with a higher // affinity than the overall attribute (e.g. allows a break after the opening "(" and then // having the entire argument list on 1 line). Necessary spaces and breaks are added inside of // the argument, using type specific visitor methods. after(node.leftParen, tokens: .break(.open, size: 0), .open(argumentListConsistency())) before(node.rightParen, tokens: .break(.close, size: 0), .close) - } - after(node.lastToken, tokens: .close) - return .visitChildren - } - - override func visit(_ node: CustomAttributeSyntax) -> SyntaxVisitorContinueKind { - // "Custom attributes" are better known to users as "property wrappers". - before(node.firstToken, tokens: .open) - if let argumentList = node.argumentList, - let leftParen = node.leftParen, let rightParen = node.rightParen - { - arrangeFunctionCallArgumentList( - argumentList, - leftDelimiter: leftParen, - rightDelimiter: rightParen, - forcesBreakBeforeRightDelimiter: false) + case nil: + break } after(node.lastToken, tokens: .close) return .visitChildren diff --git a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift index dc5d82210..d11e21606 100644 --- a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -42,7 +42,7 @@ public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule { // Ignores IBOutlet variables if let attributes = node.attributes { for attribute in attributes { - if (attribute.as(AttributeSyntax.self))?.attributeName.text == "IBOutlet" { + if (attribute.as(AttributeSyntax.self))?.attributeName.as(SimpleTypeIdentifierSyntax.self)?.name.text == "IBOutlet" { return .skipChildren } } diff --git a/Tests/SwiftFormatPrettyPrintTests/AttributeTests.swift b/Tests/SwiftFormatPrettyPrintTests/AttributeTests.swift index 9dd036ea7..edc3bb8bc 100644 --- a/Tests/SwiftFormatPrettyPrintTests/AttributeTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/AttributeTests.swift @@ -316,9 +316,6 @@ final class AttributeTests: PrettyPrintTestCase { } func testPropertyWrappers() { - // Property wrappers are `CustomAttributeSyntax` nodes (not `AttributeSyntax`) and their - // arguments are `TupleExprElementListSyntax` (like regular function call argument lists), so - // make sure that those are formatted properly. let input = """ struct X { From 216ee3ea3703bc145ba0b1359535084d6f8f7453 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 10 Jan 2023 23:11:53 +0100 Subject: [PATCH 009/332] Adjustments for merging `spacedBinaryOperator` and `unspacedBinaryOperator` --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 6e254b9d3..df7ebfbb3 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -306,9 +306,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Add a non-breaking space after the identifier if it's an operator, to separate it visually // from the following parenthesis or generic argument list. Note that even if the function is - // defining a prefix or postfix operator, or even if the operator isn't originally followed by a - // space, the token kind always comes through as `spacedBinaryOperator`. - if case .spacedBinaryOperator = node.identifier.tokenKind { + // defining a prefix or postfix operator, the token kind always comes through as + // `binaryOperator`. + if case .binaryOperator = node.identifier.tokenKind { after(node.identifier.lastToken, tokens: .space) } From 543e4e49dad6e56c2298440863a0b21f8a83a064 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 11 Jan 2023 14:49:16 +0100 Subject: [PATCH 010/332] Adjustments for merging of contextual and lexial keywords --- .../TokenStreamCreator.swift | 2 +- .../SwiftFormatRules/AlwaysUseLowerCamelCase.swift | 4 ++-- .../FileScopedDeclarationPrivacy.swift | 8 ++++---- Sources/SwiftFormatRules/FullyIndirectEnum.swift | 2 +- .../ModifierListSyntax+Convenience.swift | 2 +- .../NoAccessLevelOnExtensionDeclaration.swift | 8 ++++---- Sources/SwiftFormatRules/UseEarlyExits.swift | 4 ++-- .../SwiftFormatRules/UseShorthandTypeNames.swift | 14 +++++++------- .../UseSingleLinePropertyGetter.swift | 2 +- .../UseSynthesizedInitializer.swift | 12 ++++++------ .../UseWhereClausesInForLoops.swift | 2 +- .../ValidateDocumentationComments.swift | 2 +- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index dacccf5b1..4d00e5ef9 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1718,7 +1718,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // this special exception for `async let` statements to avoid breaking prematurely between the // `async` and `let` keywords. let breakOrSpace: Token - if node.name.tokenKind == .contextualKeyword(.async) { + if node.name.tokenKind == .keyword(.async) { breakOrSpace = .space } else { breakOrSpace = .break diff --git a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift index a9eccee21..3bfbdbf17 100644 --- a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift @@ -173,9 +173,9 @@ fileprivate func identifierDescription(for node: NodeT case .enumCaseElement: return "enum case" case .functionDecl: return "function" case .optionalBindingCondition(let binding): - return binding.letOrVarKeyword.tokenKind == .varKeyword ? "variable" : "constant" + return binding.letOrVarKeyword.tokenKind == .keyword(.var) ? "variable" : "constant" case .variableDecl(let variableDecl): - return variableDecl.letOrVarKeyword.tokenKind == .varKeyword ? "variable" : "constant" + return variableDecl.letOrVarKeyword.tokenKind == .keyword(.var) ? "variable" : "constant" default: return "identifier" } diff --git a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift index 098b23c72..c6f64d719 100644 --- a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift +++ b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift @@ -144,12 +144,12 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { switch context.configuration.fileScopedDeclarationPrivacy.accessLevel { case .private: - invalidAccess = .fileprivateKeyword - validAccess = .privateKeyword + invalidAccess = .keyword(.fileprivate) + validAccess = .keyword(.private) diagnostic = .replaceFileprivateWithPrivate case .fileprivate: - invalidAccess = .privateKeyword - validAccess = .fileprivateKeyword + invalidAccess = .keyword(.private) + validAccess = .keyword(.fileprivate) diagnostic = .replacePrivateWithFileprivate } diff --git a/Sources/SwiftFormatRules/FullyIndirectEnum.swift b/Sources/SwiftFormatRules/FullyIndirectEnum.swift index 384293ad5..aa27cd906 100644 --- a/Sources/SwiftFormatRules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormatRules/FullyIndirectEnum.swift @@ -57,7 +57,7 @@ public final class FullyIndirectEnum: SyntaxFormatRule { let leadingTrivia: Trivia let newEnumDecl: EnumDeclSyntax - if firstTok.tokenKind == .enumKeyword { + if firstTok.tokenKind == .keyword(.enum) { leadingTrivia = firstTok.leadingTrivia newEnumDecl = replaceTrivia( on: node, token: node.firstToken, leadingTrivia: []) diff --git a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift index f0ffafead..f1b37a924 100644 --- a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift +++ b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift @@ -26,7 +26,7 @@ extension ModifierListSyntax { var accessLevelModifier: DeclModifierSyntax? { for modifier in self { switch modifier.name.tokenKind { - case .publicKeyword, .privateKeyword, .fileprivateKeyword, .internalKeyword: + case .keyword(.public), .keyword(.private), .keyword(.fileprivate), .keyword(.internal): return modifier default: continue diff --git a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift index 84b8eb1c9..c1426bd52 100644 --- a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift @@ -31,15 +31,15 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { let keywordKind = accessKeyword.name.tokenKind switch keywordKind { // Public, private, or fileprivate keywords need to be moved to members - case .publicKeyword, .privateKeyword, .fileprivateKeyword: + case .keyword(.public), .keyword(.private), .keyword(.fileprivate): diagnose(.moveAccessKeyword(keyword: accessKeyword.name.text), on: accessKeyword) // 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. let accessKeywordToAdd: DeclModifierSyntax - if keywordKind == .privateKeyword { + if keywordKind == .keyword(.private) { accessKeywordToAdd - = accessKeyword.withName(accessKeyword.name.withKind(.fileprivateKeyword)) + = accessKeyword.withName(accessKeyword.name.withKind(.keyword(.fileprivate))) } else { accessKeywordToAdd = accessKeyword } @@ -58,7 +58,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { return DeclSyntax(result) // Internal keyword redundant, delete - case .internalKeyword: + case .keyword(.internal): diagnose( .removeRedundantAccessKeyword(name: node.extendedType.description), on: accessKeyword) diff --git a/Sources/SwiftFormatRules/UseEarlyExits.swift b/Sources/SwiftFormatRules/UseEarlyExits.swift index 5f27a327e..ae1adce30 100644 --- a/Sources/SwiftFormatRules/UseEarlyExits.swift +++ b/Sources/SwiftFormatRules/UseEarlyExits.swift @@ -67,13 +67,13 @@ public final class UseEarlyExits: SyntaxFormatRule { let trueBlock = ifStatement.body - let guardKeyword = TokenSyntax.guardKeyword( + let guardKeyword = TokenSyntax.keyword(.guard, leadingTrivia: ifStatement.ifKeyword.leadingTrivia, trailingTrivia: .spaces(1)) let guardStatement = GuardStmtSyntax( guardKeyword: guardKeyword, conditions: ifStatement.conditions, - elseKeyword: TokenSyntax.elseKeyword(trailingTrivia: .spaces(1)), + elseKeyword: TokenSyntax.keyword(.else, trailingTrivia: .spaces(1)), body: elseBody) var items = [ diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index ee83ee469..150a48099 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -520,12 +520,12 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // Look for accessors that indicate that this is a computed property. If none are found, then // it is a stored property (e.g., having only observers like `willSet/didSet`). switch accessorDecl.accessorKind.tokenKind { - case .contextualKeyword(.get), - .contextualKeyword(.set), - .contextualKeyword(.unsafeAddress), - .contextualKeyword(.unsafeMutableAddress), - .contextualKeyword(._read), - .contextualKeyword(._modify): + case .keyword(.get), + .keyword(.set), + .keyword(.unsafeAddress), + .keyword(.unsafeMutableAddress), + .keyword(._read), + .keyword(._modify): return false default: return true @@ -545,7 +545,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { isStoredProperty(patternBinding), patternBinding.initializer == nil, let variableDecl = nearestAncestor(of: patternBinding, type: VariableDeclSyntax.self), - variableDecl.letOrVarKeyword.tokenKind == .varKeyword + variableDecl.letOrVarKeyword.tokenKind == .keyword(.var) { return true } diff --git a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift index 87bdf9b84..8b4a35083 100644 --- a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift @@ -26,7 +26,7 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { let acc = accessorBlock.accessors.first, let body = acc.body, accessorBlock.accessors.count == 1, - acc.accessorKind.tokenKind == .contextualKeyword(.get), + acc.accessorKind.tokenKind == .keyword(.get), acc.attributes == nil, acc.modifier == nil, acc.asyncKeyword == nil, diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift index c514820d2..576c0f093 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift @@ -90,11 +90,11 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { switch synthesizedAccessLevel { case .internal: // No explicit access level or internal are equivalent. - return accessLevel == nil || accessLevel!.name.tokenKind == .internalKeyword + return accessLevel == nil || accessLevel!.name.tokenKind == .keyword(.internal) case .fileprivate: - return accessLevel != nil && accessLevel!.name.tokenKind == .fileprivateKeyword + return accessLevel != nil && accessLevel!.name.tokenKind == .keyword(.fileprivate) case .private: - return accessLevel != nil && accessLevel!.name.tokenKind == .privateKeyword + return accessLevel != nil && accessLevel!.name.tokenKind == .keyword(.private) } } @@ -116,7 +116,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { // Ensure that parameters that correspond to properties declared using 'var' have a default // argument that is identical to the property's default value. Otherwise, a default argument // doesn't match the memberwise initializer. - let isVarDecl = property.letOrVarKeyword.tokenKind == .varKeyword + let isVarDecl = property.letOrVarKeyword.tokenKind == .keyword(.var) if isVarDecl, let initializer = property.firstInitializer { guard let defaultArg = parameter.defaultArgument else { return false } guard initializer.value.description == defaultArg.value.description else { return false } @@ -212,10 +212,10 @@ fileprivate func synthesizedInitAccessLevel(using properties: [VariableDeclSynta guard let modifiers = property.modifiers else { continue } // Private takes precedence, so finding 1 private property defines the access level. - if modifiers.contains(where: {$0.name.tokenKind == .privateKeyword && $0.detail == nil}) { + if modifiers.contains(where: {$0.name.tokenKind == .keyword(.private) && $0.detail == nil}) { return .private } - if modifiers.contains(where: {$0.name.tokenKind == .fileprivateKeyword && $0.detail == nil}) { + if modifiers.contains(where: {$0.name.tokenKind == .keyword(.fileprivate) && $0.detail == nil}) { hasFileprivate = true // Can't break here because a later property might be private. } diff --git a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift index bc58af4b8..08a6783c6 100644 --- a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift @@ -101,7 +101,7 @@ fileprivate func updateWithWhereCondition( if lastToken?.trailingTrivia.containsSpaces == false { whereLeadingTrivia = .spaces(1) } - let whereKeyword = TokenSyntax.whereKeyword( + let whereKeyword = TokenSyntax.keyword(.where, leadingTrivia: whereLeadingTrivia, trailingTrivia: .spaces(1) ) diff --git a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift index c142e5d08..dd30f43b2 100644 --- a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift @@ -121,7 +121,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { // If a function is marked as `rethrows`, it doesn't have any errors of its // own that should be documented. So only require documentation for // functions marked `throws`. - let needsThrowsDesc = throwsOrRethrowsKeyword?.tokenKind == .throwsKeyword + let needsThrowsDesc = throwsOrRethrowsKeyword?.tokenKind == .keyword(.throws) if !needsThrowsDesc && throwsDesc != nil { diagnose(.removeThrowsComment(funcName: name), on: throwsOrRethrowsKeyword ?? node.firstToken) From dcbf05d38b62c0a81582e1221242cc3632cd3cd4 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 13 Jan 2023 18:36:53 +0100 Subject: [PATCH 011/332] Remove errorTokens from `CodeBlockItem` --- Sources/SwiftFormatRules/NoAssignmentInExpressions.swift | 6 ++---- .../SwiftFormatRules/OneVariableDeclarationPerLine.swift | 3 +-- Sources/SwiftFormatRules/UseEarlyExits.swift | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift index 16ddb416f..cebf79e97 100644 --- a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift @@ -57,8 +57,7 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { newItems.append( CodeBlockItemSyntax( item: .expr(ExprSyntax(assignmentExpr)), - semicolon: nil, - errorTokens: nil + semicolon: nil ) .withLeadingTrivia( (returnStmt.leadingTrivia ?? []) + (assignmentExpr.leadingTrivia ?? [])) @@ -66,8 +65,7 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { newItems.append( CodeBlockItemSyntax( item: .stmt(StmtSyntax(returnStmt.withExpression(nil))), - semicolon: nil, - errorTokens: nil + semicolon: nil ) .withLeadingTrivia([.newlines(1)]) .withTrailingTrivia(returnStmt.trailingTrivia?.withoutLeadingSpaces() ?? [])) diff --git a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift b/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift index a8829bebc..77662f92d 100644 --- a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift +++ b/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift @@ -51,8 +51,7 @@ public final class OneVariableDeclarationPerLine: SyntaxFormatRule { var splitter = VariableDeclSplitter { CodeBlockItemSyntax( item: .decl(DeclSyntax($0)), - semicolon: nil, - errorTokens: nil) + semicolon: nil) } newItems.append(contentsOf: splitter.nodes(bySplitting: visitedDecl)) } diff --git a/Sources/SwiftFormatRules/UseEarlyExits.swift b/Sources/SwiftFormatRules/UseEarlyExits.swift index ae1adce30..ff68555ab 100644 --- a/Sources/SwiftFormatRules/UseEarlyExits.swift +++ b/Sources/SwiftFormatRules/UseEarlyExits.swift @@ -78,7 +78,7 @@ public final class UseEarlyExits: SyntaxFormatRule { var items = [ CodeBlockItemSyntax( - item: .stmt(StmtSyntax(guardStatement)), semicolon: nil, errorTokens: nil), + item: .stmt(StmtSyntax(guardStatement)), semicolon: nil), ] items.append(contentsOf: trueBlock.statements) return items From 42a8904822e783490552cc03643583b5159a5fde Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 25 Jan 2023 14:28:46 +0100 Subject: [PATCH 012/332] Adjustments for re-structured effect specifiers --- .../TokenStreamCreator.swift | 43 +++++++++++-------- .../ReturnVoidInsteadOfEmptyTuple.swift | 7 ++- .../UseShorthandTypeNames.swift | 13 +++--- .../UseSingleLinePropertyGetter.swift | 3 +- .../UseSynthesizedInitializer.swift | 2 +- .../ValidateDocumentationComments.swift | 2 +- 6 files changed, 38 insertions(+), 32 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index f756dcd48..aaed3f4ae 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -450,17 +450,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AccessorDeclSyntax) -> SyntaxVisitorContinueKind { arrangeAttributeList(node.attributes) - if let asyncKeyword = node.asyncKeyword { - if node.throwsKeyword != nil { + if let asyncKeyword = node.effectSpecifiers?.asyncSpecifier { + if node.effectSpecifiers?.throwsSpecifier != nil { before(asyncKeyword, tokens: .break, .open) } else { before(asyncKeyword, tokens: .break) } } - if let throwsKeyword = node.throwsKeyword { - before(node.throwsKeyword, tokens: .break) - if node.asyncKeyword != nil { + if let throwsKeyword = node.effectSpecifiers?.throwsSpecifier { + before(node.effectSpecifiers?.throwsSpecifier, tokens: .break) + if node.effectSpecifiers?.asyncSpecifier != nil { after(throwsKeyword, tokens: .close) } } @@ -1132,9 +1132,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } - before(node.asyncKeyword, tokens: .break) - before(node.throwsTok, tokens: .break) - if let asyncKeyword = node.asyncKeyword, let throwsTok = node.throwsTok { + before(node.effectSpecifiers?.asyncSpecifier, tokens: .break) + before(node.effectSpecifiers?.throwsSpecifier, tokens: .break) + if let asyncKeyword = node.effectSpecifiers?.asyncSpecifier, let throwsTok = node.effectSpecifiers?.throwsSpecifier { before(asyncKeyword, tokens: .open) after(throwsTok, tokens: .close) } @@ -1256,7 +1256,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ReturnClauseSyntax) -> SyntaxVisitorContinueKind { - after(node.arrow, tokens: .space) + if node.parent?.is(FunctionTypeSyntax.self) ?? false { + // `FunctionTypeSyntax` used to not use `ReturnClauseSyntax` and had + // slightly different formatting behavior than the normal + // `ReturnClauseSyntax`. To maintain the previous formatting behavior, + // add a special case. + before(node.arrow, tokens: .break) + before(node.returnType.firstToken, tokens: .break) + } else { + after(node.arrow, tokens: .space) + } // Member type identifier is used when the return type is a member of another type. Add a group // here so that the base, dot, and member type are kept together when they fit. @@ -1500,10 +1509,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: FunctionTypeSyntax) -> SyntaxVisitorContinueKind { after(node.leftParen, tokens: .break(.open, size: 0), .open) before(node.rightParen, tokens: .break(.close, size: 0), .close) - before(node.asyncKeyword, tokens: .break) - before(node.throwsOrRethrowsKeyword, tokens: .break) - before(node.arrow, tokens: .break) - before(node.returnType.firstToken, tokens: .break) + before(node.effectSpecifiers?.asyncSpecifier, tokens: .break) + before(node.effectSpecifiers?.throwsSpecifier, tokens: .break) return .visitChildren } @@ -1723,10 +1730,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: FunctionSignatureSyntax) -> SyntaxVisitorContinueKind { - before(node.asyncOrReasyncKeyword, tokens: .break) - before(node.throwsOrRethrowsKeyword, tokens: .break) - if let asyncOrReasyncKeyword = node.asyncOrReasyncKeyword, - let throwsOrRethrowsKeyword = node.throwsOrRethrowsKeyword + before(node.effectSpecifiers?.asyncSpecifier, tokens: .break) + before(node.effectSpecifiers?.throwsSpecifier, tokens: .break) + if let asyncOrReasyncKeyword = node.effectSpecifiers?.asyncSpecifier, + let throwsOrRethrowsKeyword = node.effectSpecifiers?.throwsSpecifier { before(asyncOrReasyncKeyword, tokens: .open) after(throwsOrRethrowsKeyword, tokens: .close) @@ -1868,7 +1875,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: ArrowExprSyntax) -> SyntaxVisitorContinueKind { // The break before the `throws` keyword is inserted at the `InfixOperatorExpr` level so that it // is placed in the correct relative position to the group surrounding the "operator". - after(node.throwsToken, tokens: .break) + after(node.effectSpecifiers?.throwsSpecifier, tokens: .break) return .visitChildren } diff --git a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift index e8e7c89d5..4322b3e50 100644 --- a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift @@ -23,7 +23,7 @@ import SwiftSyntax /// Format: `-> ()` is replaced with `-> Void` public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { public override func visit(_ node: FunctionTypeSyntax) -> TypeSyntax { - guard let returnType = node.returnType.as(TupleTypeSyntax.self), + guard let returnType = node.output.returnType.as(TupleTypeSyntax.self), returnType.elements.count == 0 else { return super.visit(node) @@ -43,7 +43,10 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { // `(Int -> ()) -> ()` should become `(Int -> Void) -> Void`). let arguments = visit(node.arguments) let voidKeyword = makeVoidIdentifierType(toReplace: returnType) - return TypeSyntax(node.withArguments(arguments).withReturnType(TypeSyntax(voidKeyword))) + var rewrittenNode = node + rewrittenNode.arguments = arguments + rewrittenNode.output.returnType = TypeSyntax(voidKeyword) + return TypeSyntax(rewrittenNode) } public override func visit(_ node: ClosureSignatureSyntax) -> ClosureSignatureSyntax { diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 150a48099..dfdcf3f73 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -419,10 +419,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { leftParen: functionType.leftParen, argumentTypes: functionType.arguments, rightParen: functionType.rightParen, - asyncKeyword: functionType.asyncKeyword, - throwsOrRethrowsKeyword: functionType.throwsOrRethrowsKeyword, - arrow: functionType.arrow, - returnType: functionType.returnType + effectSpecifiers: functionType.effectSpecifiers, + arrow: functionType.output.arrow, + returnType: functionType.output.returnType ) return ExprSyntax(result) @@ -461,8 +460,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { leftParen: TokenSyntax, argumentTypes: TupleTypeElementListSyntax, rightParen: TokenSyntax, - asyncKeyword: TokenSyntax?, - throwsOrRethrowsKeyword: TokenSyntax?, + effectSpecifiers: TypeEffectSpecifiersSyntax?, arrow: TokenSyntax, returnType: TypeSyntax ) -> SequenceExprSyntax? { @@ -478,8 +476,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { elementList: argumentTypeExprs, rightParen: rightParen) let arrowExpr = ArrowExprSyntax( - asyncKeyword: asyncKeyword, - throwsToken: throwsOrRethrowsKeyword, + effectSpecifiers: effectSpecifiers, arrowToken: arrow) return SequenceExprSyntax( diff --git a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift index 8b4a35083..4c79820d7 100644 --- a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift @@ -29,8 +29,7 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { acc.accessorKind.tokenKind == .keyword(.get), acc.attributes == nil, acc.modifier == nil, - acc.asyncKeyword == nil, - acc.throwsKeyword == nil + acc.effectSpecifiers == nil else { return node } diagnose(.removeExtraneousGetBlock, on: acc) diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift index 576c0f093..9915258ab 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift @@ -40,7 +40,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { // Collect any possible redundant initializers into a list } else if let initDecl = member.as(InitializerDeclSyntax.self) { guard initDecl.optionalMark == nil else { continue } - guard initDecl.signature.throwsOrRethrowsKeyword == nil else { continue } + guard initDecl.signature.effectSpecifiers?.throwsSpecifier == nil else { continue } initializers.append(initDecl) } } diff --git a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift index dd30f43b2..a145879be 100644 --- a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift @@ -63,7 +63,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { } validateThrows( - signature.throwsOrRethrowsKeyword, name: name, throwsDesc: commentInfo.throwsDescription, node: node) + signature.effectSpecifiers?.throwsSpecifier, name: name, throwsDesc: commentInfo.throwsDescription, node: node) validateReturn( returnClause, name: name, returnDesc: commentInfo.returnsDescription, node: node) let funcParameters = funcParametersIdentifiers(in: signature.input.parameterList) From 1032b1e296b210578c1617a0c80f58740add1323 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 20 Jan 2023 17:37:18 +0100 Subject: [PATCH 013/332] Adjustment for removed `with` functions --- .../LegacyTriviaBehavior.swift | 4 ++-- .../TokenStreamCreator.swift | 2 +- .../AddModifierRewriter.swift | 20 ++++++++-------- .../SwiftFormatRules/DoNotUseSemicolons.swift | 2 +- .../FileScopedDeclarationPrivacy.swift | 24 +++++++++---------- .../SwiftFormatRules/FullyIndirectEnum.swift | 8 +++---- .../GroupNumericLiterals.swift | 2 +- .../SwiftFormatRules/NeverForceUnwrap.swift | 4 ++-- ...NeverUseImplicitlyUnwrappedOptionals.swift | 2 +- .../NoAccessLevelOnExtensionDeclaration.swift | 14 +++++------ .../NoAssignmentInExpressions.swift | 10 ++++---- .../NoCasesWithOnlyFallthrough.swift | 11 +++++---- .../NoEmptyTrailingClosureParentheses.swift | 6 ++--- .../NoLabelsInCasePatterns.swift | 12 +++++----- .../NoParensAroundConditions.swift | 18 +++++++------- .../NoVoidReturnOnFunctionSignature.swift | 4 ++-- Sources/SwiftFormatRules/OneCasePerLine.swift | 18 +++++++------- .../OneVariableDeclarationPerLine.swift | 8 +++---- Sources/SwiftFormatRules/OrderedImports.swift | 3 ++- Sources/SwiftFormatRules/ReplaceTrivia.swift | 4 ++-- .../ReturnVoidInsteadOfEmptyTuple.swift | 2 +- .../SemicolonSyntaxProtocol.swift | 3 +-- .../TokenSyntax+Convenience.swift | 8 +++---- .../UseShorthandTypeNames.swift | 8 +++---- .../UseSingleLinePropertyGetter.swift | 2 +- .../UseWhereClausesInForLoops.swift | 4 ++-- 26 files changed, 102 insertions(+), 101 deletions(-) diff --git a/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift b/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift index 6f42bedab..e1c452728 100644 --- a/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift +++ b/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift @@ -17,7 +17,7 @@ private final class LegacyTriviaBehaviorRewriter: SyntaxRewriter { override func visit(_ token: TokenSyntax) -> TokenSyntax { var token = token if let pendingLeadingTrivia = pendingLeadingTrivia { - token = token.withLeadingTrivia(pendingLeadingTrivia + token.leadingTrivia) + token = token.with(\.leadingTrivia, pendingLeadingTrivia + token.leadingTrivia) self.pendingLeadingTrivia = nil } if token.nextToken != nil, @@ -25,7 +25,7 @@ private final class LegacyTriviaBehaviorRewriter: SyntaxRewriter { { pendingLeadingTrivia = Trivia(pieces: Array(token.trailingTrivia[firstIndexToMove...])) token = - token.withTrailingTrivia(Trivia(pieces: Array(token.trailingTrivia[.. TokenSyntax { if let rewrittenTrivia = rewriteTokenTriviaMap[token] { - return token.withLeadingTrivia(rewrittenTrivia) + return token.with(\.leadingTrivia, rewrittenTrivia) } return token } diff --git a/Sources/SwiftFormatRules/AddModifierRewriter.swift b/Sources/SwiftFormatRules/AddModifierRewriter.swift index 495038d74..4f10b4c85 100644 --- a/Sources/SwiftFormatRules/AddModifierRewriter.swift +++ b/Sources/SwiftFormatRules/AddModifierRewriter.swift @@ -33,7 +33,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { // Put accessor keyword before the first modifier keyword in the declaration let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.withModifiers(newModifiers)) + return DeclSyntax(node.with(\.modifiers, newModifiers)) } override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax { @@ -46,7 +46,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.withModifiers(newModifiers)) + return DeclSyntax(node.with(\.modifiers, newModifiers)) } override func visit(_ node: AssociatedtypeDeclSyntax) -> DeclSyntax { @@ -59,7 +59,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.withModifiers(newModifiers)) + return DeclSyntax(node.with(\.modifiers, newModifiers)) } override func visit(_ node: ClassDeclSyntax) -> DeclSyntax { @@ -72,7 +72,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.withModifiers(newModifiers)) + return DeclSyntax(node.with(\.modifiers, newModifiers)) } override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { @@ -85,7 +85,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.withModifiers(newModifiers)) + return DeclSyntax(node.with(\.modifiers, newModifiers)) } override func visit(_ node: ProtocolDeclSyntax) -> DeclSyntax { @@ -98,7 +98,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.withModifiers(newModifiers)) + return DeclSyntax(node.with(\.modifiers, newModifiers)) } override func visit(_ node: StructDeclSyntax) -> DeclSyntax { @@ -111,7 +111,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.withModifiers(newModifiers)) + return DeclSyntax(node.with(\.modifiers, newModifiers)) } override func visit(_ node: TypealiasDeclSyntax) -> DeclSyntax { @@ -124,7 +124,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.withModifiers(newModifiers)) + return DeclSyntax(node.with(\.modifiers, newModifiers)) } override func visit(_ node: InitializerDeclSyntax) -> DeclSyntax { @@ -137,7 +137,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.withModifiers(newModifiers)) + return DeclSyntax(node.with(\.modifiers, newModifiers)) } override func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax { @@ -150,7 +150,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.withModifiers(newModifiers)) + return DeclSyntax(node.with(\.modifiers, newModifiers)) } /// Moves trivia in the given node to correct the placement of potentially displaced trivia in the diff --git a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift index 26d3bb035..0aa080a3b 100644 --- a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift @@ -87,7 +87,7 @@ public final class DoNotUseSemicolons: SyntaxFormatRule { // This discards any trailingTrivia from the semicolon. That trivia is at most some spaces, // and the pretty printer adds any necessary spaces so it's safe to discard. - newItem = newItem.withSemicolon(nil) + newItem = newItem.with(\.semicolon, nil) if idx < node.count - 1 { diagnose(.removeSemicolonAndMove, on: semicolon) } else { diff --git a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift index c6f64d719..30518c97c 100644 --- a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift +++ b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift @@ -24,7 +24,7 @@ import SwiftSyntax public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { public override func visit(_ node: SourceFileSyntax) -> SourceFileSyntax { let newStatements = rewrittenCodeBlockItems(node.statements) - return node.withStatements(newStatements) + return node.with(\.statements, newStatements) } /// Returns a list of code block items equivalent to the given list, but where any file-scoped @@ -40,7 +40,7 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { let newCodeBlockItems = codeBlockItems.map { codeBlockItem -> CodeBlockItemSyntax in switch codeBlockItem.item { case .decl(let decl): - return codeBlockItem.withItem(.decl(rewrittenDecl(decl))) + return codeBlockItem.with(\.item, .decl(rewrittenDecl(decl))) default: return codeBlockItem } @@ -59,43 +59,43 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { return DeclSyntax(rewrittenDecl( functionDecl, modifiers: functionDecl.modifiers, - factory: functionDecl.withModifiers)) + factory: { functionDecl.with(\.modifiers, $0) })) case .variableDecl(let variableDecl): return DeclSyntax(rewrittenDecl( variableDecl, modifiers: variableDecl.modifiers, - factory: variableDecl.withModifiers)) + factory: { variableDecl.with(\.modifiers, $0) })) case .classDecl(let classDecl): return DeclSyntax(rewrittenDecl( classDecl, modifiers: classDecl.modifiers, - factory: classDecl.withModifiers)) + factory: { classDecl.with(\.modifiers, $0) })) case .structDecl(let structDecl): return DeclSyntax(rewrittenDecl( structDecl, modifiers: structDecl.modifiers, - factory: structDecl.withModifiers)) + factory: { structDecl.with(\.modifiers, $0) })) case .enumDecl(let enumDecl): return DeclSyntax(rewrittenDecl( enumDecl, modifiers: enumDecl.modifiers, - factory: enumDecl.withModifiers)) + factory: { enumDecl.with(\.modifiers, $0) })) case .protocolDecl(let protocolDecl): return DeclSyntax(rewrittenDecl( protocolDecl, modifiers: protocolDecl.modifiers, - factory: protocolDecl.withModifiers)) + factory: { protocolDecl.with(\.modifiers, $0) })) case .typealiasDecl(let typealiasDecl): return DeclSyntax(rewrittenDecl( typealiasDecl, modifiers: typealiasDecl.modifiers, - factory: typealiasDecl.withModifiers)) + factory: { typealiasDecl.with(\.modifiers, $0) })) default: return decl @@ -113,12 +113,12 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { let newClauses = ifConfigDecl.clauses.map { clause -> IfConfigClauseSyntax in switch clause.elements { case .statements(let codeBlockItemList)?: - return clause.withElements(.statements(rewrittenCodeBlockItems(codeBlockItemList))) + return clause.with(\.elements, .statements(rewrittenCodeBlockItems(codeBlockItemList))) default: return clause } } - return ifConfigDecl.withClauses(IfConfigClauseListSyntax(newClauses)) + return ifConfigDecl.with(\.clauses, IfConfigClauseListSyntax(newClauses)) } /// Returns a rewritten version of the given declaration if its modifier list contains `private` @@ -161,7 +161,7 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { let name = modifier.name if name.tokenKind == invalidAccess { diagnose(diagnostic, on: name) - return modifier.withName(name.withKind(validAccess)) + return modifier.with(\.name, name.withKind(validAccess)) } return modifier } diff --git a/Sources/SwiftFormatRules/FullyIndirectEnum.swift b/Sources/SwiftFormatRules/FullyIndirectEnum.swift index aa27cd906..aab82ed1f 100644 --- a/Sources/SwiftFormatRules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormatRules/FullyIndirectEnum.swift @@ -44,10 +44,10 @@ public final class FullyIndirectEnum: SyntaxFormatRule { return member } - let newCase = caseMember.withModifiers(modifiers.remove(name: "indirect")) + let newCase = caseMember.with(\.modifiers, modifiers.remove(name: "indirect")) let formattedCase = formatCase( unformattedCase: newCase, leadingTrivia: firstModifier.leadingTrivia) - return member.withDecl(DeclSyntax(formattedCase)) + return member.with(\.decl, DeclSyntax(formattedCase)) } // If the `indirect` keyword being added would be the first token in the decl, we need to move @@ -70,8 +70,8 @@ public final class FullyIndirectEnum: SyntaxFormatRule { name: TokenSyntax.identifier( "indirect", leadingTrivia: leadingTrivia, trailingTrivia: .spaces(1)), detail: nil) - let newMemberBlock = node.members.withMembers(MemberDeclListSyntax(newMembers)) - return DeclSyntax(newEnumDecl.addModifier(newModifier).withMembers(newMemberBlock)) + let newMemberBlock = node.members.with(\.members, MemberDeclListSyntax(newMembers)) + return DeclSyntax(newEnumDecl.addModifier(newModifier).with(\.members, newMemberBlock)) } /// Returns a value indicating whether all enum cases in the given list are indirect. diff --git a/Sources/SwiftFormatRules/GroupNumericLiterals.swift b/Sources/SwiftFormatRules/GroupNumericLiterals.swift index 2ca47b091..aa4ebb519 100644 --- a/Sources/SwiftFormatRules/GroupNumericLiterals.swift +++ b/Sources/SwiftFormatRules/GroupNumericLiterals.swift @@ -59,7 +59,7 @@ public final class GroupNumericLiterals: SyntaxFormatRule { } newDigits = isNegative ? "-" + newDigits : newDigits - let result = node.withDigits( + let result = node.with(\.digits, TokenSyntax.integerLiteral( newDigits, leadingTrivia: node.digits.leadingTrivia, diff --git a/Sources/SwiftFormatRules/NeverForceUnwrap.swift b/Sources/SwiftFormatRules/NeverForceUnwrap.swift index 7d23fd27e..4a50007f5 100644 --- a/Sources/SwiftFormatRules/NeverForceUnwrap.swift +++ b/Sources/SwiftFormatRules/NeverForceUnwrap.swift @@ -31,7 +31,7 @@ public final class NeverForceUnwrap: SyntaxLintRule { public override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren } - diagnose(.doNotForceUnwrap(name: node.expression.withoutTrivia().description), on: node) + diagnose(.doNotForceUnwrap(name: node.expression.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description), on: node) return .skipChildren } @@ -41,7 +41,7 @@ public final class NeverForceUnwrap: SyntaxLintRule { guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren } guard let questionOrExclamation = node.questionOrExclamationMark else { return .skipChildren } guard questionOrExclamation.tokenKind == .exclamationMark else { return .skipChildren } - diagnose(.doNotForceCast(name: node.typeName.withoutTrivia().description), on: node) + diagnose(.doNotForceCast(name: node.typeName.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description), on: node) return .skipChildren } } diff --git a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift index d11e21606..1f1d52cfe 100644 --- a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -59,7 +59,7 @@ public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule { guard let violation = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) else { return } diagnose( .doNotUseImplicitUnwrapping( - identifier: violation.wrappedType.withoutTrivia().description), on: type) + identifier: violation.wrappedType.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description), on: type) } } diff --git a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift index c1426bd52..dd6841bb2 100644 --- a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift @@ -39,7 +39,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { let accessKeywordToAdd: DeclModifierSyntax if keywordKind == .keyword(.private) { accessKeywordToAdd - = accessKeyword.withName(accessKeyword.name.withKind(.keyword(.fileprivate))) + = accessKeyword.with(\.name, accessKeyword.name.withKind(.keyword(.fileprivate))) } else { accessKeywordToAdd = accessKeyword } @@ -52,9 +52,9 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { on: node.extensionKeyword, token: node.extensionKeyword, leadingTrivia: accessKeyword.leadingTrivia) - let result = node.withMembers(newMembers) - .withModifiers(modifiers.remove(name: accessKeyword.name.text)) - .withExtensionKeyword(newKeyword) + let result = node.with(\.members, newMembers) + .with(\.modifiers, modifiers.remove(name: accessKeyword.name.text)) + .with(\.extensionKeyword, newKeyword) return DeclSyntax(result) // Internal keyword redundant, delete @@ -66,8 +66,8 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { on: node.extensionKeyword, token: node.extensionKeyword, leadingTrivia: accessKeyword.leadingTrivia) - let result = node.withModifiers(modifiers.remove(name: accessKeyword.name.text)) - .withExtensionKeyword(newKeyword) + let result = node.with(\.modifiers, modifiers.remove(name: accessKeyword.name.text)) + .with(\.extensionKeyword, newKeyword) return DeclSyntax(result) default: @@ -94,7 +94,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { let newDecl = addModifier(declaration: member, modifierKeyword: formattedKeyword) .as(DeclSyntax.self) else { continue } - newMembers.append(memberItem.withDecl(newDecl)) + newMembers.append(memberItem.with(\.decl, newDecl)) } return MemberDeclListSyntax(newMembers) } diff --git a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift index cebf79e97..8eda56e97 100644 --- a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift @@ -59,16 +59,16 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { item: .expr(ExprSyntax(assignmentExpr)), semicolon: nil ) - .withLeadingTrivia( + .with(\.leadingTrivia, (returnStmt.leadingTrivia ?? []) + (assignmentExpr.leadingTrivia ?? [])) - .withTrailingTrivia([])) + .with(\.trailingTrivia, [])) newItems.append( CodeBlockItemSyntax( - item: .stmt(StmtSyntax(returnStmt.withExpression(nil))), + item: .stmt(StmtSyntax(returnStmt.with(\.expression, nil))), semicolon: nil ) - .withLeadingTrivia([.newlines(1)]) - .withTrailingTrivia(returnStmt.trailingTrivia?.withoutLeadingSpaces() ?? [])) + .with(\.leadingTrivia, [.newlines(1)]) + .with(\.trailingTrivia, returnStmt.trailingTrivia?.withoutLeadingSpaces() ?? [])) default: newItems.append(newItem) diff --git a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift b/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift index 1ff77089f..b5aa6dc8e 100644 --- a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift +++ b/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift @@ -178,25 +178,26 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { // more items. newCaseItems.append(contentsOf: label.caseItems.dropLast()) newCaseItems.append( - label.caseItems.last!.withTrailingComma( + label.caseItems.last!.with( + \.trailingComma, TokenSyntax.commaToken(trailingTrivia: .spaces(1)))) // Diagnose the cases being collapsed. We do this for all but the last one in the array; the // last one isn't diagnosed because it will contain the body that applies to all the previous // cases. - diagnose(.collapseCase(name: label.caseItems.withoutTrivia().description), on: label) + diagnose(.collapseCase(name: label.caseItems.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description), on: label) } newCaseItems.append(contentsOf: labels.last!.caseItems) - let newCase = cases.last!.withLabel(.case( - labels.last!.withCaseItems(CaseItemListSyntax(newCaseItems)))) + let newCase = cases.last!.with(\.label, .case( + labels.last!.with(\.caseItems, CaseItemListSyntax(newCaseItems)))) // Only the first violation case can have displaced trivia, because any non-whitespace // trivia in the other violation cases would've prevented collapsing. if let displacedLeadingTrivia = cases.first!.leadingTrivia?.withoutLastLine() { let existingLeadingTrivia = newCase.leadingTrivia ?? [] let mergedLeadingTrivia = displacedLeadingTrivia + existingLeadingTrivia - return newCase.withLeadingTrivia(mergedLeadingTrivia) + return newCase.with(\.leadingTrivia, mergedLeadingTrivia) } else { return newCase } diff --git a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift index 36924c962..2df45668d 100644 --- a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift +++ b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift @@ -29,7 +29,7 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { { return super.visit(node) } - guard let name = node.calledExpression.lastToken?.withoutTrivia() else { + guard let name = node.calledExpression.lastToken?.with(\.leadingTrivia, []).with(\.trailingTrivia, []) else { return super.visit(node) } @@ -45,8 +45,8 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { token: rewrittenCalledExpr.lastToken, trailingTrivia: .spaces(1)) let formattedClosure = visit(trailingClosure).as(ClosureExprSyntax.self) - let result = node.withLeftParen(nil).withRightParen(nil).withCalledExpression(formattedExp) - .withTrailingClosure(formattedClosure) + let result = node.with(\.leftParen, nil).with(\.rightParen, nil).with(\.calledExpression, formattedExp) + .with(\.trailingClosure, formattedClosure) return ExprSyntax(result) } } diff --git a/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift b/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift index 99525809b..07d02c293 100644 --- a/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift +++ b/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift @@ -50,23 +50,23 @@ public final class NoLabelsInCasePatterns: SyntaxFormatRule { } // Remove label if it's the same as the value identifier - let name = valueBinding.valuePattern.withoutTrivia().description + let name = valueBinding.valuePattern.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description guard name == label.text else { newArgs.append(argument) continue } diagnose(.removeRedundantLabel(name: name), on: label) - newArgs.append(argument.withLabel(nil).withColon(nil)) + newArgs.append(argument.with(\.label, nil).with(\.colon, nil)) } let newArgList = TupleExprElementListSyntax(newArgs) - let newFuncCall = funcCall.withArgumentList(newArgList) - let newExpPat = expPat.withExpression(ExprSyntax(newFuncCall)) - let newItem = item.withPattern(PatternSyntax(newExpPat)) + let newFuncCall = funcCall.with(\.argumentList, newArgList) + let newExpPat = expPat.with(\.expression, ExprSyntax(newFuncCall)) + let newItem = item.with(\.pattern, PatternSyntax(newExpPat)) newCaseItems.append(newItem) } let newCaseItemList = CaseItemListSyntax(newCaseItems) - return node.withCaseItems(newCaseItemList) + return node.with(\.caseItems, newCaseItemList) } } diff --git a/Sources/SwiftFormatRules/NoParensAroundConditions.swift b/Sources/SwiftFormatRules/NoParensAroundConditions.swift index a2aa8df1e..df8c60bb1 100644 --- a/Sources/SwiftFormatRules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormatRules/NoParensAroundConditions.swift @@ -54,11 +54,11 @@ public final class NoParensAroundConditions: SyntaxFormatRule { public override func visit(_ node: IfStmtSyntax) -> StmtSyntax { let conditions = visit(node.conditions) - var result = node.withIfKeyword(node.ifKeyword.withOneTrailingSpace()) - .withConditions(conditions) - .withBody(visit(node.body)) + var result = node.with(\.ifKeyword, node.ifKeyword.withOneTrailingSpace()) + .with(\.conditions, conditions) + .with(\.body, visit(node.body)) if let elseBody = node.elseBody { - result = result.withElseBody(visit(elseBody)) + result = result.with(\.elseBody, visit(elseBody)) } return StmtSyntax(result) } @@ -69,7 +69,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { else { return super.visit(node) } - return node.withCondition(.expression(extractExpr(tup))) + return node.with(\.condition, .expression(extractExpr(tup))) } /// FIXME(hbh): Parsing for SwitchStmtSyntax is not implemented. @@ -80,7 +80,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { return super.visit(node) } return StmtSyntax( - node.withExpression(extractExpr(tup)).withCases(visit(node.cases))) + node.with(\.expression, extractExpr(tup)).with(\.cases, visit(node.cases))) } public override func visit(_ node: RepeatWhileStmtSyntax) -> StmtSyntax { @@ -89,9 +89,9 @@ public final class NoParensAroundConditions: SyntaxFormatRule { else { return super.visit(node) } - let newNode = node.withCondition(extractExpr(tup)) - .withWhileKeyword(node.whileKeyword.withOneTrailingSpace()) - .withBody(visit(node.body)) + let newNode = node.with(\.condition, extractExpr(tup)) + .with(\.whileKeyword, node.whileKeyword.withOneTrailingSpace()) + .with(\.body, visit(node.body)) return StmtSyntax(newNode) } } diff --git a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift b/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift index 61487b801..3c6f40fe8 100644 --- a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift +++ b/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift @@ -26,11 +26,11 @@ public final class NoVoidReturnOnFunctionSignature: SyntaxFormatRule { public override func visit(_ node: FunctionSignatureSyntax) -> FunctionSignatureSyntax { if let ret = node.output?.returnType.as(SimpleTypeIdentifierSyntax.self), ret.name.text == "Void" { diagnose(.removeRedundantReturn("Void"), on: ret) - return node.withOutput(nil) + return node.with(\.output, nil) } if let tup = node.output?.returnType.as(TupleTypeSyntax.self), tup.elements.isEmpty { diagnose(.removeRedundantReturn("()"), on: tup) - return node.withOutput(nil) + return node.with(\.output, nil) } return node } diff --git a/Sources/SwiftFormatRules/OneCasePerLine.swift b/Sources/SwiftFormatRules/OneCasePerLine.swift index 65c88db4f..c30c1a8a1 100644 --- a/Sources/SwiftFormatRules/OneCasePerLine.swift +++ b/Sources/SwiftFormatRules/OneCasePerLine.swift @@ -60,7 +60,7 @@ public final class OneCasePerLine: SyntaxFormatRule { // Remove the trailing comma on the final element, if there was one. if last.trailingComma != nil { - elements[elements.count - 1] = last.withTrailingComma(nil) + elements[elements.count - 1] = last.with(\.trailingComma, nil) } defer { elements.removeAll() } @@ -70,14 +70,14 @@ public final class OneCasePerLine: SyntaxFormatRule { /// Creates and returns a new `EnumCaseDeclSyntax` with the given elements, based on the current /// basis declaration, and updates the comment preserving state if needed. mutating func makeCaseDeclFromBasis(elements: [EnumCaseElementSyntax]) -> EnumCaseDeclSyntax { - let caseDecl = basis.withElements(EnumCaseElementListSyntax(elements)) + let caseDecl = basis.with(\.elements, EnumCaseElementListSyntax(elements)) if shouldKeepLeadingTrivia { shouldKeepLeadingTrivia = false // We don't bother preserving any indentation because the pretty printer will fix that up. // All we need to do here is ensure that there is a newline. - basis = basis.withLeadingTrivia(Trivia.newlines(1)) + basis = basis.with(\.leadingTrivia, Trivia.newlines(1)) } return caseDecl @@ -106,12 +106,12 @@ public final class OneCasePerLine: SyntaxFormatRule { diagnose(.moveAssociatedOrRawValueCase(name: element.identifier.text), on: element) if let caseDeclForCollectedElements = collector.makeCaseDeclAndReset() { - newMembers.append(member.withDecl(DeclSyntax(caseDeclForCollectedElements))) + newMembers.append(member.with(\.decl, DeclSyntax(caseDeclForCollectedElements))) } let separatedCaseDecl = - collector.makeCaseDeclFromBasis(elements: [element.withTrailingComma(nil)]) - newMembers.append(member.withDecl(DeclSyntax(separatedCaseDecl))) + collector.makeCaseDeclFromBasis(elements: [element.with(\.trailingComma, nil)]) + newMembers.append(member.with(\.decl, DeclSyntax(separatedCaseDecl))) } else { collector.addElement(element) } @@ -119,12 +119,12 @@ public final class OneCasePerLine: SyntaxFormatRule { // Make sure to emit any trailing collected elements. if let caseDeclForCollectedElements = collector.makeCaseDeclAndReset() { - newMembers.append(member.withDecl(DeclSyntax(caseDeclForCollectedElements))) + newMembers.append(member.with(\.decl, DeclSyntax(caseDeclForCollectedElements))) } } - let newMemberBlock = node.members.withMembers(MemberDeclListSyntax(newMembers)) - return DeclSyntax(node.withMembers(newMemberBlock)) + let newMemberBlock = node.members.with(\.members, MemberDeclListSyntax(newMembers)) + return DeclSyntax(node.with(\.members, newMemberBlock)) } } diff --git a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift b/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift index 77662f92d..b21207b74 100644 --- a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift +++ b/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift @@ -139,7 +139,7 @@ private struct VariableDeclSplitter { // it's an initializer following other un-flushed lone identifier // bindings, that's not valid Swift. But in either case, we'll flush // them as a single decl. - bindingQueue.append(binding.withTrailingComma(nil)) + bindingQueue.append(binding.with(\.trailingComma, nil)) flushRemaining() } else if let typeAnnotation = binding.typeAnnotation { bindingQueue.append(binding) @@ -172,7 +172,7 @@ private struct VariableDeclSplitter { guard !bindingQueue.isEmpty else { return } let newDecl = - varDecl.withBindings(PatternBindingListSyntax(bindingQueue)) + varDecl.with(\.bindings, PatternBindingListSyntax(bindingQueue)) nodes.append(generator(newDecl)) fixOriginalVarDeclTrivia() @@ -191,9 +191,9 @@ private struct VariableDeclSplitter { assert(binding.initializer == nil) let newBinding = - binding.withTrailingComma(nil).withTypeAnnotation(typeAnnotation) + binding.with(\.trailingComma, nil).with(\.typeAnnotation, typeAnnotation) let newDecl = - varDecl.withBindings(PatternBindingListSyntax([newBinding])) + varDecl.with(\.bindings, PatternBindingListSyntax([newBinding])) nodes.append(generator(newDecl)) fixOriginalVarDeclTrivia() diff --git a/Sources/SwiftFormatRules/OrderedImports.swift b/Sources/SwiftFormatRules/OrderedImports.swift index 81e0fe2e3..6b505a455 100644 --- a/Sources/SwiftFormatRules/OrderedImports.swift +++ b/Sources/SwiftFormatRules/OrderedImports.swift @@ -131,7 +131,8 @@ public final class OrderedImports: SyntaxFormatRule { formatAndAppend(linesSection: lines[lastSliceStartIndex.. TokenSyntax { guard token == self.token else { return token } return token - .withLeadingTrivia(leadingTrivia ?? token.leadingTrivia) - .withTrailingTrivia(trailingTrivia ?? token.trailingTrivia) + .with(\.leadingTrivia, leadingTrivia ?? token.leadingTrivia) + .with(\.trailingTrivia, trailingTrivia ?? token.trailingTrivia) } } diff --git a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift index 4322b3e50..b1e29c06e 100644 --- a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift @@ -80,7 +80,7 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { input = node.input } let voidKeyword = makeVoidIdentifierType(toReplace: returnType) - return node.withInput(input).withOutput(output.withReturnType(TypeSyntax(voidKeyword))) + return node.with(\.input, input).with(\.output, output.with(\.returnType, TypeSyntax(voidKeyword))) } /// Returns a value indicating whether the leading trivia of the given token contained any diff --git a/Sources/SwiftFormatRules/SemicolonSyntaxProtocol.swift b/Sources/SwiftFormatRules/SemicolonSyntaxProtocol.swift index efe28452f..b0fd39950 100644 --- a/Sources/SwiftFormatRules/SemicolonSyntaxProtocol.swift +++ b/Sources/SwiftFormatRules/SemicolonSyntaxProtocol.swift @@ -14,8 +14,7 @@ import SwiftSyntax /// Protocol that declares support for accessing and modifying a token that represents a semicolon. protocol SemicolonSyntaxProtocol: SyntaxProtocol { - var semicolon: TokenSyntax? { get } - func withSemicolon(_ newSemicolon: TokenSyntax?) -> Self + var semicolon: TokenSyntax? { get set } } extension MemberDeclListItemSyntax: SemicolonSyntaxProtocol {} diff --git a/Sources/SwiftFormatRules/TokenSyntax+Convenience.swift b/Sources/SwiftFormatRules/TokenSyntax+Convenience.swift index 078b54fd1..c788b973d 100644 --- a/Sources/SwiftFormatRules/TokenSyntax+Convenience.swift +++ b/Sources/SwiftFormatRules/TokenSyntax+Convenience.swift @@ -15,23 +15,23 @@ import SwiftSyntax extension TokenSyntax { /// Returns this token with only one space at the end of its trailing trivia. func withOneTrailingSpace() -> TokenSyntax { - return withTrailingTrivia(trailingTrivia.withOneTrailingSpace()) + return with(\.trailingTrivia, trailingTrivia.withOneTrailingSpace()) } /// Returns this token with only one space at the beginning of its leading /// trivia. func withOneLeadingSpace() -> TokenSyntax { - return withLeadingTrivia(leadingTrivia.withOneLeadingSpace()) + return with(\.leadingTrivia, leadingTrivia.withOneLeadingSpace()) } /// Returns this token with only one newline at the end of its leading trivia. func withOneTrailingNewline() -> TokenSyntax { - return withTrailingTrivia(trailingTrivia.withOneTrailingNewline()) + return with(\.trailingTrivia, trailingTrivia.withOneTrailingNewline()) } /// Returns this token with only one newline at the beginning of its leading /// trivia. func withOneLeadingNewline() -> TokenSyntax { - return withLeadingTrivia(leadingTrivia.withOneLeadingNewline()) + return with(\.leadingTrivia, leadingTrivia.withOneLeadingNewline()) } } diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index dfdcf3f73..30d10e949 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -93,8 +93,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // Even if we don't shorten this specific type that we're visiting, we may have rewritten // something in the generic argument list that we recursively visited, so return the original // node with that swapped out. - let result = node.withGenericArgumentClause( - genericArgumentClause.withArguments(genericArgumentList)) + let result = node.with(\.genericArgumentClause, + genericArgumentClause.with(\.arguments, genericArgumentList)) return TypeSyntax(result) } @@ -180,8 +180,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // Even if we don't shorten this specific expression that we're visiting, we may have // rewritten something in the generic argument list that we recursively visited, so return the // original node with that swapped out. - let result = node.withGenericArgumentClause( - node.genericArgumentClause.withArguments(genericArgumentList)) + let result = node.with(\.genericArgumentClause, + node.genericArgumentClause.with(\.arguments, genericArgumentList)) return ExprSyntax(result) } diff --git a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift index 4c79820d7..24f569f12 100644 --- a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift @@ -37,7 +37,7 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { let newBlock = CodeBlockSyntax( leftBrace: accessorBlock.leftBrace, statements: body.statements, rightBrace: accessorBlock.rightBrace) - return node.withAccessor(.getter(newBlock)) + return node.with(\.accessor, .getter(newBlock)) } } diff --git a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift index 08a6783c6..67f364a25 100644 --- a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift @@ -111,8 +111,8 @@ fileprivate func updateWithWhereCondition( ) // Replace the where clause and extract the body from the IfStmt. - let newBody = node.body.withStatements(statements) - return node.withWhereClause(whereClause).withBody(newBody) + let newBody = node.body.with(\.statements, statements) + return node.with(\.whereClause, whereClause).with(\.body, newBody) } extension Finding.Message { From 02a366d843af63942adc5ce228d6bb7880ae6a97 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 25 Jan 2023 15:41:15 -0800 Subject: [PATCH 014/332] Update swift-format to account for new multiline string tree structure. This is a companion to https://github.com/apple/swift-syntax/pull/1255. The new structure of multiline strings yielded some nice cleanup of the way we handle those strings *directly*, but to keep the existing indentation decisions, some parts of multiline string processing bled out into other areas. Such is life. --- .../LegacyTriviaBehavior.swift | 2 +- .../SwiftFormatCore/Trivia+Convenience.swift | 10 + .../TokenStreamCreator.swift | 209 ++++++++++-------- .../StringTests.swift | 152 ++++++++++++- 4 files changed, 282 insertions(+), 91 deletions(-) diff --git a/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift b/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift index 6f42bedab..b48e8c6fa 100644 --- a/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift +++ b/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift @@ -36,7 +36,7 @@ private final class LegacyTriviaBehaviorRewriter: SyntaxRewriter { /// behavior. private func shouldTriviaPieceBeMoved(_ piece: TriviaPiece) -> Bool { switch piece { - case .spaces, .tabs, .unexpectedText: + case .spaces, .tabs, .unexpectedText, .backslashes: return false default: return true diff --git a/Sources/SwiftFormatCore/Trivia+Convenience.swift b/Sources/SwiftFormatCore/Trivia+Convenience.swift index f69091b88..ab56031a2 100644 --- a/Sources/SwiftFormatCore/Trivia+Convenience.swift +++ b/Sources/SwiftFormatCore/Trivia+Convenience.swift @@ -153,4 +153,14 @@ extension Trivia { return false }) } + + /// Returns `true` if this trivia contains any backslahes (used for multiline string newline + /// suppression). + public var containsBackslashes: Bool { + return contains( + where: { + if case .backslashes = $0 { return true } + return false + }) + } } diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index f756dcd48..f31c115f7 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -33,10 +33,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// appended since that break. private var canMergeNewlinesIntoLastBreak = false - /// Keeps track of the prefix length of multiline string segments when they are visited so that - /// the prefix can be stripped at the beginning of lines before the text is added to the token - /// stream. - private var pendingMultilineStringSegmentPrefixLengths = [TokenSyntax: Int]() + /// Keeps track of the kind of break that should be used inside a multiline string. This differs + /// depending on surrounding context due to some tricky special cases, so this lets us pass that + /// information down to the strings that need it. + private var pendingMultilineStringBreakKinds = [StringLiteralExprSyntax: BreakKind]() /// Lists tokens that shouldn't be appended to the token stream as `syntax` tokens. They will be /// printed conditionally using a different type of token. @@ -659,7 +659,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ReturnStmtSyntax) -> SyntaxVisitorContinueKind { - before(node.expression?.firstToken, tokens: .break) + if let expression = node.expression { + if leftmostMultilineStringLiteral(of: expression) != nil { + before(expression.firstToken, tokens: .break(.open)) + after(expression.lastToken, tokens: .break(.close(mustBreak: false))) + } else { + before(expression.firstToken, tokens: .break) + } + } return .visitChildren } @@ -1035,21 +1042,32 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.firstToken, tokens: .open) } - // If we have an open delimiter following the colon, use a space instead of a continuation - // break so that we don't awkwardly shift the delimiter down and indent it further if it - // wraps. - let tokenAfterColon: Token = startsWithOpenDelimiter(Syntax(node.expression)) ? .space : .break + var additionalEndTokens = [Token]() + if let colon = node.colon { + // If we have an open delimiter following the colon, use a space instead of a continuation + // break so that we don't awkwardly shift the delimiter down and indent it further if it + // wraps. + var tokensAfterColon: [Token] = [ + startsWithOpenDelimiter(Syntax(node.expression)) ? .space : .break + ] - after(node.colon, tokens: tokenAfterColon) + if leftmostMultilineStringLiteral(of: node.expression) != nil { + tokensAfterColon.append(.break(.open(kind: .block), size: 0)) + additionalEndTokens = [.break(.close(mustBreak: false), size: 0)] + } + + after(colon, tokens: tokensAfterColon) + } if let trailingComma = node.trailingComma { + before(trailingComma, tokens: additionalEndTokens) var afterTrailingComma: [Token] = [.break(.same)] if shouldGroup { afterTrailingComma.insert(.close, at: 0) } after(trailingComma, tokens: afterTrailingComma) } else if shouldGroup { - after(node.lastToken, tokens: .close) + after(node.lastToken, tokens: additionalEndTokens + [.close]) } } @@ -1774,8 +1792,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // If the rhs starts with a parenthesized expression, stack indentation around it. // Otherwise, use regular continuation breaks. - if let (unindentingNode, _) = stackedIndentationBehavior(after: binOp, rhs: rhs) { - beforeTokens = [.break(.open(kind: .continuation))] + if let (unindentingNode, _, breakKind) = stackedIndentationBehavior(after: binOp, rhs: rhs) + { + beforeTokens = [.break(.open(kind: breakKind))] after(unindentingNode.lastToken, tokens: [.break(.close(mustBreak: false), size: 0)]) } else { beforeTokens = [.break(.continue)] @@ -1790,7 +1809,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } after(binOp.lastToken, tokens: beforeTokens) - } else if let (unindentingNode, shouldReset) = + } else if let (unindentingNode, shouldReset, breakKind) = stackedIndentationBehavior(after: binOp, rhs: rhs) { // For parenthesized expressions and for unparenthesized usages of `&&` and `||`, we don't @@ -1800,7 +1819,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // use open-continuation/close pairs around such operators and their right-hand sides so // that the continuation breaks inside those scopes "stack", instead of receiving the // usual single-level "continuation line or not" behavior. - let openBreakTokens: [Token] = [.break(.open(kind: .continuation)), .open] + let openBreakTokens: [Token] = [.break(.open(kind: breakKind)), .open] if wrapsBeforeOperator { before(binOp.firstToken, tokens: openBreakTokens) } else { @@ -1921,8 +1940,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let initializer = node.initializer { let expr = initializer.value - if let (unindentingNode, _) = stackedIndentationBehavior(rhs: expr) { - after(initializer.equal, tokens: .break(.open(kind: .continuation))) + if let (unindentingNode, _, breakKind) = stackedIndentationBehavior(rhs: expr) { + after(initializer.equal, tokens: .break(.open(kind: breakKind))) after(unindentingNode.lastToken, tokens: .break(.close(mustBreak: false), size: 0)) } else { after(initializer.equal, tokens: .break(.continue)) @@ -2100,32 +2119,48 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: StringLiteralExprSyntax) -> SyntaxVisitorContinueKind { if node.openQuote.tokenKind == .multilineStringQuote { - // If it's a multiline string, the last segment of the literal will end with a newline and - // zero or more whitespace that indicates the amount of whitespace stripped from each line of - // the string literal. - if let lastSegment = node.segments.last?.as(StringSegmentSyntax.self), - let lastLine - = lastSegment.content.text.split(separator: "\n", omittingEmptySubsequences: false).last - { - let prefixCount = lastLine.count - - // Segments may be `StringSegmentSyntax` or `ExpressionSegmentSyntax`; for the purposes of - // newline handling and whitespace stripping, we only need to handle the former. - for segmentSyntax in node.segments { - guard let segment = segmentSyntax.as(StringSegmentSyntax.self) else { - continue - } - // Register the content tokens of the segments and the amount of leading whitespace to - // strip; this will be retrieved when we visit the token. - pendingMultilineStringSegmentPrefixLengths[segment.content] = prefixCount - } - } + // Looks up the correct break kind based on prior context. + let breakKind = pendingMultilineStringBreakKinds[node, default: .same] + after(node.openQuote, tokens: .break(breakKind, size: 0, newlines: .hard(count: 1))) + before(node.closeQuote, tokens: .break(breakKind, newlines: .hard(count: 1))) } return .visitChildren } override func visit(_ node: StringSegmentSyntax) -> SyntaxVisitorContinueKind { - return .visitChildren + // Looks up the correct break kind based on prior context. + func breakKind() -> BreakKind { + if let stringLiteralSegments = node.parent?.as(StringLiteralSegmentsSyntax.self), + let stringLiteralExpr = stringLiteralSegments.parent?.as(StringLiteralExprSyntax.self) + { + return pendingMultilineStringBreakKinds[stringLiteralExpr, default: .same] + } else { + return .same + } + } + + let segmentText = node.content.text + if segmentText.hasSuffix("\n") { + // If this is a multiline string segment, it will end in a newline. Remove the newline and + // append the rest of the string, followed by a break if it's not the last line before the + // closing quotes. (The `StringLiteralExpr` above does the closing break.) + let remainder = node.content.text.dropLast() + if !remainder.isEmpty { + appendToken(.syntax(String(remainder))) + } + appendToken(.break(breakKind(), newlines: .hard(count: 1))) + } else { + appendToken(.syntax(segmentText)) + } + + if node.trailingTrivia?.containsBackslashes == true { + // Segments with trailing backslashes won't end with a literal newline; the backslash is + // considered trivia. To preserve the original text and wrapping, we need to manually render + // the backslash and a break into the token stream. + appendToken(.syntax("\\")) + appendToken(.break(breakKind(), newlines: .hard(count: 1))) + } + return .skipChildren } override func visit(_ node: AssociatedtypeDeclSyntax) -> SyntaxVisitorContinueKind { @@ -2343,9 +2378,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { extractLeadingTrivia(token) closeScopeTokens.forEach(appendToken) - if let pendingSegmentIndex = pendingMultilineStringSegmentPrefixLengths.index(forKey: token) { - appendMultilineStringSegments(at: pendingSegmentIndex) - } else if !ignoredTokens.contains(token) { + if !ignoredTokens.contains(token) { // Otherwise, it's just a regular token, so add the text as-is. appendToken(.syntax(token.presence == .present ? token.text : "")) } @@ -2357,48 +2390,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .skipChildren } - /// Appends the contents of the pending multiline string segment at the given index in the - /// registration dictionary (removing it from that dictionary) to the token stream, splitting it - /// into lines along with required line breaks and stripping the leading whitespace. - private func appendMultilineStringSegments(at index: Dictionary.Index) { - let (token, prefixCount) = pendingMultilineStringSegmentPrefixLengths[index] - pendingMultilineStringSegmentPrefixLengths.remove(at: index) - - let lines = token.text.split(separator: "\n", omittingEmptySubsequences: false) - - // The first "line" is a special case. If it is non-empty, then it is a piece of text that - // immediately followed an interpolation segment on the same line of the string, like the - // " baz" in "foo bar \(x + y) baz". If that is the case, we need to insert that text before - // anything else. - let firstLine = lines.first! - if !firstLine.isEmpty { - appendToken(.syntax(String(firstLine))) - } - - // Add the remaining lines of the segment, preceding each with a newline and stripping the - // leading whitespace so that the pretty-printer can re-indent the string according to the - // standard rules that it would apply. - for line in lines.dropFirst() as ArraySlice { - appendNewlines(.hard) - - // Verify that the characters to be stripped are all spaces. If they are not, the string - // is not valid (no line should contain less leading whitespace than the line with the - // closing quotes), but the parser still allows this and it's flagged as an error later during - // compilation, so we don't want to destroy the user's text in that case. - let stringToAppend: Substring - if (line.prefix(prefixCount).allSatisfy { $0 == " " }) { - stringToAppend = line.dropFirst(prefixCount) - } else { - // Only strip as many spaces as we have. This will force the misaligned line to line up with - // the others; let's assume that's what the user wanted anyway. - stringToAppend = line.drop { $0 == " " } - } - if !stringToAppend.isEmpty { - appendToken(.syntax(String(stringToAppend))) - } - } - } - /// Appends the before-tokens of the given syntax token to the token stream. private func appendBeforeTokens(_ token: TokenSyntax) { if let before = beforeMap.removeValue(forKey: token) { @@ -3179,6 +3170,26 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } + /// Walks the expression and returns the leftmost multiline string literal (which might be the + /// expression itself) if the leftmost child is a multiline string literal. + /// + /// - Parameter expr: The expression whose leftmost multiline string literal should be returned. + /// - Returns: The leftmost multiline string literal, or nil if the leftmost subexpression was + /// not a multiline string literal. + private func leftmostMultilineStringLiteral(of expr: ExprSyntax) -> StringLiteralExprSyntax? { + switch Syntax(expr).as(SyntaxEnum.self) { + case .stringLiteralExpr(let stringLiteralExpr) + where stringLiteralExpr.openQuote.tokenKind == .multilineStringQuote: + return stringLiteralExpr + case .infixOperatorExpr(let infixOperatorExpr): + return leftmostMultilineStringLiteral(of: infixOperatorExpr.leftOperand) + case .ternaryExpr(let ternaryExpr): + return leftmostMultilineStringLiteral(of: ternaryExpr.conditionExpression) + default: + return nil + } + } + /// Returns the outermost node enclosing the given node whose closing delimiter(s) must be kept /// alongside the last token of the given node. Any tokens between `node.lastToken` and the /// returned node's `lastToken` are delimiter tokens that shouldn't be preceded by a break. @@ -3208,7 +3219,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func stackedIndentationBehavior( after operatorExpr: ExprSyntax? = nil, rhs: ExprSyntax - ) -> (unindentingNode: Syntax, shouldReset: Bool)? { + ) -> (unindentingNode: Syntax, shouldReset: Bool, breakKind: OpenBreakKind)? { // Check for logical operators first, and if it's that kind of operator, stack indentation // around the entire right-hand-side. We have to do this check before checking the RHS for // parentheses because if the user writes something like `... && (foo) > bar || ...`, we don't @@ -3227,9 +3238,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // paren into the right hand side by unindenting after the final closing paren. This glues // the paren to the last token of `rhs`. if let unindentingParenExpr = outermostEnclosingNode(from: Syntax(rhs)) { - return (unindentingNode: unindentingParenExpr, shouldReset: true) + return ( + unindentingNode: unindentingParenExpr, shouldReset: true, breakKind: .continuation) } - return (unindentingNode: Syntax(rhs), shouldReset: true) + return (unindentingNode: Syntax(rhs), shouldReset: true, breakKind: .continuation) } } @@ -3238,7 +3250,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let ternaryExpr = rhs.as(TernaryExprSyntax.self) { // We don't try to absorb any parens in this case, because the condition of a ternary cannot // be grouped with any exprs outside of the condition. - return (unindentingNode: Syntax(ternaryExpr.conditionExpression), shouldReset: false) + return ( + unindentingNode: Syntax(ternaryExpr.conditionExpression), shouldReset: false, + breakKind: .continuation) } // If the right-hand-side of the operator is or starts with a parenthesized expression, stack @@ -3249,9 +3263,26 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // paren into the right hand side by unindenting after the final closing paren. This glues the // paren to the last token of `rhs`. if let unindentingParenExpr = outermostEnclosingNode(from: Syntax(rhs)) { - return (unindentingNode: unindentingParenExpr, shouldReset: true) + return (unindentingNode: unindentingParenExpr, shouldReset: true, breakKind: .continuation) + } + + if let innerExpr = parenthesizedExpr.elementList.first?.expression, + let stringLiteralExpr = innerExpr.as(StringLiteralExprSyntax.self), + stringLiteralExpr.openQuote.tokenKind == .multilineStringQuote + { + pendingMultilineStringBreakKinds[stringLiteralExpr] = .continue + return nil } - return (unindentingNode: Syntax(parenthesizedExpr), shouldReset: false) + + return ( + unindentingNode: Syntax(parenthesizedExpr), shouldReset: false, breakKind: .continuation) + } + + // If the expression is a multiline string that is unparenthesized, create a block-based + // indentation scope and have the segments aligned inside it. + if let stringLiteralExpr = leftmostMultilineStringLiteral(of: rhs) { + pendingMultilineStringBreakKinds[stringLiteralExpr] = .same + return (unindentingNode: Syntax(stringLiteralExpr), shouldReset: false, breakKind: .block) } // Otherwise, don't stack--use regular continuation breaks instead. diff --git a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift index b8a1d4678..13644ea0d 100644 --- a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift @@ -38,7 +38,23 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 30) } - func testMultilineStringIsReindentedCorrectly() { + func testMultilineStringWithAssignmentOperatorInsteadOfPatternBinding() { + let input = + #""" + someString = """ + this string's total + length will be longer + than the column limit + even though none of + its individual lines + are. + """ + """# + + assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 30) + } + + func testMultilineStringUnlabeledArgumentIsReindentedCorrectly() { let input = #""" functionCall(longArgument, anotherLongArgument, """ @@ -62,6 +78,30 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 25) } + func testMultilineStringLabeledArgumentIsReindentedCorrectly() { + let input = + #""" + functionCall(longArgument: x, anotherLongArgument: y, longLabel: """ + some multi- + line string + """) + """# + + let expected = + #""" + functionCall( + longArgument: x, + anotherLongArgument: y, + longLabel: """ + some multi- + line string + """) + + """# + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 25) + } + func testMultilineStringInterpolations() { let input = #""" @@ -177,4 +217,114 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 25) } + + func testMultilineStringPreservesTrailingBackslashes() { + let input = + #""" + let x = """ + there should be \ + backslashes at \ + the end of \ + every line \ + except this one + """ + """# + + let expected = + #""" + let x = """ + there should be \ + backslashes at \ + the end of \ + every line \ + except this one + """ + + """# + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20) + } + + func testMultilineStringInParenthesizedExpression() { + let input = + #""" + let x = (""" + this is a + multiline string + """) + """# + + let expected = + #""" + let x = + (""" + this is a + multiline string + """) + + """# + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20) + } + + func testMultilineStringAfterStatementKeyword() { + let input = + #""" + return """ + this is a + multiline string + """ + return """ + this is a + multiline string + """ + "hello" + """# + + let expected = + #""" + return """ + this is a + multiline string + """ + return """ + this is a + multiline string + """ + "hello" + + """# + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20) + } + + func testMultilineStringsInExpression() { + // This output could probably be improved, but it's also a fairly unlikely occurrence. The + // important part of this test is that the first string in the expression is indented relative + // to the `let`. + let input = + #""" + let x = """ + this is a + multiline string + """ + """ + this is more + multiline string + """ + """# + + let expected = + #""" + let x = + """ + this is a + multiline string + """ + + """ + this is more + multiline string + """ + + """# + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20) + } } From 74279596e921b2a10963507b56c53976090c09a8 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Mon, 30 Jan 2023 14:24:10 +0000 Subject: [PATCH 015/332] Update for SwiftSyntax if/switch expression work --- Sources/SwiftFormat/Pipelines+Generated.swift | 4 +- .../TokenStreamCreator.swift | 9 +-- .../NoParensAroundConditions.swift | 10 +-- Sources/SwiftFormatRules/UseEarlyExits.swift | 11 +-- .../UseWhereClausesInForLoops.swift | 32 +++++---- .../IfStmtTests.swift | 68 ++++++++++++++++++ .../SwitchStmtTests.swift | 71 +++++++++++++++++++ .../NoParensAroundConditionsTests.swift | 25 +++++++ 8 files changed, 200 insertions(+), 30 deletions(-) diff --git a/Sources/SwiftFormat/Pipelines+Generated.swift b/Sources/SwiftFormat/Pipelines+Generated.swift index 6701a0a96..80c2e1ad1 100644 --- a/Sources/SwiftFormat/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Pipelines+Generated.swift @@ -161,7 +161,7 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } - override func visit(_ node: IfStmtSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: IfExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } @@ -271,7 +271,7 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } - override func visit(_ node: SwitchStmtSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: SwitchExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 6adf0e32a..28c4e9850 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -480,7 +480,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: IfStmtSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: IfExprSyntax) -> SyntaxVisitorContinueKind { // There may be a consistent breaking group around this node, see `CodeBlockItemSyntax`. This // group is necessary so that breaks around and inside of the conditions aren't forced to break // when the if-stmt spans multiple lines. @@ -515,7 +515,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // any newlines between `else` and the open brace or a following `if`. if let tokenAfterElse = elseKeyword.nextToken(viewMode: .all), tokenAfterElse.leadingTrivia.hasLineComment { after(node.elseKeyword, tokens: .break(.same, size: 1)) - } else if let elseBody = node.elseBody, elseBody.is(IfStmtSyntax.self) { + } else if let elseBody = node.elseBody, elseBody.is(IfExprSyntax.self) { after(node.elseKeyword, tokens: .space) } } @@ -680,7 +680,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: SwitchStmtSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: SwitchExprSyntax) -> SyntaxVisitorContinueKind { before(node.switchKeyword, tokens: .open) after(node.switchKeyword, tokens: .space) before(node.leftBrace, tokens: .break(.reset)) @@ -1480,7 +1480,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // This group applies to a top-level if-stmt so that all of the bodies will have the same // breaking behavior. - if let ifStmt = node.item.as(IfStmtSyntax.self) { + if let exprStmt = node.item.as(ExpressionStmtSyntax.self), + let ifStmt = exprStmt.expression.as(IfExprSyntax.self) { before(ifStmt.conditions.firstToken, tokens: .open(.consistent)) after(ifStmt.lastToken, tokens: .close) } diff --git a/Sources/SwiftFormatRules/NoParensAroundConditions.swift b/Sources/SwiftFormatRules/NoParensAroundConditions.swift index df8c60bb1..162715254 100644 --- a/Sources/SwiftFormatRules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormatRules/NoParensAroundConditions.swift @@ -52,7 +52,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { ) } - public override func visit(_ node: IfStmtSyntax) -> StmtSyntax { + public override func visit(_ node: IfExprSyntax) -> ExprSyntax { let conditions = visit(node.conditions) var result = node.with(\.ifKeyword, node.ifKeyword.withOneTrailingSpace()) .with(\.conditions, conditions) @@ -60,7 +60,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { if let elseBody = node.elseBody { result = result.with(\.elseBody, visit(elseBody)) } - return StmtSyntax(result) + return ExprSyntax(result) } public override func visit(_ node: ConditionElementSyntax) -> ConditionElementSyntax { @@ -72,14 +72,14 @@ public final class NoParensAroundConditions: SyntaxFormatRule { return node.with(\.condition, .expression(extractExpr(tup))) } - /// FIXME(hbh): Parsing for SwitchStmtSyntax is not implemented. - public override func visit(_ node: SwitchStmtSyntax) -> StmtSyntax { + /// FIXME(hbh): Parsing for SwitchExprSyntax is not implemented. + public override func visit(_ node: SwitchExprSyntax) -> ExprSyntax { guard let tup = node.expression.as(TupleExprSyntax.self), tup.elementList.firstAndOnly != nil else { return super.visit(node) } - return StmtSyntax( + return ExprSyntax( node.with(\.expression, extractExpr(tup)).with(\.cases, visit(node.cases))) } diff --git a/Sources/SwiftFormatRules/UseEarlyExits.swift b/Sources/SwiftFormatRules/UseEarlyExits.swift index ff68555ab..49b7f6cee 100644 --- a/Sources/SwiftFormatRules/UseEarlyExits.swift +++ b/Sources/SwiftFormatRules/UseEarlyExits.swift @@ -54,11 +54,12 @@ public final class UseEarlyExits: SyntaxFormatRule { let result = CodeBlockItemListSyntax( codeBlockItems.flatMap { (codeBlockItem: CodeBlockItemSyntax) -> [CodeBlockItemSyntax] in - // The `elseBody` of an `IfStmtSyntax` will be a `CodeBlockSyntax` if it's an `else` block, - // or another `IfStmtSyntax` if it's an `else if` block. We only want to handle the former. - guard let ifStatement = codeBlockItem.item.as(IfStmtSyntax.self), - let elseBody = ifStatement.elseBody?.as(CodeBlockSyntax.self), - codeBlockEndsWithEarlyExit(elseBody) + // The `elseBody` of an `IfExprSyntax` will be a `CodeBlockSyntax` if it's an `else` block, + // or another `IfExprSyntax` if it's an `else if` block. We only want to handle the former. + guard let exprStmt = codeBlockItem.item.as(ExpressionStmtSyntax.self), + let ifStatement = exprStmt.expression.as(IfExprSyntax.self), + let elseBody = ifStatement.elseBody?.as(CodeBlockSyntax.self), + codeBlockEndsWithEarlyExit(elseBody) else { return [codeBlockItem] } diff --git a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift index 67f364a25..415929be7 100644 --- a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift @@ -52,22 +52,26 @@ public final class UseWhereClausesInForLoops: SyntaxFormatRule { forInStmt: ForInStmtSyntax ) -> ForInStmtSyntax { switch Syntax(firstStmt).as(SyntaxEnum.self) { - case .ifStmt(let ifStmt) - where ifStmt.conditions.count == 1 - && ifStmt.elseKeyword == nil - && forInStmt.body.statements.count == 1: - // Extract the condition of the IfStmt. - let conditionElement = ifStmt.conditions.first! - guard let condition = conditionElement.condition.as(ExprSyntax.self) else { + case .expressionStmt(let exprStmt): + switch Syntax(exprStmt.expression).as(SyntaxEnum.self) { + case .ifExpr(let ifExpr) + where ifExpr.conditions.count == 1 + && ifExpr.elseKeyword == nil + && forInStmt.body.statements.count == 1: + // Extract the condition of the IfExpr. + let conditionElement = ifExpr.conditions.first! + guard let condition = conditionElement.condition.as(ExprSyntax.self) else { + return forInStmt + } + diagnose(.useWhereInsteadOfIf, on: ifExpr) + return updateWithWhereCondition( + node: forInStmt, + condition: condition, + statements: ifExpr.body.statements + ) + default: return forInStmt } - diagnose(.useWhereInsteadOfIf, on: ifStmt) - return updateWithWhereCondition( - node: forInStmt, - condition: condition, - statements: ifStmt.body.statements - ) - case .guardStmt(let guardStmt) where guardStmt.conditions.count == 1 && guardStmt.body.statements.count == 1 diff --git a/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift b/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift index 51fad551e..a67dfc754 100644 --- a/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift @@ -153,6 +153,74 @@ final class IfStmtTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: config) } + func testIfExpression1() { + let input = + """ + func foo() -> Int { + if var1 < var2 { + 23 + } + else if d < e { + 24 + } + else { + 0 + } + } + """ + + let expected = + """ + func foo() -> Int { + if var1 < var2 { + 23 + } else if d < e { + 24 + } else { + 0 + } + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 23) + } + + func testIfExpression2() { + let input = + """ + func foo() -> Int { + let x = if var1 < var2 { + 23 + } + else if d < e { + 24 + } + else { + 0 + } + return x + } + """ + + let expected = + """ + func foo() -> Int { + let x = if var1 < var2 { + 23 + } else if d < e { + 24 + } else { + 0 + } + return x + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 26) + } + func testMatchingPatternConditions() { let input = """ diff --git a/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift b/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift index 365e8ce36..35726d224 100644 --- a/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift @@ -205,6 +205,77 @@ final class SwitchStmtTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) } + func testSwitchExpression1() { + let input = + """ + func foo() -> Int { + switch value1 + value2 + value3 + value4 { + case "a": + 0 + case "b": + 1 + default: + 2 + } + } + """ + + let expected = + """ + func foo() -> Int { + switch value1 + value2 + value3 + + value4 + { + case "a": + 0 + case "b": + 1 + default: + 2 + } + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 35) + } + + + func testSwitchExpression2() { + let input = + """ + func foo() -> Int { + let x = switch value1 + value2 + value3 + value4 { + case "a": + 0 + case "b": + 1 + default: + 2 + } + return x + } + """ + + let expected = + """ + func foo() -> Int { + let x = switch value1 + value2 + value3 + value4 { + case "a": + 0 + case "b": + 1 + default: + 2 + } + return x + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 52) + } + func testUnknownDefault() { let input = """ diff --git a/Tests/SwiftFormatRulesTests/NoParensAroundConditionsTests.swift b/Tests/SwiftFormatRulesTests/NoParensAroundConditionsTests.swift index 8b0b53493..865082ed3 100644 --- a/Tests/SwiftFormatRulesTests/NoParensAroundConditionsTests.swift +++ b/Tests/SwiftFormatRulesTests/NoParensAroundConditionsTests.swift @@ -135,4 +135,29 @@ final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { if foo.someCall({ if x {} }) {} """) } + + func testParensAroundIfAndSwitchExprs() { + XCTAssertFormatting( + NoParensAroundConditions.self, + input: """ + let x = if (x) {} + let y = switch (4) { default: break } + func foo() { + return if (x) {} + } + func bar() { + return switch (4) { default: break } + } + """, + expected: """ + let x = if x {} + let y = switch 4 { default: break } + func foo() { + return if x {} + } + func bar() { + return switch 4 { default: break } + } + """) + } } From d7b390fafce18fa8e734c196a6a90fbde430f8a0 Mon Sep 17 00:00:00 2001 From: David Brunow <> Date: Mon, 6 Feb 2023 16:50:19 -0600 Subject: [PATCH 016/332] Fix formatting of postfix pound ifs --- .../TokenStreamCreator.swift | 13 +++- .../IfConfigTests.swift | 72 +++++++++---------- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 953d0661a..d7caf656f 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1308,11 +1308,22 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } if isNestedInPostfixIfConfig(node: Syntax(node)) { + let breakToken: Token + let previousToken = node.parent?.parent?.previousToken + + if previousToken?.parent?.parent?.parent?.parent?.syntaxNodeType == IfConfigClauseSyntax.self || + previousToken?.text == "}" { + breakToken = .break(.reset) + } else { + breakToken = .break + before(node.parent?.parent?.as(IfConfigDeclSyntax.self)?.poundEndif, tokens: [.break]) + } + before( node.firstToken, tokens: [ .printerControl(kind: .enableBreaking), - .break(.reset), + breakToken, ] ) } else if let condition = node.condition { diff --git a/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift b/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift index e1ad9e913..9a01fe631 100644 --- a/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift @@ -247,10 +247,10 @@ final class IfConfigTests: PrettyPrintTestCase { """ VStack { Text("something") - #if os(iOS) - .iOSSpecificModifier() - #endif - .commonModifier() + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() } """ @@ -277,13 +277,13 @@ final class IfConfigTests: PrettyPrintTestCase { """ VStack { Text("something") - #if os(iOS) - .iOSSpecificModifier() - .anotherModifier() - .anotherAnotherModifier() - #endif - .commonModifier() - .anotherCommonModifier() + #if os(iOS) + .iOSSpecificModifier() + .anotherModifier() + .anotherAnotherModifier() + #endif + .commonModifier() + .anotherCommonModifier() } """ @@ -311,14 +311,14 @@ final class IfConfigTests: PrettyPrintTestCase { """ VStack { Text("something") - #if os(iOS) || os(watchOS) - #if os(iOS) - .iOSModifier() - #else - .watchOSModifier() + #if os(iOS) || os(watchOS) + #if os(iOS) + .iOSModifier() + #else + .watchOSModifier() + #endif + .iOSAndWatchOSModifier() #endif - .iOSAndWatchOSModifier() - #endif } """ @@ -343,10 +343,10 @@ final class IfConfigTests: PrettyPrintTestCase { """ VStack { textView - #if os(iOS) - .iOSSpecificModifier() - #endif - .commonModifier() + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() } """ @@ -406,9 +406,9 @@ final class IfConfigTests: PrettyPrintTestCase { """ EmptyView() .padding([.vertical]) - #if os(iOS) - .iOSSpecificModifier() - #endif + #if os(iOS) + .iOSSpecificModifier() + #endif .commonModifier() """ @@ -438,17 +438,17 @@ final class IfConfigTests: PrettyPrintTestCase { """ EmptyView() .padding([.vertical]) - #if os(iOS) - .iOSSpecificModifier( - SpecificType() - .onChanged { _ in - // do things - } - .onEnded { _ in - // do things - } - ) - #endif + #if os(iOS) + .iOSSpecificModifier( + SpecificType() + .onChanged { _ in + // do things + } + .onEnded { _ in + // do things + } + ) + #endif """ From 4fd796fa98c598756e6ffe3ba3e6d4b51a2204f0 Mon Sep 17 00:00:00 2001 From: David Brunow <> Date: Mon, 6 Feb 2023 17:22:02 -0600 Subject: [PATCH 017/332] Improve implementation --- .../TokenStreamCreator.swift | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index d7caf656f..e62186d26 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1309,14 +1309,15 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if isNestedInPostfixIfConfig(node: Syntax(node)) { let breakToken: Token - let previousToken = node.parent?.parent?.previousToken + let currentIfConfigDecl = node.parent?.parent?.as(IfConfigDeclSyntax.self) + let tokenBeforeCurrentIfConfigDecl = currentIfConfigDecl?.previousToken - if previousToken?.parent?.parent?.parent?.parent?.syntaxNodeType == IfConfigClauseSyntax.self || - previousToken?.text == "}" { + if isNestedInIfConfig(Syntax(tokenBeforeCurrentIfConfigDecl)) || + tokenBeforeCurrentIfConfigDecl?.text == "}" { breakToken = .break(.reset) } else { breakToken = .break - before(node.parent?.parent?.as(IfConfigDeclSyntax.self)?.poundEndif, tokens: [.break]) + before(currentIfConfigDecl?.poundEndif, tokens: [.break]) } before( @@ -3525,6 +3526,9 @@ private func isNestedInPostfixIfConfig(node: Syntax) -> Bool { var this: Syntax? = node while this?.parent != nil { + // This guard handles the situation where a type with its own modifiers + // is nested inside of an if config. That type should not count as being + // in a postfix if config because its entire body is inside the if config. if this?.is(TupleExprElementSyntax.self) == true { return false } @@ -3540,6 +3544,20 @@ private func isNestedInPostfixIfConfig(node: Syntax) -> Bool { return false } +private func isNestedInIfConfig(node: Syntax) -> Bool { + var this: Syntax? = node + + while this?.parent != nil { + if this?.is(IfConfigClauseSyntax.self) == true { + return true + } + + this = this?.parent + } + + return false +} + extension Syntax { /// Creates a pretty-printable token stream for the provided Syntax node. func makeTokenStream(configuration: Configuration, operatorContext: OperatorContext) -> [Token] { From dea0d251800ae632b02dd3b75f3c4625c0e82a29 Mon Sep 17 00:00:00 2001 From: David Brunow <> Date: Tue, 7 Feb 2023 10:33:49 -0600 Subject: [PATCH 018/332] Fix build issues after bringing in the latest from main --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 43e156699..394099f73 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1333,9 +1333,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if isNestedInPostfixIfConfig(node: Syntax(node)) { let breakToken: Token let currentIfConfigDecl = node.parent?.parent?.as(IfConfigDeclSyntax.self) - let tokenBeforeCurrentIfConfigDecl = currentIfConfigDecl?.previousToken - if isNestedInIfConfig(Syntax(tokenBeforeCurrentIfConfigDecl)) || + if let currentIfConfigDecl = currentIfConfigDecl, + let tokenBeforeCurrentIfConfigDecl = currentIfConfigDecl?.previousToken, + isNestedInIfConfig(node: Syntax(tokenBeforeCurrentIfConfigDecl)) || tokenBeforeCurrentIfConfigDecl?.text == "}" { breakToken = .break(.reset) } else { From 79b9d3aa7afdefcb21c2e735a81f0b59713a9955 Mon Sep 17 00:00:00 2001 From: David Brunow <> Date: Tue, 7 Feb 2023 10:40:18 -0600 Subject: [PATCH 019/332] Fix more build errors --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 394099f73..aee1d892e 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1335,9 +1335,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { let currentIfConfigDecl = node.parent?.parent?.as(IfConfigDeclSyntax.self) if let currentIfConfigDecl = currentIfConfigDecl, - let tokenBeforeCurrentIfConfigDecl = currentIfConfigDecl?.previousToken, + let tokenBeforeCurrentIfConfigDecl = currentIfConfigDecl.previousToken, isNestedInIfConfig(node: Syntax(tokenBeforeCurrentIfConfigDecl)) || - tokenBeforeCurrentIfConfigDecl?.text == "}" { + tokenBeforeCurrentIfConfigDecl.text == "}" { breakToken = .break(.reset) } else { breakToken = .break From 4ae7690ef08e177eede2f8a6570ac5074da4feab Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Wed, 22 Feb 2023 16:14:05 -0800 Subject: [PATCH 020/332] [reference-bindings] Change letOrVarKeyword -> bindingKeyword. --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 8 ++++---- Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift | 4 ++-- Sources/SwiftFormatRules/UseShorthandTypeNames.swift | 2 +- Sources/SwiftFormatRules/UseSynthesizedInitializer.swift | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 28c4e9850..5fc50c76f 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1909,14 +1909,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if node.bindings.count == 1 { // If there is only a single binding, don't allow a break between the `let/var` keyword and // the identifier; there are better places to break later on. - after(node.letOrVarKeyword, tokens: .space) + after(node.bindingKeyword, tokens: .space) } else { // If there is more than one binding, we permit an open-break after `let/var` so that each of // the comma-delimited items will potentially receive indentation. We also add a group around // the individual bindings to bind them together better. (This is done here, not in // `visit(_: PatternBindingSyntax)`, because we only want that behavior when there are // multiple bindings.) - after(node.letOrVarKeyword, tokens: .break(.open)) + after(node.bindingKeyword, tokens: .break(.open)) for binding in node.bindings { before(binding.firstToken, tokens: .open) @@ -2106,7 +2106,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ValueBindingPatternSyntax) -> SyntaxVisitorContinueKind { - after(node.letOrVarKeyword, tokens: .break) + after(node.bindingKeyword, tokens: .break) return .visitChildren } @@ -2290,7 +2290,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: OptionalBindingConditionSyntax) -> SyntaxVisitorContinueKind { - after(node.letOrVarKeyword, tokens: .break) + after(node.bindingKeyword, tokens: .break) if let typeAnnotation = node.typeAnnotation { after( diff --git a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift index 3bfbdbf17..b65f38bdc 100644 --- a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift @@ -173,9 +173,9 @@ fileprivate func identifierDescription(for node: NodeT case .enumCaseElement: return "enum case" case .functionDecl: return "function" case .optionalBindingCondition(let binding): - return binding.letOrVarKeyword.tokenKind == .keyword(.var) ? "variable" : "constant" + return binding.bindingKeyword.tokenKind == .keyword(.var) ? "variable" : "constant" case .variableDecl(let variableDecl): - return variableDecl.letOrVarKeyword.tokenKind == .keyword(.var) ? "variable" : "constant" + return variableDecl.bindingKeyword.tokenKind == .keyword(.var) ? "variable" : "constant" default: return "identifier" } diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 30d10e949..0f38e4be6 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -542,7 +542,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { isStoredProperty(patternBinding), patternBinding.initializer == nil, let variableDecl = nearestAncestor(of: patternBinding, type: VariableDeclSyntax.self), - variableDecl.letOrVarKeyword.tokenKind == .keyword(.var) + variableDecl.bindingKeyword.tokenKind == .keyword(.var) { return true } diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift index 9915258ab..4233aafd6 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift @@ -116,7 +116,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { // Ensure that parameters that correspond to properties declared using 'var' have a default // argument that is identical to the property's default value. Otherwise, a default argument // doesn't match the memberwise initializer. - let isVarDecl = property.letOrVarKeyword.tokenKind == .keyword(.var) + let isVarDecl = property.bindingKeyword.tokenKind == .keyword(.var) if isVarDecl, let initializer = property.firstInitializer { guard let defaultArg = parameter.defaultArgument else { return false } guard initializer.value.description == defaultArg.value.description else { return false } From 89a0b6f5f5be53baddb81530d35502c679086994 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 16 Mar 2023 08:28:45 +0000 Subject: [PATCH 021/332] Update ArgumentParser dependency in `Package.swift` (#492) Synchronized with https://github.com/apple/swift-syntax/pull/1408. 1.2.2 is the version of ArgumentParser used by the toolchain, according to the checkout config: https://github.com/apple/swift/blob/277674a642b058fe95c37e24d5c861c5b81b6a82/utils/update_checkout/update-checkout-config.json#L103 Sticking to the old versions like 1.0.x and 1.1.x has negative downstream effects, since tools using SwiftSyntax (like `swift-format`) are restricted to these versions too, which can easily conflict with other packages. A more general solution could be to rely on SemVer and use `.upToNextMajor` instead of `.upToNextMinor`. --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 9762b661c..99e0630ef 100644 --- a/Package.swift +++ b/Package.swift @@ -217,7 +217,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { url: "https://github.com/apple/swift-argument-parser.git", // This should be kept in sync with the same dependency used by // swift-syntax. - Version("1.0.1").. Date: Tue, 28 Mar 2023 15:26:34 -0700 Subject: [PATCH 022/332] Adjustments to split `FunctionParameterSyntax` into multiple nodes for function parameters, closure parameters and enum parameters Companion of https://github.com/apple/swift-syntax/pull/1455 --- Sources/SwiftFormat/Pipelines+Generated.swift | 10 ++ .../TokenStreamCreator.swift | 97 ++++++++++++++++++- .../AlwaysUseLowerCamelCase.swift | 26 ++++- .../AmbiguousTrailingClosureOverload.swift | 2 +- .../FunctionDeclSyntax+Convenience.swift | 2 +- .../NoLeadingUnderscores.swift | 18 +++- .../UseSynthesizedInitializer.swift | 7 +- .../ValidateDocumentationComments.swift | 4 +- Sources/generate-pipeline/RuleCollector.swift | 2 +- 9 files changed, 149 insertions(+), 19 deletions(-) diff --git a/Sources/SwiftFormat/Pipelines+Generated.swift b/Sources/SwiftFormat/Pipelines+Generated.swift index 80c2e1ad1..bc748aa1a 100644 --- a/Sources/SwiftFormat/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Pipelines+Generated.swift @@ -55,6 +55,11 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } + override func visit(_ node: ClosureParameterSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + return .visitChildren + } + override func visit(_ node: ClosureSignatureSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) visitIfEnabled(ReturnVoidInsteadOfEmptyTuple.visit, for: node) @@ -92,6 +97,11 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } + override func visit(_ node: EnumCaseParameterSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + return .visitChildren + } + override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 5fc50c76f..fe5eb8f0b 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1122,7 +1122,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // When it's parenthesized, the input is a `ParameterClauseSyntax`. Otherwise, it's a // `ClosureParamListSyntax`. The parenthesized version is wrapped in open/close breaks so that // the parens create an extra level of indentation. - if let parameterClause = input.as(ParameterClauseSyntax.self) { + if let parameterClause = input.as(ClosureParameterClauseSyntax.self) { // Whether we should prioritize keeping ") throws -> " together. We can only do // this if the closure has arguments. let keepOutputTogether = @@ -1141,7 +1141,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(input.lastToken, tokens: .close) } - arrangeParameterClause(parameterClause, forcesBreakBeforeRightParen: true) + arrangeClosureParameterClause(parameterClause, forcesBreakBeforeRightParen: true) } else { // Group around the arguments, but don't use open/close breaks because there are no parens // to create a new scope. @@ -1245,6 +1245,30 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: ClosureParameterClauseSyntax) -> SyntaxVisitorContinueKind { + // Prioritize keeping ") throws -> " together. We can only do this if the function + // has arguments. + if !node.parameterList.isEmpty && config.prioritizeKeepingFunctionOutputTogether { + // Due to visitation order, this .open corresponds to a .close added in FunctionDeclSyntax + // or SubscriptDeclSyntax. + before(node.rightParen, tokens: .open) + } + + return .visitChildren + } + + override func visit(_ node: EnumCaseParameterClauseSyntax) -> SyntaxVisitorContinueKind { + // Prioritize keeping ") throws -> " together. We can only do this if the function + // has arguments. + if !node.parameterList.isEmpty && config.prioritizeKeepingFunctionOutputTogether { + // Due to visitation order, this .open corresponds to a .close added in FunctionDeclSyntax + // or SubscriptDeclSyntax. + before(node.rightParen, tokens: .open) + } + + return .visitChildren + } + override func visit(_ node: ParameterClauseSyntax) -> SyntaxVisitorContinueKind { // Prioritize keeping ") throws -> " together. We can only do this if the function // has arguments. @@ -1257,6 +1281,37 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: ClosureParameterSyntax) -> SyntaxVisitorContinueKind { + before(node.firstToken, tokens: .open) + arrangeAttributeList(node.attributes) + before( + node.secondName, + tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + after(node.colon, tokens: .break) + + if let trailingComma = node.trailingComma { + after(trailingComma, tokens: .close, .break(.same)) + } else { + after(node.lastToken, tokens: .close) + } + return .visitChildren + } + + override func visit(_ node: EnumCaseParameterSyntax) -> SyntaxVisitorContinueKind { + before(node.firstToken, tokens: .open) + before( + node.secondName, + tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + after(node.colon, tokens: .break) + + if let trailingComma = node.trailingComma { + after(trailingComma, tokens: .close, .break(.same)) + } else { + after(node.lastToken, tokens: .close) + } + return .visitChildren + } + override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken, tokens: .open) arrangeAttributeList(node.attributes) @@ -1413,7 +1468,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.trailingComma, tokens: .break) if let associatedValue = node.associatedValue { - arrangeParameterClause(associatedValue, forcesBreakBeforeRightParen: false) + arrangeEnumCaseParameterClause(associatedValue, forcesBreakBeforeRightParen: false) } return .visitChildren @@ -2588,6 +2643,42 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return contentsIterator.next() == nil && !commentPrecedesRightBrace } + /// Applies formatting to a collection of parameters for a decl. + /// + /// - Parameters: + /// - parameters: A node that contains the parameters that can be passed to a decl when its + /// called. + /// - forcesBreakBeforeRightParen: Whether a break should be required before the right paren + /// when the right paren is on a different line than the corresponding left paren. + private func arrangeClosureParameterClause( + _ parameters: ClosureParameterClauseSyntax, forcesBreakBeforeRightParen: Bool + ) { + guard !parameters.parameterList.isEmpty else { return } + + after(parameters.leftParen, tokens: .break(.open, size: 0), .open(argumentListConsistency())) + before( + parameters.rightParen, + tokens: .break(.close(mustBreak: forcesBreakBeforeRightParen), size: 0), .close) + } + + /// Applies formatting to a collection of enum case parameters for a decl. + /// + /// - Parameters: + /// - parameters: A node that contains the parameters that can be passed to a decl when its + /// called. + /// - forcesBreakBeforeRightParen: Whether a break should be required before the right paren + /// when the right paren is on a different line than the corresponding left paren. + private func arrangeEnumCaseParameterClause( + _ parameters: EnumCaseParameterClauseSyntax, forcesBreakBeforeRightParen: Bool + ) { + guard !parameters.parameterList.isEmpty else { return } + + after(parameters.leftParen, tokens: .break(.open, size: 0), .open(argumentListConsistency())) + before( + parameters.rightParen, + tokens: .break(.close(mustBreak: forcesBreakBeforeRightParen), size: 0), .close) + } + /// Applies formatting to a collection of parameters for a decl. /// /// - Parameters: diff --git a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift index b65f38bdc..8be86313b 100644 --- a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift @@ -73,7 +73,16 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { diagnoseLowerCamelCaseViolations( param.name, allowUnderscores: false, description: identifierDescription(for: node)) } - } else if let parameterClause = input.as(ParameterClauseSyntax.self) { + } else if let parameterClause = input.as(ClosureParameterClauseSyntax.self) { + for param in parameterClause.parameterList { + diagnoseLowerCamelCaseViolations( + param.firstName, allowUnderscores: false, description: identifierDescription(for: node)) + if let secondName = param.secondName { + diagnoseLowerCamelCaseViolations( + secondName, allowUnderscores: false, description: identifierDescription(for: node)) + } + } + } else if let parameterClause = input.as(EnumCaseParameterClauseSyntax.self) { for param in parameterClause.parameterList { if let firstName = param.firstName { diagnoseLowerCamelCaseViolations( @@ -84,6 +93,15 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { secondName, allowUnderscores: false, description: identifierDescription(for: node)) } } + } else if let parameterClause = input.as(ParameterClauseSyntax.self) { + for param in parameterClause.parameterList { + diagnoseLowerCamelCaseViolations( + param.firstName, allowUnderscores: false, description: identifierDescription(for: node)) + if let secondName = param.secondName { + diagnoseLowerCamelCaseViolations( + secondName, allowUnderscores: false, description: identifierDescription(for: node)) + } + } } } return .visitChildren @@ -106,10 +124,8 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { for param in node.signature.input.parameterList { // These identifiers aren't described using `identifierDescription(for:)` because no single // node can disambiguate the argument label from the parameter name. - if let label = param.firstName { - diagnoseLowerCamelCaseViolations( - label, allowUnderscores: false, description: "argument label") - } + diagnoseLowerCamelCaseViolations( + param.firstName, allowUnderscores: false, description: "argument label") if let paramName = param.secondName { diagnoseLowerCamelCaseViolations( paramName, allowUnderscores: false, description: "function parameter") diff --git a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift index d20310197..89741008f 100644 --- a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift +++ b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift @@ -41,7 +41,7 @@ public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { for fn in functions { let params = fn.signature.input.parameterList guard let firstParam = params.firstAndOnly else { continue } - guard let type = firstParam.type, type.is(FunctionTypeSyntax.self) else { continue } + guard firstParam.type.is(FunctionTypeSyntax.self) else { continue } if let mods = fn.modifiers, mods.has(modifier: "static") || mods.has(modifier: "class") { staticOverloads[fn.identifier.text, default: []].append(fn) } else { diff --git a/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift b/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift index 2ee98b4e9..75e5e2a67 100644 --- a/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift +++ b/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift @@ -16,7 +16,7 @@ extension FunctionDeclSyntax { /// Constructs a name for a function that includes parameter labels, i.e. `foo(_:bar:)`. var fullDeclName: String { let params = signature.input.parameterList.map { param in - "\(param.firstName?.text ?? "_"):" + "\(param.firstName.text):" } return "\(identifier.text)(\(params.joined()))" } diff --git a/Sources/SwiftFormatRules/NoLeadingUnderscores.swift b/Sources/SwiftFormatRules/NoLeadingUnderscores.swift index 051818b2b..e3f44900a 100644 --- a/Sources/SwiftFormatRules/NoLeadingUnderscores.swift +++ b/Sources/SwiftFormatRules/NoLeadingUnderscores.swift @@ -56,7 +56,15 @@ public final class NoLeadingUnderscores: SyntaxLintRule { return .visitChildren } - public override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: ClosureParameterSyntax) -> SyntaxVisitorContinueKind { + // If both names are provided, we want to check `secondName`, which will be the parameter name + // (in that case, `firstName` is the label). If only one name is present, then it is recorded in + // `firstName`, and it is both the label and the parameter name. + diagnoseIfNameStartsWithUnderscore(node.secondName ?? node.firstName) + return .visitChildren + } + + public override func visit(_ node: EnumCaseParameterSyntax) -> SyntaxVisitorContinueKind { // If both names are provided, we want to check `secondName`, which will be the parameter name // (in that case, `firstName` is the label). If only one name is present, then it is recorded in // `firstName`, and it is both the label and the parameter name. @@ -66,6 +74,14 @@ public final class NoLeadingUnderscores: SyntaxLintRule { return .visitChildren } + public override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind { + // If both names are provided, we want to check `secondName`, which will be the parameter name + // (in that case, `firstName` is the label). If only one name is present, then it is recorded in + // `firstName`, and it is both the label and the parameter name. + diagnoseIfNameStartsWithUnderscore(node.secondName ?? node.firstName) + return .visitChildren + } + public override func visit(_ node: GenericParameterSyntax) -> SyntaxVisitorContinueKind { diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift index 4233aafd6..88b7e7eb1 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift @@ -106,8 +106,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { guard parameters.count == properties.count else { return false } for (idx, parameter) in parameters.enumerated() { - guard let paramId = parameter.firstName, parameter.secondName == nil else { return false } - guard let paramType = parameter.type else { return false } + guard parameter.secondName == nil else { return false } let property = properties[idx] let propertyId = property.firstIdentifier @@ -124,9 +123,9 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { return false } - if propertyId.identifier.text != paramId.text + if propertyId.identifier.text != parameter.firstName.text || propertyType.description.trimmingCharacters( - in: .whitespaces) != paramType.description.trimmingCharacters(in: .whitespacesAndNewlines) + in: .whitespaces) != parameter.type.description.trimmingCharacters(in: .whitespacesAndNewlines) { return false } } return true diff --git a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift index a145879be..cbf1bab18 100644 --- a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift @@ -139,9 +139,7 @@ fileprivate func funcParametersIdentifiers(in paramList: FunctionParameterListSy // If there is a label and an identifier, then the identifier (`secondName`) is the name that // should be documented. Otherwise, the label and identifier are the same, occupying // `firstName`. - guard let parameterIdentifier = parameter.secondName ?? parameter.firstName else { - continue - } + let parameterIdentifier = parameter.secondName ?? parameter.firstName funcParameters.append(parameterIdentifier.text) } return funcParameters diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index 7c23a93d3..dc5830bcb 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -126,7 +126,7 @@ final class RuleCollector { guard let function = member.decl.as(FunctionDeclSyntax.self) else { continue } guard function.identifier.text == "visit" else { continue } let params = function.signature.input.parameterList - guard let firstType = params.firstAndOnly?.type?.as(SimpleTypeIdentifierSyntax.self) else { + guard let firstType = params.firstAndOnly?.type.as(SimpleTypeIdentifierSyntax.self) else { continue } visitedNodes.append(firstType.name.text) From 5907222da12cdb2605e823f3be63c8c491431b95 Mon Sep 17 00:00:00 2001 From: stackotter Date: Sun, 2 Apr 2023 21:58:26 +1000 Subject: [PATCH 023/332] Fix pretty printing of incorrectly formatted closures with a signature and multiple statements --- .../TokenStreamCreator.swift | 2 +- .../ClosureExprTests.swift | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 5fc50c76f..46ab83f44 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1073,7 +1073,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind { let newlineBehavior: NewlineBehavior - if forcedBreakingClosures.remove(node.id) != nil { + if forcedBreakingClosures.remove(node.id) != nil || node.statements.count > 1 { newlineBehavior = .soft } else { newlineBehavior = .elective diff --git a/Tests/SwiftFormatPrettyPrintTests/ClosureExprTests.swift b/Tests/SwiftFormatPrettyPrintTests/ClosureExprTests.swift index 5518c5a96..cab0b8553 100644 --- a/Tests/SwiftFormatPrettyPrintTests/ClosureExprTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/ClosureExprTests.swift @@ -516,4 +516,24 @@ final class ClosureExprTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 40) } + + func testClosureWithSignatureAndMultipleStatements() { + let input = + """ + { a in a + 1 + a + 2 + } + """ + + let expected = + """ + { a in + a + 1 + a + 2 + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40) + } } From 2910f57ff5df9249001486c1ec6f1ff15652e22a Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Sat, 1 Apr 2023 11:33:08 -0700 Subject: [PATCH 024/332] Update to non-optional leading/trailingTrivia swift-syntax API --- .../SyntaxProtocol+Convenience.swift | 1 - .../TokenStreamCreator.swift | 4 +-- .../NoAssignmentInExpressions.swift | 4 +-- .../NoCasesWithOnlyFallthrough.swift | 14 ++------ Sources/SwiftFormatRules/OrderedImports.swift | 34 ++++++++----------- .../UseShorthandTypeNames.swift | 2 +- ...eTripleSlashForDocumentationComments.swift | 4 +-- 7 files changed, 25 insertions(+), 38 deletions(-) diff --git a/Sources/SwiftFormatCore/SyntaxProtocol+Convenience.swift b/Sources/SwiftFormatCore/SyntaxProtocol+Convenience.swift index 01673ffcb..415c8d7f6 100644 --- a/Sources/SwiftFormatCore/SyntaxProtocol+Convenience.swift +++ b/Sources/SwiftFormatCore/SyntaxProtocol+Convenience.swift @@ -25,7 +25,6 @@ extension SyntaxProtocol { /// be returned. /// - Returns: The absolute position of the trivia piece. public func position(ofLeadingTriviaAt index: Trivia.Index) -> AbsolutePosition { - let leadingTrivia = self.leadingTrivia ?? [] guard leadingTrivia.indices.contains(index) else { preconditionFailure("Index was out of bounds in the node's leading trivia.") } diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 5fc50c76f..6532c35e8 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -876,7 +876,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // The node's content is either a `DictionaryElementListSyntax` or a `TokenSyntax` for a colon // token (for an empty dictionary). if !(node.content.as(DictionaryElementListSyntax.self)?.isEmpty ?? true) - || node.content.leadingTrivia?.numberOfComments ?? 0 > 0 + || node.content.leadingTrivia.numberOfComments > 0 || node.rightSquare.leadingTrivia.numberOfComments > 0 { after(node.leftSquare, tokens: .break(.open, size: 0), .open) @@ -2161,7 +2161,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { appendToken(.syntax(segmentText)) } - if node.trailingTrivia?.containsBackslashes == true { + if node.trailingTrivia.containsBackslashes { // Segments with trailing backslashes won't end with a literal newline; the backslash is // considered trivia. To preserve the original text and wrapping, we need to manually render // the backslash and a break into the token stream. diff --git a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift index 8eda56e97..e327b59d7 100644 --- a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift @@ -60,7 +60,7 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { semicolon: nil ) .with(\.leadingTrivia, - (returnStmt.leadingTrivia ?? []) + (assignmentExpr.leadingTrivia ?? [])) + (returnStmt.leadingTrivia) + (assignmentExpr.leadingTrivia)) .with(\.trailingTrivia, [])) newItems.append( CodeBlockItemSyntax( @@ -68,7 +68,7 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { semicolon: nil ) .with(\.leadingTrivia, [.newlines(1)]) - .with(\.trailingTrivia, returnStmt.trailingTrivia?.withoutLeadingSpaces() ?? [])) + .with(\.trailingTrivia, returnStmt.trailingTrivia.withoutLeadingSpaces())) default: newItems.append(newItem) diff --git a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift b/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift index b5aa6dc8e..cb5f6dc01 100644 --- a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift +++ b/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift @@ -139,13 +139,11 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { } // Check for any comments that are adjacent to the case or fallthrough statement. - if let leadingTrivia = switchCase.leadingTrivia, - leadingTrivia.drop(while: { !$0.isNewline }).contains(where: { $0.isComment }) + if switchCase.leadingTrivia.drop(while: { !$0.isNewline }).contains(where: { $0.isComment }) { return false } - if let leadingTrivia = onlyStatement.leadingTrivia, - leadingTrivia.drop(while: { !$0.isNewline }).contains(where: { $0.isComment }) + if onlyStatement.leadingTrivia.drop(while: { !$0.isNewline }).contains(where: { $0.isComment }) { return false } @@ -194,13 +192,7 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { // Only the first violation case can have displaced trivia, because any non-whitespace // trivia in the other violation cases would've prevented collapsing. - if let displacedLeadingTrivia = cases.first!.leadingTrivia?.withoutLastLine() { - let existingLeadingTrivia = newCase.leadingTrivia ?? [] - let mergedLeadingTrivia = displacedLeadingTrivia + existingLeadingTrivia - return newCase.with(\.leadingTrivia, mergedLeadingTrivia) - } else { - return newCase - } + return newCase.with(\.leadingTrivia, cases.first!.leadingTrivia.withoutLastLine() + newCase.leadingTrivia) } } diff --git a/Sources/SwiftFormatRules/OrderedImports.swift b/Sources/SwiftFormatRules/OrderedImports.swift index 6b505a455..187b79673 100644 --- a/Sources/SwiftFormatRules/OrderedImports.swift +++ b/Sources/SwiftFormatRules/OrderedImports.swift @@ -298,23 +298,20 @@ fileprivate func generateLines(codeBlockItemList: CodeBlockItemListSyntax, conte } for block in codeBlockItemList { - - if let leadingTrivia = block.leadingTrivia { - afterNewline = false - - for piece in leadingTrivia { - switch piece { - // Create new Line objects when we encounter newlines. - case .newlines(let N): - for _ in 0.. DeclSyntax { guard let commentText = decl.docComment else { return decl } - guard let declLeadingTrivia = decl.leadingTrivia else { return decl } + let docComments = commentText.components(separatedBy: "\n") var pieces = [TriviaPiece]() // Ensures the documentation comment is a docLineComment. var hasFoundDocComment = false - for piece in declLeadingTrivia.reversed() { + for piece in decl.leadingTrivia.reversed() { if case .docBlockComment(_) = piece, !hasFoundDocComment { hasFoundDocComment = true diagnose(.avoidDocBlockComment, on: decl) From a0801d92bd4543bea0ef2dbb53a7146d82b0508b Mon Sep 17 00:00:00 2001 From: stackotter Date: Mon, 3 Apr 2023 23:10:27 +1000 Subject: [PATCH 025/332] Fix fatal error caused by switch cases without any statements (#473) --- .../TokenStreamCreator.swift | 16 ++++++-- .../SwitchStmtTests.swift | 37 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 5fc50c76f..a48db5759 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -718,13 +718,23 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.unknownAttr?.lastToken, tokens: .space) after(node.label.lastToken, tokens: .break(.reset, size: 0), .break(.open), .open) - // If switch/case labels were configured to be indented, insert an extra `close` break after the - // case body to match the `open` break above + // If switch/case labels were configured to be indented, insert an extra `close` break after + // the case body to match the `open` break above var afterLastTokenTokens: [Token] = [.break(.close, size: 0), .close] if config.indentSwitchCaseLabels { afterLastTokenTokens.append(.break(.close, size: 0)) } - after(node.lastToken, tokens: afterLastTokenTokens) + + // If the case contains statements, add the closing tokens after the last token of the case. + // Otherwise, add the closing tokens before the next case (or the end of the switch) to have the + // same effect. If instead the opening and closing tokens were omitted completely in the absence + // of statements, comments within the empty case would be incorrectly indented to the same level + // as the case label. + if node.label.lastToken != node.lastToken { + after(node.lastToken, tokens: afterLastTokenTokens) + } else { + before(node.nextToken, tokens: afterLastTokenTokens) + } return .visitChildren } diff --git a/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift b/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift index 35726d224..08e07d94e 100644 --- a/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift @@ -78,6 +78,43 @@ final class SwitchStmtTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 35) } + func testSwitchEmptyCases() { + let input = + """ + switch a { + case b: + default: + print("Not b") + } + + switch a { + case b: + // Comment but no statements + default: + print("Not b") + } + """ + + let expected = + """ + switch a { + case b: + default: + print("Not b") + } + + switch a { + case b: + // Comment but no statements + default: + print("Not b") + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 35) + } + func testSwitchCompoundCases() { let input = """ From 1ca68450175a1106a0bd0d8f827b9bd2dd7e4367 Mon Sep 17 00:00:00 2001 From: stackotter Date: Tue, 4 Apr 2023 09:22:51 +1000 Subject: [PATCH 026/332] Fix formatting of import statements with attributes, and ensure that they **never** wrap (fixes #445) --- .../SwiftFormatPrettyPrint/PrettyPrint.swift | 21 ++++++++++++++----- Sources/SwiftFormatPrettyPrint/Token.swift | 6 ++++-- .../TokenStreamCreator.swift | 9 ++++++-- .../ImportTests.swift | 8 +++++++ 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/PrettyPrint.swift b/Sources/SwiftFormatPrettyPrint/PrettyPrint.swift index a85173487..f35acc61b 100644 --- a/Sources/SwiftFormatPrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormatPrettyPrint/PrettyPrint.swift @@ -129,11 +129,16 @@ public class PrettyPrinter { private var activeBreakSuppressionCount = 0 /// Whether breaks are supressed from firing. When true, no breaks should fire and the only way to - /// move to a new line is an explicit new line token. - private var isBreakingSupressed: Bool { + /// move to a new line is an explicit new line token. Discretionary breaks aren't suppressed + /// if ``allowSuppressedDiscretionaryBreaks`` is true. + private var isBreakingSuppressed: Bool { return activeBreakSuppressionCount > 0 } + /// Indicates whether discretionary breaks should still be included even if break suppression is + /// enabled (see ``isBreakingSuppressed``). + private var allowSuppressedDiscretionaryBreaks = false + /// The computed indentation level, as a number of spaces, based on the state of any unclosed /// delimiters and whether or not the current line is a continuation line. private var currentIndentation: [Indent] { @@ -469,7 +474,7 @@ public class PrettyPrinter { case .soft(_, let discretionary): // A discretionary newline (i.e. from the source) should create a line break even if the // rules for breaking are disabled. - overrideBreakingSuppressed = discretionary + overrideBreakingSuppressed = discretionary && allowSuppressedDiscretionaryBreaks mustBreak = true case .hard: // A hard newline must always create a line break, regardless of the context. @@ -477,7 +482,7 @@ public class PrettyPrinter { mustBreak = true } - let suppressBreaking = isBreakingSupressed && !overrideBreakingSuppressed + let suppressBreaking = isBreakingSuppressed && !overrideBreakingSuppressed if !suppressBreaking && ((!isAtStartOfLine && length > spaceRemaining) || mustBreak) { currentLineIsContinuation = isContinuationIfBreakFires writeNewlines(newline) @@ -527,8 +532,14 @@ public class PrettyPrinter { case .printerControl(let kind): switch kind { - case .disableBreaking: + case .disableBreaking(let allowDiscretionary): activeBreakSuppressionCount += 1 + // Override the supression of discretionary breaks if we're at the top level or + // discretionary breaks are currently allowed (false should override true, but not the other + // way around). + if activeBreakSuppressionCount == 1 || allowSuppressedDiscretionaryBreaks { + allowSuppressedDiscretionaryBreaks = allowDiscretionary + } case .enableBreaking: activeBreakSuppressionCount -= 1 } diff --git a/Sources/SwiftFormatPrettyPrint/Token.swift b/Sources/SwiftFormatPrettyPrint/Token.swift index 431edb288..fdfcb297f 100644 --- a/Sources/SwiftFormatPrettyPrint/Token.swift +++ b/Sources/SwiftFormatPrettyPrint/Token.swift @@ -162,8 +162,10 @@ enum PrinterControlKind { /// control token is encountered. /// /// It's valid to nest `disableBreaking` and `enableBreaking` tokens. Breaks will be suppressed - /// long as there is at least 1 unmatched disable token. - case disableBreaking + /// long as there is at least 1 unmatched disable token. If `allowDiscretionary` is `true`, then + /// discretionary breaks aren't effected. An `allowDiscretionary` value of true never overrides a + /// value of false. Hard breaks are always inserted no matter what. + case disableBreaking(allowDiscretionary: Bool) /// A signal that break tokens should be allowed to fire following this token, as long as there /// are no other unmatched disable tokens. diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 5fc50c76f..10085ba9d 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1339,7 +1339,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ] ) } else if let condition = node.condition { - before(condition.firstToken, tokens: .printerControl(kind: .disableBreaking)) + before(condition.firstToken, tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: true))) after( condition.lastToken, tokens: .printerControl(kind: .enableBreaking), .break(.reset, size: 0)) @@ -1668,9 +1668,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind { - after(node.attributes?.lastToken, tokens: .space) + // Import declarations should never be wrapped. + before(node.firstToken, tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: false))) + + arrangeAttributeList(node.attributes) after(node.importTok, tokens: .space) after(node.importKind, tokens: .space) + + after(node.lastToken, tokens: .printerControl(kind: .enableBreaking)) return .visitChildren } diff --git a/Tests/SwiftFormatPrettyPrintTests/ImportTests.swift b/Tests/SwiftFormatPrettyPrintTests/ImportTests.swift index befa073b5..326f36718 100644 --- a/Tests/SwiftFormatPrettyPrintTests/ImportTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/ImportTests.swift @@ -7,6 +7,12 @@ final class ImportTests: PrettyPrintTestCase { import class MyModule.MyClass import struct MyModule.MyStruct @testable import testModule + + @_spi( + STP + ) + @testable + import testModule """ let expected = @@ -17,6 +23,8 @@ final class ImportTests: PrettyPrintTestCase { import struct MyModule.MyStruct @testable import testModule + @_spi(STP) @testable import testModule + """ // Imports should not wrap From 32e369e8082f1a9f008f54b5f4365e0386d1ad05 Mon Sep 17 00:00:00 2001 From: stackotter Date: Tue, 4 Apr 2023 21:23:40 +1000 Subject: [PATCH 027/332] Avoid disambiguating parentheses being removed from called closures in conditions (#298) A bit of a mouthfull to fit into a reasonably sized commit message. The NoParensAroundConditions rule removed parens around immediately called closures and in doing so introduced ambiguities into code that was correct to begin with. --- .../NoParensAroundConditions.swift | 14 ++++++++++---- .../NoParensAroundConditionsTests.swift | 13 +++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftFormatRules/NoParensAroundConditions.swift b/Sources/SwiftFormatRules/NoParensAroundConditions.swift index 162715254..1ee8678a7 100644 --- a/Sources/SwiftFormatRules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormatRules/NoParensAroundConditions.swift @@ -30,10 +30,16 @@ public final class NoParensAroundConditions: SyntaxFormatRule { assert(tuple.elementList.count == 1) let expr = tuple.elementList.first!.expression - // If the condition is a function with a trailing closure, removing the - // outer set of parentheses introduces a parse ambiguity. - if let fnCall = expr.as(FunctionCallExprSyntax.self), fnCall.trailingClosure != nil { - return ExprSyntax(tuple) + // If the condition is a function with a trailing closure or if it's an immediately called + // closure, removing the outer set of parentheses introduces a parse ambiguity. + if let fnCall = expr.as(FunctionCallExprSyntax.self) { + if fnCall.trailingClosure != nil { + // Leave parentheses around call with trailing closure. + return ExprSyntax(tuple) + } else if fnCall.calledExpression.as(ClosureExprSyntax.self) != nil { + // Leave parentheses around immediately called closure. + return ExprSyntax(tuple) + } } diagnose(.removeParensAroundExpression, on: expr) diff --git a/Tests/SwiftFormatRulesTests/NoParensAroundConditionsTests.swift b/Tests/SwiftFormatRulesTests/NoParensAroundConditionsTests.swift index 865082ed3..9dbe9d244 100644 --- a/Tests/SwiftFormatRulesTests/NoParensAroundConditionsTests.swift +++ b/Tests/SwiftFormatRulesTests/NoParensAroundConditionsTests.swift @@ -160,4 +160,17 @@ final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { } """) } + + func testParensAroundAmbiguousConditions() { + XCTAssertFormatting( + NoParensAroundConditions.self, + input: """ + if ({ true }()) {} + if (functionWithTrailingClosure { 5 }) {} + """, + expected: """ + if ({ true }()) {} + if (functionWithTrailingClosure { 5 }) {} + """) + } } From c2c3b0187b0f3d38adfab88b5c1c1ea7bf214d63 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 5 Apr 2023 15:28:52 -0700 Subject: [PATCH 028/332] =?UTF-8?q?Remove=20usages=20of=20functions=20that?= =?UTF-8?q?=20shouldn=E2=80=99t=20be=20part=20of=20SwiftSyntax=E2=80=99s?= =?UTF-8?q?=20public=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift | 2 +- Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift | 4 ++-- .../NoAccessLevelOnExtensionDeclaration.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift index 30518c97c..69814fe24 100644 --- a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift +++ b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift @@ -161,7 +161,7 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { let name = modifier.name if name.tokenKind == invalidAccess { diagnose(diagnostic, on: name) - return modifier.with(\.name, name.withKind(validAccess)) + return modifier.with(\.name, name.with(\.tokenKind, validAccess)) } return modifier } diff --git a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift index f1b37a924..eb7cebab9 100644 --- a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift +++ b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift @@ -38,9 +38,9 @@ extension ModifierListSyntax { /// Returns modifier list without the given modifier. func remove(name: String) -> ModifierListSyntax { guard has(modifier: name) else { return self } - for mod in self { + for (index, mod) in self.enumerated() { if mod.name.text == name { - return removing(childAt: mod.indexInParent) + return removing(childAt: index) } } return self diff --git a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift index dd6841bb2..b8058fd7a 100644 --- a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift @@ -39,7 +39,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { let accessKeywordToAdd: DeclModifierSyntax if keywordKind == .keyword(.private) { accessKeywordToAdd - = accessKeyword.with(\.name, accessKeyword.name.withKind(.keyword(.fileprivate))) + = accessKeyword.with(\.name, accessKeyword.name.with(\.tokenKind, .keyword(.fileprivate))) } else { accessKeywordToAdd = accessKeyword } From eae9b782e5fff783a7c007f20b683c1c03cb608b Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Fri, 7 Apr 2023 10:50:06 -0700 Subject: [PATCH 029/332] Prepare 508.0.0 release. --- README.md | 27 ++++++++++++++++------- Sources/swift-format/VersionOptions.swift | 2 +- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c66c94d64..f78af4826 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,25 @@ invoked via an [API](#api-usage). > and the code is provided so that it can be tested on real-world code and > experiments can be made by modifying it. -## Matching swift-format to Your Swift Version (Swift 5.7 and earlier) +## Matching swift-format to Your Swift Version -> NOTE: `swift-format` on the `main` branch now uses a version of -> [SwiftSyntax](https://github.com/apple/swift-syntax) whose parser has been -> rewritten in Swift and no longer has dependencies on libraries in the -> Swift toolchain. This allows `swift-format` to be built, developed, and -> run using any version of Swift that can compile it, decoupling it from -> the version that supported a particular syntax. +### Swift 5.8 and later + +As of Swift 5.8, swift-format depends on the version of +[SwiftSyntax](https://github.com/apple/swift-syntax) whose parser has been +rewritten in Swift and no longer has dependencies on libraries in the +Swift toolchain. + +This change allows `swift-format` to be built, developed, and run using +any version of Swift that can compile it, decoupling it from the version +that supported a particular syntax. However, earlier versions of swift-format +will still not be able to recognize new syntax added in later versions of the +language and parser. + +Note also that the version numbering scheme has changed to match +SwiftSyntax; the 5.8 release of swift-format is `508.0.0`, not `0.50800.0`. + +### Swift 5.7 and earlier `swift-format` versions 0.50700.0 and earlier depend on versions of [SwiftSyntax](https://github.com/apple/swift-syntax) that used a standalone @@ -54,7 +65,7 @@ then once you have identified the version you need, you can check out the source and build it using the following commands: ```sh -VERSION=0.50700.0 # replace this with the version you need +VERSION=508.0.0 # replace this with the version you need git clone https://github.com/apple/swift-format.git cd swift-format git checkout "tags/$VERSION" diff --git a/Sources/swift-format/VersionOptions.swift b/Sources/swift-format/VersionOptions.swift index 8ac851fdc..84a420181 100644 --- a/Sources/swift-format/VersionOptions.swift +++ b/Sources/swift-format/VersionOptions.swift @@ -20,7 +20,7 @@ struct VersionOptions: ParsableArguments { func validate() throws { if version { // TODO: Automate updates to this somehow. - print("0.50500.0") + print("508.0.0") throw ExitCode.success } } From f3e5cc55435b9ba5b5aad6e24ab003d896ba7788 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 10 Apr 2023 16:16:18 -0700 Subject: [PATCH 030/332] Remove the `swift-tools-support-core` dependency from swift-format. This was only being used for the diagnostics engine, which is easy enough to roll by hand and it's one less dependency to keep in sync with the rest of the toolchain. In the future, we may want an output mode that uses the new prettier diagnostic printer from swift-syntax. --- Package.swift | 6 - .../Frontend/FormatFrontend.swift | 3 +- Sources/swift-format/Frontend/Frontend.swift | 4 +- .../swift-format/Utilities/Diagnostic.swift | 80 ++++++++++ .../Utilities/DiagnosticsEngine.swift | 126 +++++++++++++++ .../Utilities/StderrDiagnosticPrinter.swift | 28 ++-- Sources/swift-format/Utilities/TTY.swift | 29 ++++ .../Utilities/UnifiedDiagnosticsEngine.swift | 151 ------------------ 8 files changed, 254 insertions(+), 173 deletions(-) create mode 100644 Sources/swift-format/Utilities/Diagnostic.swift create mode 100644 Sources/swift-format/Utilities/DiagnosticsEngine.swift create mode 100644 Sources/swift-format/Utilities/TTY.swift delete mode 100644 Sources/swift-format/Utilities/UnifiedDiagnosticsEngine.swift diff --git a/Package.swift b/Package.swift index 99e0630ef..68d0d41e9 100644 --- a/Package.swift +++ b/Package.swift @@ -143,7 +143,6 @@ let package = Package( .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), - .product(name: "TSCBasic", package: "swift-tools-support-core"), ] ), @@ -223,15 +222,10 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { url: "https://github.com/apple/swift-syntax.git", branch: "main" ), - .package( - url: "https://github.com/apple/swift-tools-support-core.git", - exact: Version("0.4.0") - ), ] } else { package.dependencies += [ .package(path: "../swift-argument-parser"), .package(path: "../swift-syntax"), - .package(path: "../swift-tools-support-core"), ] } diff --git a/Sources/swift-format/Frontend/FormatFrontend.swift b/Sources/swift-format/Frontend/FormatFrontend.swift index 996b1a924..aac36856b 100644 --- a/Sources/swift-format/Frontend/FormatFrontend.swift +++ b/Sources/swift-format/Frontend/FormatFrontend.swift @@ -40,7 +40,8 @@ class FormatFrontend: Frontend { return } - let diagnosticHandler: (Diagnostic, SourceLocation) -> () = { (diagnostic, location) in + let diagnosticHandler: (SwiftDiagnostics.Diagnostic, SourceLocation) -> () = { + (diagnostic, location) in guard !self.lintFormatOptions.ignoreUnparsableFiles else { // No diagnostics should be emitted in this mode. return diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index fa9611fac..0c8c4a50f 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -57,7 +57,7 @@ class Frontend { final let diagnosticPrinter: StderrDiagnosticPrinter /// The diagnostic engine to which warnings and errors will be emitted. - final let diagnosticsEngine: UnifiedDiagnosticsEngine + final let diagnosticsEngine: DiagnosticsEngine /// Options that apply during formatting or linting. final let lintFormatOptions: LintFormatOptions @@ -83,7 +83,7 @@ class Frontend { self.diagnosticPrinter = StderrDiagnosticPrinter( colorMode: lintFormatOptions.colorDiagnostics.map { $0 ? .on : .off } ?? .auto) self.diagnosticsEngine = - UnifiedDiagnosticsEngine(diagnosticsHandlers: [diagnosticPrinter.printDiagnostic]) + DiagnosticsEngine(diagnosticsHandlers: [diagnosticPrinter.printDiagnostic]) } /// Runs the linter or formatter over the inputs. diff --git a/Sources/swift-format/Utilities/Diagnostic.swift b/Sources/swift-format/Utilities/Diagnostic.swift new file mode 100644 index 000000000..3a80333a9 --- /dev/null +++ b/Sources/swift-format/Utilities/Diagnostic.swift @@ -0,0 +1,80 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftFormatCore +import SwiftSyntax + +/// Diagnostic data that retains the separation of a finding category (if present) from the rest of +/// the message, allowing diagnostic printers that want to print those values separately to do so. +struct Diagnostic { + /// The severity of the diagnostic. + enum Severity { + case note + case warning + case error + } + + /// Represents the location of a diagnostic. + struct Location { + /// The file path associated with the diagnostic. + var file: String + + /// The 1-based line number where the diagnostic occurred. + var line: Int + + /// The 1-based column number where the diagnostic occurred. + var column: Int + + /// Creates a new diagnostic location from the given source location. + init(_ sourceLocation: SourceLocation) { + self.file = sourceLocation.file! + self.line = sourceLocation.line! + self.column = sourceLocation.column! + } + + /// Creates a new diagnostic location with the given finding location. + init(_ findingLocation: Finding.Location) { + self.file = findingLocation.file + self.line = findingLocation.line + self.column = findingLocation.column + } + } + + /// The severity of the diagnostic. + var severity: Severity + + /// The location where the diagnostic occurred, if known. + var location: Location? + + /// The category of the diagnostic, if any. + var category: String? + + /// The message text associated with the diagnostic. + var message: String + + var description: String { + if let category = category { + return "[\(category)] \(message)" + } else { + return message + } + } + + /// Creates a new diagnostic with the given severity, location, optional category, and + /// message. + init(severity: Severity, location: Location?, category: String? = nil, message: String) { + self.severity = severity + self.location = location + self.category = category + self.message = message + } +} diff --git a/Sources/swift-format/Utilities/DiagnosticsEngine.swift b/Sources/swift-format/Utilities/DiagnosticsEngine.swift new file mode 100644 index 000000000..52e0b8909 --- /dev/null +++ b/Sources/swift-format/Utilities/DiagnosticsEngine.swift @@ -0,0 +1,126 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftFormatCore +import SwiftSyntax +import SwiftDiagnostics + +/// Unifies the handling of findings from the linter, parsing errors from the syntax parser, and +/// generic errors from the frontend so that they are emitted in a uniform fashion. +final class DiagnosticsEngine { + /// The handler functions that will be called to process diagnostics that are emitted. + private let handlers: [(Diagnostic) -> Void] + + /// A Boolean value indicating whether any errors were emitted by the diagnostics engine. + private(set) var hasErrors: Bool + + /// A Boolean value indicating whether any warnings were emitted by the diagnostics engine. + private(set) var hasWarnings: Bool + + /// Creates a new diagnostics engine with the given diagnostic handlers. + /// + /// - Parameter diagnosticsHandlers: An array of functions, each of which takes a `Diagnostic` as + /// its sole argument and returns `Void`. The functions are called whenever a diagnostic is + /// received by the engine. + init(diagnosticsHandlers: [(Diagnostic) -> Void]) { + self.handlers = diagnosticsHandlers + self.hasErrors = false + self.hasWarnings = false + } + + /// Emits the diagnostic by passing it to the registered handlers, and tracks whether it was an + /// error or warning diagnostic. + private func emit(_ diagnostic: Diagnostic) { + switch diagnostic.severity { + case .error: self.hasErrors = true + case .warning: self.hasWarnings = true + default: break + } + + for handler in handlers { + handler(diagnostic) + } + } + + /// Emits a generic error message. + /// + /// - Parameters: + /// - message: The message associated with the error. + /// - location: The location in the source code associated with the error, or nil if there is no + /// location associated with the error. + func emitError(_ message: String, location: SourceLocation? = nil) { + emit( + Diagnostic( + severity: .error, + location: location.map(Diagnostic.Location.init), + message: message)) + } + + /// Emits a finding from the linter and any of its associated notes as diagnostics. + /// + /// - Parameter finding: The finding that should be emitted. + func consumeFinding(_ finding: Finding) { + emit(diagnosticMessage(for: finding)) + + for note in finding.notes { + emit( + Diagnostic( + severity: .note, + location: note.location.map(Diagnostic.Location.init), + message: "\(note.message)")) + } + } + + /// Emits a diagnostic from the syntax parser and any of its associated notes. + /// + /// - Parameter diagnostic: The syntax parser diagnostic that should be emitted. + func consumeParserDiagnostic( + _ diagnostic: SwiftDiagnostics.Diagnostic, + _ location: SourceLocation + ) { + emit(diagnosticMessage(for: diagnostic.diagMessage, at: location)) + } + + /// Converts a diagnostic message from the syntax parser into a diagnostic message that can be + /// used by the `TSCBasic` diagnostics engine and returns it. + private func diagnosticMessage( + for message: SwiftDiagnostics.DiagnosticMessage, + at location: SourceLocation + ) -> Diagnostic { + let severity: Diagnostic.Severity + switch message.severity { + case .error: severity = .error + case .warning: severity = .warning + case .note: severity = .note + } + return Diagnostic( + severity: severity, + location: Diagnostic.Location(location), + category: nil, + message: message.message) + } + + /// Converts a lint finding into a diagnostic message that can be used by the `TSCBasic` + /// diagnostics engine and returns it. + private func diagnosticMessage(for finding: Finding) -> Diagnostic { + let severity: Diagnostic.Severity + switch finding.severity { + case .error: severity = .error + case .warning: severity = .warning + } + return Diagnostic( + severity: severity, + location: finding.location.map(Diagnostic.Location.init), + category: "\(finding.category)", + message: "\(finding.message.text)") + } +} diff --git a/Sources/swift-format/Utilities/StderrDiagnosticPrinter.swift b/Sources/swift-format/Utilities/StderrDiagnosticPrinter.swift index f6452be82..f7730f00c 100644 --- a/Sources/swift-format/Utilities/StderrDiagnosticPrinter.swift +++ b/Sources/swift-format/Utilities/StderrDiagnosticPrinter.swift @@ -12,7 +12,6 @@ import Dispatch import Foundation -import TSCBasic /// Manages printing of diagnostics to standard error. final class StderrDiagnosticPrinter { @@ -49,11 +48,7 @@ final class StderrDiagnosticPrinter { init(colorMode: ColorMode) { switch colorMode { case .auto: - if let stream = stderrStream.stream as? LocalFileOutputByteStream { - useColors = TerminalController.isTTY(stream) - } else { - useColors = false - } + useColors = isTTY(FileHandle.standardError) case .off: useColors = false case .on: @@ -62,25 +57,32 @@ final class StderrDiagnosticPrinter { } /// Prints a diagnostic to standard error. - func printDiagnostic(_ diagnostic: TSCBasic.Diagnostic) { + func printDiagnostic(_ diagnostic: Diagnostic) { printQueue.sync { let stderr = FileHandleTextOutputStream(FileHandle.standardError) - stderr.write("\(ansiSGR(.boldWhite))\(diagnostic.location): ") + stderr.write("\(ansiSGR(.boldWhite))\(description(of: diagnostic.location)): ") - switch diagnostic.behavior { + switch diagnostic.severity { case .error: stderr.write("\(ansiSGR(.boldRed))error: ") case .warning: stderr.write("\(ansiSGR(.boldMagenta))warning: ") case .note: stderr.write("\(ansiSGR(.boldGray))note: ") - case .remark, .ignored: break } - let data = diagnostic.data as! UnifiedDiagnosticData - if let category = data.category { + if let category = diagnostic.category { stderr.write("\(ansiSGR(.boldYellow))[\(category)] ") } - stderr.write("\(ansiSGR(.boldWhite))\(data.message)\(ansiSGR(.reset))\n") + stderr.write("\(ansiSGR(.boldWhite))\(diagnostic.message)\(ansiSGR(.reset))\n") + } + } + + /// Returns a string representation of the given diagnostic location, or a fallback string if the + /// location was not known. + private func description(of location: Diagnostic.Location?) -> String { + if let location = location { + return "\(location.file):\(location.line):\(location.column)" } + return "" } /// Returns the complete ANSI sequence used to enable the given SGR if colors are enabled in the diff --git a/Sources/swift-format/Utilities/TTY.swift b/Sources/swift-format/Utilities/TTY.swift new file mode 100644 index 000000000..35fc35841 --- /dev/null +++ b/Sources/swift-format/Utilities/TTY.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// Returns a value indicating whether or not the stream is a TTY. +func isTTY(_ fileHandle: FileHandle) -> Bool { + // The implementation of this function is adapted from `TerminalController.swift` in + // swift-tools-support-core. + #if os(Windows) + // The TSC implementation of this function only returns `.file` or `.dumb` for Windows, + // neither of which is a TTY. + return false + #else + if ProcessInfo.processInfo.environment["TERM"] == "dumb" { + return false + } + return isatty(fileHandle.fileDescriptor) != 0 + #endif +} diff --git a/Sources/swift-format/Utilities/UnifiedDiagnosticsEngine.swift b/Sources/swift-format/Utilities/UnifiedDiagnosticsEngine.swift deleted file mode 100644 index de6963f58..000000000 --- a/Sources/swift-format/Utilities/UnifiedDiagnosticsEngine.swift +++ /dev/null @@ -1,151 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import SwiftFormatCore -import SwiftSyntax -import SwiftDiagnostics -import TSCBasic - -/// Diagnostic data that retains the separation of a finding category (if present) from the rest of -/// the message, allowing diagnostic printers that want to print those values separately to do so. -struct UnifiedDiagnosticData: DiagnosticData { - /// The category of the diagnostic, if any. - var category: String? - - /// The message text associated with the diagnostic. - var message: String - - var description: String { - if let category = category { - return "[\(category)] \(message)" - } else { - return message - } - } - - /// Creates a new unified diagnostic with the given optional category and message. - init(category: String? = nil, message: String) { - self.category = category - self.message = message - } -} - -/// Unifies the handling of findings from the linter, parsing errors from the syntax parser, and -/// generic errors from the frontend so that they are treated uniformly by the underlying -/// diagnostics engine from the `swift-tools-support-core` package. -final class UnifiedDiagnosticsEngine { - /// Represents a location from either the linter or the syntax parser and supports converting it - /// to a string representation for printing. - private enum UnifiedLocation: DiagnosticLocation { - /// A location received from the swift parser. - case parserLocation(SourceLocation) - - /// A location received from the linter. - case findingLocation(Finding.Location) - - var description: String { - switch self { - case .parserLocation(let location): - // SwiftSyntax's old diagnostic printer also force-unwrapped these, so we assume that they - // will always be present if the location itself is non-nil. - return "\(location.file!):\(location.line!):\(location.column!)" - case .findingLocation(let location): - return "\(location.file):\(location.line):\(location.column)" - } - } - } - - /// The underlying diagnostics engine. - private let diagnosticsEngine: DiagnosticsEngine - - /// A Boolean value indicating whether any errors were emitted by the diagnostics engine. - var hasErrors: Bool { diagnosticsEngine.hasErrors } - - /// A Boolean value indicating whether any warnings were emitted by the diagnostics engine. - var hasWarnings: Bool { - diagnosticsEngine.diagnostics.contains { $0.behavior == .warning } - } - - /// Creates a new unified diagnostics engine with the given diagnostic handlers. - /// - /// - Parameter diagnosticsHandlers: An array of functions, each of which takes a `Diagnostic` as - /// its sole argument and returns `Void`. The functions are called whenever a diagnostic is - /// received by the engine. - init(diagnosticsHandlers: [DiagnosticsEngine.DiagnosticsHandler]) { - self.diagnosticsEngine = DiagnosticsEngine(handlers: diagnosticsHandlers) - } - - /// Emits a generic error message. - /// - /// - Parameters: - /// - message: The message associated with the error. - /// - location: The location in the source code associated with the error, or nil if there is no - /// location associated with the error. - func emitError(_ message: String, location: SourceLocation? = nil) { - diagnosticsEngine.emit( - .error(UnifiedDiagnosticData(message: message)), - location: location.map(UnifiedLocation.parserLocation)) - } - - /// Emits a finding from the linter and any of its associated notes as diagnostics. - /// - /// - Parameter finding: The finding that should be emitted. - func consumeFinding(_ finding: Finding) { - diagnosticsEngine.emit( - diagnosticMessage(for: finding), - location: finding.location.map(UnifiedLocation.findingLocation)) - - for note in finding.notes { - diagnosticsEngine.emit( - .note(UnifiedDiagnosticData(message: "\(note.message)")), - location: note.location.map(UnifiedLocation.findingLocation)) - } - } - - /// Emits a diagnostic from the syntax parser and any of its associated notes. - /// - /// - Parameter diagnostic: The syntax parser diagnostic that should be emitted. - func consumeParserDiagnostic( - _ diagnostic: SwiftDiagnostics.Diagnostic, - _ location: SourceLocation - ) { - diagnosticsEngine.emit( - diagnosticMessage(for: diagnostic.diagMessage), - location: UnifiedLocation.parserLocation(location)) - } - - /// Converts a diagnostic message from the syntax parser into a diagnostic message that can be - /// used by the `TSCBasic` diagnostics engine and returns it. - private func diagnosticMessage(for message: SwiftDiagnostics.DiagnosticMessage) - -> TSCBasic.Diagnostic.Message - { - let data = UnifiedDiagnosticData(category: nil, message: message.message) - - switch message.severity { - case .error: return .error(data) - case .warning: return .warning(data) - case .note: return .note(data) - } - } - - /// Converts a lint finding into a diagnostic message that can be used by the `TSCBasic` - /// diagnostics engine and returns it. - private func diagnosticMessage(for finding: Finding) -> TSCBasic.Diagnostic.Message { - let data = - UnifiedDiagnosticData(category: "\(finding.category)", message: "\(finding.message.text)") - - switch finding.severity { - case .error: return .error(data) - case .warning: return .warning(data) - } - } -} From 437b6cb41de2de489f3d257f388f9fffd753515e Mon Sep 17 00:00:00 2001 From: CippoX Date: Wed, 12 Apr 2023 14:03:01 +0200 Subject: [PATCH 031/332] removed warnings "Use (viewMode: .sourceAccurate) instead" --- .../LegacyTriviaBehavior.swift | 2 +- Sources/SwiftFormatCore/RuleMask.swift | 8 +- .../TokenStreamCreator.swift | 406 +++++++++--------- .../AddModifierRewriter.swift | 6 +- .../DeclSyntaxProtocol+Comments.swift | 2 +- .../SwiftFormatRules/DoNotUseSemicolons.swift | 2 +- .../SwiftFormatRules/FullyIndirectEnum.swift | 6 +- .../ModifierListSyntax+Convenience.swift | 4 +- .../NoCasesWithOnlyFallthrough.swift | 2 +- .../NoEmptyTrailingClosureParentheses.swift | 4 +- .../NoParensAroundConditions.swift | 2 +- .../OneVariableDeclarationPerLine.swift | 2 +- Sources/SwiftFormatRules/OrderedImports.swift | 10 +- .../ReturnVoidInsteadOfEmptyTuple.swift | 4 +- .../UseShorthandTypeNames.swift | 10 +- ...eTripleSlashForDocumentationComments.swift | 2 +- .../UseWhereClausesInForLoops.swift | 2 +- .../ValidateDocumentationComments.swift | 2 +- 18 files changed, 238 insertions(+), 238 deletions(-) diff --git a/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift b/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift index 5011c8be8..6fc9365c3 100644 --- a/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift +++ b/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift @@ -20,7 +20,7 @@ private final class LegacyTriviaBehaviorRewriter: SyntaxRewriter { token = token.with(\.leadingTrivia, pendingLeadingTrivia + token.leadingTrivia) self.pendingLeadingTrivia = nil } - if token.nextToken != nil, + if token.nextToken(viewMode: .sourceAccurate) != nil, let firstIndexToMove = token.trailingTrivia.firstIndex(where: shouldTriviaPieceBeMoved) { pendingLeadingTrivia = Trivia(pieces: Array(token.trailingTrivia[firstIndexToMove...])) diff --git a/Sources/SwiftFormatCore/RuleMask.swift b/Sources/SwiftFormatCore/RuleMask.swift index b614b17b0..37dfec1c2 100644 --- a/Sources/SwiftFormatCore/RuleMask.swift +++ b/Sources/SwiftFormatCore/RuleMask.swift @@ -136,7 +136,7 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor { // MARK: - Syntax Visitation Methods override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { - guard let firstToken = node.firstToken else { + guard let firstToken = node.firstToken(viewMode: .sourceAccurate) else { return .visitChildren } let comments = loneLineComments(in: firstToken.leadingTrivia, isFirstToken: true) @@ -159,14 +159,14 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor { } override func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind { - guard let firstToken = node.firstToken else { + guard let firstToken = node.firstToken(viewMode: .sourceAccurate) else { return .visitChildren } return appendRuleStatusDirectives(from: firstToken, of: Syntax(node)) } override func visit(_ node: MemberDeclListItemSyntax) -> SyntaxVisitorContinueKind { - guard let firstToken = node.firstToken else { + guard let firstToken = node.firstToken(viewMode: .sourceAccurate) else { return .visitChildren } return appendRuleStatusDirectives(from: firstToken, of: Syntax(node)) @@ -183,7 +183,7 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor { private func appendRuleStatusDirectives(from token: TokenSyntax, of node: Syntax) -> SyntaxVisitorContinueKind { - let isFirstInFile = token.previousToken == nil + let isFirstInFile = token.previousToken(viewMode: .sourceAccurate) == nil let matches = loneLineComments(in: token.leadingTrivia, isFirstToken: isFirstInFile) .compactMap(ruleStatusDirectiveMatch) let sourceRange = node.sourceRange(converter: sourceLocationConverter) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 79cc1a16f..4a74b9ed6 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -109,7 +109,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { betweenElementsOf collectionNode: Node ) where Node.Element == Syntax { for element in collectionNode.dropLast() { - after(element.lastToken, tokens: tokens) + after(element.lastToken(viewMode: .sourceAccurate), tokens: tokens) } } @@ -120,7 +120,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { betweenElementsOf collectionNode: Node ) where Node.Element: SyntaxProtocol { for element in collectionNode.dropLast() { - after(element.lastToken, tokens: tokens) + after(element.lastToken(viewMode: .sourceAccurate), tokens: tokens) } } @@ -131,18 +131,18 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { betweenElementsOf collectionNode: Node ) where Node.Element == DeclSyntax { for element in collectionNode.dropLast() { - after(element.lastToken, tokens: tokens) + after(element.lastToken(viewMode: .sourceAccurate), tokens: tokens) } } private func verbatimToken(_ node: Syntax, indentingBehavior: IndentingBehavior = .allLines) { - if let firstToken = node.firstToken { + if let firstToken = node.firstToken(viewMode: .sourceAccurate) { appendBeforeTokens(firstToken) } appendToken(.verbatim(Verbatim(text: node.description, indentingBehavior: indentingBehavior))) - if let lastToken = node.lastToken { + if let lastToken = node.lastToken(viewMode: .sourceAccurate) { // Extract any comments that trail the verbatim block since they belong to the next syntax // token. Leading comments don't need special handling since they belong to the current node, // and will get printed. @@ -224,7 +224,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { - guard let lastTokenOfExtendedType = node.extendedType.lastToken else { + guard let lastTokenOfExtendedType = node.extendedType.lastToken(viewMode: .sourceAccurate) else { fatalError("ExtensionDeclSyntax.extendedType must have at least one token") } arrangeTypeDeclBlock( @@ -253,29 +253,29 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { genericWhereClause: GenericWhereClauseSyntax?, members: MemberDeclBlockSyntax ) { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) arrangeAttributeList(attributes) // Prioritize keeping " :" together (corresponding group close is // below at `lastTokenBeforeBrace`). - let firstTokenAfterAttributes = modifiers?.firstToken ?? typeKeyword + let firstTokenAfterAttributes = modifiers?.firstToken(viewMode: .sourceAccurate) ?? typeKeyword before(firstTokenAfterAttributes, tokens: .open) after(typeKeyword, tokens: .break) arrangeBracesAndContents(of: members, contentsKeyPath: \.members) if let genericWhereClause = genericWhereClause { - before(genericWhereClause.firstToken, tokens: .break(.same), .open) + before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) after(members.leftBrace, tokens: .close) } let lastTokenBeforeBrace = inheritanceClause?.colon - ?? genericParameterOrPrimaryAssociatedTypeClause?.lastToken + ?? genericParameterOrPrimaryAssociatedTypeClause?.lastToken(viewMode: .sourceAccurate) ?? identifier after(lastTokenBeforeBrace, tokens: .close) - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } // MARK: - Function and function-like declaration nodes (initializers, deinitializers, subscripts) @@ -287,7 +287,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // has arguments. if hasArguments && config.prioritizeKeepingFunctionOutputTogether { // Due to visitation order, the matching .open break is added in ParameterClauseSyntax. - after(node.signature.lastToken, tokens: .close) + after(node.signature.lastToken(viewMode: .sourceAccurate), tokens: .close) } let mustBreak = node.body != nil || node.signature.output != nil @@ -295,7 +295,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Prioritize keeping " func (" together. Also include the ")" if the parameter // list is empty. - let firstTokenAfterAttributes = node.modifiers?.firstToken ?? node.funcKeyword + let firstTokenAfterAttributes = node.modifiers?.firstToken(viewMode: .sourceAccurate) ?? node.funcKeyword before(firstTokenAfterAttributes, tokens: .open) after(node.funcKeyword, tokens: .break) if hasArguments || node.genericParameterClause != nil { @@ -309,7 +309,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // defining a prefix or postfix operator, the token kind always comes through as // `binaryOperator`. if case .binaryOperator = node.identifier.tokenKind { - after(node.identifier.lastToken, tokens: .space) + after(node.identifier.lastToken(viewMode: .sourceAccurate), tokens: .space) } arrangeFunctionLikeDecl( @@ -329,13 +329,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // has arguments. if hasArguments && config.prioritizeKeepingFunctionOutputTogether { // Due to visitation order, the matching .open break is added in ParameterClauseSyntax. - after(node.signature.lastToken, tokens: .close) + after(node.signature.lastToken(viewMode: .sourceAccurate), tokens: .close) } arrangeParameterClause(node.signature.input, forcesBreakBeforeRightParen: node.body != nil) // Prioritize keeping " init" together. - let firstTokenAfterAttributes = node.modifiers?.firstToken ?? node.initKeyword + let firstTokenAfterAttributes = node.modifiers?.firstToken(viewMode: .sourceAccurate) ?? node.initKeyword before(firstTokenAfterAttributes, tokens: .open) if hasArguments || node.genericParameterClause != nil { @@ -367,10 +367,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind { let hasArguments = !node.indices.parameterList.isEmpty - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) // Prioritize keeping " subscript" together. - if let firstModifierToken = node.modifiers?.firstToken { + if let firstModifierToken = node.modifiers?.firstToken(viewMode: .sourceAccurate) { before(firstModifierToken, tokens: .open) if hasArguments || node.genericParameterClause != nil { @@ -384,17 +384,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // arguments. if hasArguments && config.prioritizeKeepingFunctionOutputTogether { // Due to visitation order, the matching .open break is added in ParameterClauseSyntax. - after(node.result.lastToken, tokens: .close) + after(node.result.lastToken(viewMode: .sourceAccurate), tokens: .close) } arrangeAttributeList(node.attributes) if let genericWhereClause = node.genericWhereClause { - before(genericWhereClause.firstToken, tokens: .break(.same), .open) - after(genericWhereClause.lastToken, tokens: .close) + before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) + after(genericWhereClause.lastToken(viewMode: .sourceAccurate), tokens: .close) } - before(node.result.firstToken, tokens: .break) + before(node.result.firstToken(viewMode: .sourceAccurate), tokens: .break) if let accessorOrCodeBlock = node.accessor { switch accessorOrCodeBlock { @@ -405,7 +405,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) arrangeParameterClause(node.indices, forcesBreakBeforeRightParen: true) @@ -421,17 +421,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { body: Node?, bodyContentsKeyPath: KeyPath? ) where BodyContents.Element: SyntaxProtocol { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) arrangeAttributeList(attributes) arrangeBracesAndContents(of: body, contentsKeyPath: bodyContentsKeyPath) if let genericWhereClause = genericWhereClause { - before(genericWhereClause.firstToken, tokens: .break(.same), .open) - after(body?.leftBrace ?? genericWhereClause.lastToken, tokens: .close) + before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) + after(body?.leftBrace ?? genericWhereClause.lastToken(viewMode: .sourceAccurate), tokens: .close) } - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } // MARK: - Property and subscript accessor block nodes @@ -442,7 +442,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // protocol and we want to let them be placed on the same line if possible. Otherwise, we // place a newline between each accessor. let newlines: NewlineBehavior = child.body == nil ? .elective : .soft - after(child.lastToken, tokens: .break(.same, size: 1, newlines: newlines)) + after(child.lastToken(viewMode: .sourceAccurate), tokens: .break(.same, size: 1, newlines: newlines)) } return .visitChildren } @@ -484,8 +484,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // There may be a consistent breaking group around this node, see `CodeBlockItemSyntax`. This // group is necessary so that breaks around and inside of the conditions aren't forced to break // when the if-stmt spans multiple lines. - before(node.conditions.firstToken, tokens: .open) - after(node.conditions.lastToken, tokens: .close) + before(node.conditions.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(node.conditions.lastToken(viewMode: .sourceAccurate), tokens: .close) after(node.ifKeyword, tokens: .space) @@ -494,8 +494,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // the conditions. There are no breaks around the first condition because if-statements look // better without a break between the "if" and the first condition. for condition in node.conditions.dropFirst() { - before(condition.firstToken, tokens: .break(.open(kind: .continuation), size: 0)) - after(condition.lastToken, tokens: .break(.close(mustBreak: false), size: 0)) + before(condition.firstToken(viewMode: .sourceAccurate), tokens: .break(.open(kind: .continuation), size: 0)) + after(condition.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) } arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) @@ -531,8 +531,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Add break groups, using open continuation breaks, around all conditions so that continuations // inside of the conditions can stack in addition to continuations between the conditions. for condition in node.conditions { - before(condition.firstToken, tokens: .break(.open(kind: .continuation), size: 0)) - after(condition.lastToken, tokens: .break(.close(mustBreak: false), size: 0)) + before(condition.firstToken(viewMode: .sourceAccurate), tokens: .break(.open(kind: .continuation), size: 0)) + after(condition.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) } before(node.elseKeyword, tokens: .break(.reset), .open) @@ -571,7 +571,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after( typeAnnotation.colon, tokens: .break(.open(kind: .continuation), newlines: .elective(ignoresDiscretionary: true))) - after(typeAnnotation.lastToken, tokens: .break(.close(mustBreak: false), size: 0)) + after(typeAnnotation.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) } arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) @@ -591,8 +591,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // condition could be longer than the column limit since there are no breaks between the label // or while token. for condition in node.conditions.dropFirst() { - before(condition.firstToken, tokens: .break(.open(kind: .continuation), size: 0)) - after(condition.lastToken, tokens: .break(.close(mustBreak: false), size: 0)) + before(condition.firstToken(viewMode: .sourceAccurate), tokens: .break(.open(kind: .continuation), size: 0)) + after(condition.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) } arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) @@ -605,7 +605,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if config.lineBreakBeforeControlFlowKeywords { before(node.whileKeyword, tokens: .break(.same), .open) - after(node.condition.lastToken, tokens: .close) + after(node.condition.lastToken(viewMode: .sourceAccurate), tokens: .close) } else { // The length of the condition needs to force the breaks around the braces of the repeat // stmt's body, so that there's always a break before the right brace when the while & @@ -613,7 +613,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.whileKeyword, tokens: .space) // The `open` token occurs after the ending tokens for the braced `body` node. before(node.body.rightBrace, tokens: .open) - after(node.condition.lastToken, tokens: .close) + after(node.condition.lastToken(viewMode: .sourceAccurate), tokens: .close) } after(node.whileKeyword, tokens: .space) return .visitChildren @@ -635,11 +635,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // old (pre-SE-0276) behavior (a fixed space after the `catch` keyword). if catchItems.count > 1 { for catchItem in catchItems { - before(catchItem.firstToken, tokens: .break(.open(kind: .continuation))) - after(catchItem.lastToken, tokens: .break(.close(mustBreak: false), size: 0)) + before(catchItem.firstToken(viewMode: .sourceAccurate), tokens: .break(.open(kind: .continuation))) + after(catchItem.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) } } else { - before(node.catchItems?.firstToken, tokens: .space) + before(node.catchItems?.firstToken(viewMode: .sourceAccurate), tokens: .space) } } @@ -661,17 +661,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: ReturnStmtSyntax) -> SyntaxVisitorContinueKind { if let expression = node.expression { if leftmostMultilineStringLiteral(of: expression) != nil { - before(expression.firstToken, tokens: .break(.open)) - after(expression.lastToken, tokens: .break(.close(mustBreak: false))) + before(expression.firstToken(viewMode: .sourceAccurate), tokens: .break(.open)) + after(expression.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false))) } else { - before(expression.firstToken, tokens: .break) + before(expression.firstToken(viewMode: .sourceAccurate), tokens: .break) } } return .visitChildren } override func visit(_ node: ThrowStmtSyntax) -> SyntaxVisitorContinueKind { - before(node.expression.firstToken, tokens: .break) + before(node.expression.firstToken(viewMode: .sourceAccurate), tokens: .break) return .visitChildren } @@ -690,10 +690,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // if-configuration clause requires a break here in order to be allowed on a new line. for ifConfigDecl in node.cases.filter({ $0.is(IfConfigDeclSyntax.self) }) { if config.indentSwitchCaseLabels { - before(ifConfigDecl.firstToken, tokens: .break(.open)) - after(ifConfigDecl.lastToken, tokens: .break(.close, size: 0)) + before(ifConfigDecl.firstToken(viewMode: .sourceAccurate), tokens: .break(.open)) + after(ifConfigDecl.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0)) } else { - before(ifConfigDecl.firstToken, tokens: .break(.same)) + before(ifConfigDecl.firstToken(viewMode: .sourceAccurate), tokens: .break(.same)) } } @@ -713,10 +713,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } else { openBreak = .break(.same, newlines: .soft) } - before(node.firstToken, tokens: openBreak) + before(node.firstToken(viewMode: .sourceAccurate), tokens: openBreak) - after(node.unknownAttr?.lastToken, tokens: .space) - after(node.label.lastToken, tokens: .break(.reset, size: 0), .break(.open), .open) + after(node.unknownAttr?.lastToken(viewMode: .sourceAccurate), tokens: .space) + after(node.label.lastToken(viewMode: .sourceAccurate), tokens: .break(.reset, size: 0), .break(.open), .open) // If switch/case labels were configured to be indented, insert an extra `close` break after // the case body to match the `open` break above @@ -730,10 +730,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // same effect. If instead the opening and closing tokens were omitted completely in the absence // of statements, comments within the empty case would be incorrectly indented to the same level // as the case label. - if node.label.lastToken != node.lastToken { - after(node.lastToken, tokens: afterLastTokenTokens) + if node.label.lastToken(viewMode: .sourceAccurate) != node.lastToken(viewMode: .sourceAccurate) { + after(node.lastToken(viewMode: .sourceAccurate), tokens: afterLastTokenTokens) } else { - before(node.nextToken, tokens: afterLastTokenTokens) + before(node.nextToken(viewMode: .sourceAccurate), tokens: afterLastTokenTokens) } return .visitChildren @@ -749,7 +749,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // following a `NoCasesWithOnlyFallthrough` transformation that might merge cases. let caseItems = Array(node.caseItems) for (index, item) in caseItems.enumerated() { - before(item.firstToken, tokens: .open) + before(item.firstToken(viewMode: .sourceAccurate), tokens: .open) if let trailingComma = item.trailingComma { // Insert a newline before the next item if it has a where clause and this item doesn't. let nextItemHasWhereClause = @@ -758,7 +758,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { let newlines: NewlineBehavior = requiresNewline ? .soft : .elective after(trailingComma, tokens: .close, .break(.continue, size: 1, newlines: newlines)) } else { - after(item.lastToken, tokens: .close) + after(item.lastToken(viewMode: .sourceAccurate), tokens: .close) } } @@ -837,9 +837,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// /// - Parameter node: The tuple expression element to be arranged. private func arrangeAsTupleExprElement(_ node: TupleExprElementSyntax) { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) after(node.colon, tokens: .break) - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) if let trailingComma = node.trailingComma { closingDelimiterTokens.insert(trailingComma) } @@ -857,8 +857,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { insertTokens(.break(.same), betweenElementsOf: node) for element in node { - before(element.firstToken, tokens: .open) - after(element.lastToken, tokens: .close) + before(element.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(element.lastToken(viewMode: .sourceAccurate), tokens: .close) if let trailingComma = element.trailingComma { closingDelimiterTokens.insert(trailingComma) } @@ -868,12 +868,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let trailingComma = lastElement.trailingComma { ignoredTokens.insert(trailingComma) } - before(node.first?.firstToken, tokens: .commaDelimitedRegionStart) + before(node.first?.firstToken(viewMode: .sourceAccurate), tokens: .commaDelimitedRegionStart) let endToken = Token.commaDelimitedRegionEnd( hasTrailingComma: lastElement.trailingComma != nil, isSingleElement: node.first == lastElement) - after(lastElement.expression.lastToken, tokens: [endToken]) + after(lastElement.expression.lastToken(viewMode: .sourceAccurate), tokens: [endToken]) } return .visitChildren } @@ -899,9 +899,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { insertTokens(.break(.same), betweenElementsOf: node) for element in node { - before(element.firstToken, tokens: .open) + before(element.firstToken(viewMode: .sourceAccurate), tokens: .open) after(element.colon, tokens: .break) - after(element.lastToken, tokens: .close) + after(element.lastToken(viewMode: .sourceAccurate), tokens: .close) if let trailingComma = element.trailingComma { closingDelimiterTokens.insert(trailingComma) } @@ -911,12 +911,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let trailingComma = lastElement.trailingComma { ignoredTokens.insert(trailingComma) } - before(node.first?.firstToken, tokens: .commaDelimitedRegionStart) + before(node.first?.firstToken(viewMode: .sourceAccurate), tokens: .commaDelimitedRegionStart) let endToken = Token.commaDelimitedRegionEnd( hasTrailingComma: lastElement.trailingComma != nil, isSingleElement: node.first == node.last) - after(lastElement.lastToken, tokens: endToken) + after(lastElement.lastToken(viewMode: .sourceAccurate), tokens: endToken) } return .visitChildren } @@ -958,10 +958,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // When this function call is wrapped by a try-expr or await-expr, the group applied when // visiting that wrapping expression is sufficient. Adding another group here in that case // can result in unnecessarily breaking after the try/await keyword. - if !(base.firstToken?.previousToken(viewMode: .all)?.parent?.is(TryExprSyntax.self) ?? false - || base.firstToken?.previousToken(viewMode: .all)?.parent?.is(AwaitExprSyntax.self) ?? false) { - before(base.firstToken, tokens: .open) - after(calledMemberAccessExpr.name.lastToken, tokens: .close) + if !(base.firstToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .all)?.parent?.is(TryExprSyntax.self) ?? false + || base.firstToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .all)?.parent?.is(AwaitExprSyntax.self) ?? false) { + before(base.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(calledMemberAccessExpr.name.lastToken(viewMode: .sourceAccurate), tokens: .close) } } } @@ -1049,7 +1049,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { shouldGroup: Bool ) { if shouldGroup { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) } var additionalEndTokens = [Token]() @@ -1077,7 +1077,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } after(trailingComma, tokens: afterTrailingComma) } else if shouldGroup { - after(node.lastToken, tokens: additionalEndTokens + [.close]) + after(node.lastToken(viewMode: .sourceAccurate), tokens: additionalEndTokens + [.close]) } } @@ -1118,7 +1118,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ClosureSignatureSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) arrangeAttributeList( node.attributes, suppressFinalBreak: node.input == nil && node.capture == nil) @@ -1147,16 +1147,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } else { // Group outside of the parens, so that the argument list together, preferring to break // between the argument list and the output. - before(input.firstToken, tokens: .open) - after(input.lastToken, tokens: .close) + before(input.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(input.lastToken(viewMode: .sourceAccurate), tokens: .close) } arrangeClosureParameterClause(parameterClause, forcesBreakBeforeRightParen: true) } else { // Group around the arguments, but don't use open/close breaks because there are no parens // to create a new scope. - before(input.firstToken, tokens: .open(argumentListConsistency())) - after(input.lastToken, tokens: .close) + before(input.firstToken(viewMode: .sourceAccurate), tokens: .open(argumentListConsistency())) + after(input.lastToken(viewMode: .sourceAccurate), tokens: .close) } } @@ -1168,7 +1168,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } before(node.output?.arrow, tokens: .break) - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) before(node.inTok, tokens: .break(.same)) return .visitChildren } @@ -1180,15 +1180,15 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ClosureCaptureItemSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) - after(node.specifier?.lastToken, tokens: .break) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(node.specifier?.lastToken(viewMode: .sourceAccurate), tokens: .break) before(node.assignToken, tokens: .break) after(node.assignToken, tokens: .break) if let trailingComma = node.trailingComma { before(trailingComma, tokens: .close) after(trailingComma, tokens: .break(.same)) } else { - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -1198,8 +1198,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let calledMemberAccessExpr = node.calledExpression.as(MemberAccessExprSyntax.self) { if let base = calledMemberAccessExpr.base, base.is(IdentifierExprSyntax.self) { - before(base.firstToken, tokens: .open) - after(calledMemberAccessExpr.name.lastToken, tokens: .close) + before(base.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(calledMemberAccessExpr.name.lastToken(viewMode: .sourceAccurate), tokens: .close) } } @@ -1292,7 +1292,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ClosureParameterSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) arrangeAttributeList(node.attributes) before( node.secondName, @@ -1302,13 +1302,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let trailingComma = node.trailingComma { after(trailingComma, tokens: .close, .break(.same)) } else { - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } override func visit(_ node: EnumCaseParameterSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) before( node.secondName, tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) @@ -1317,13 +1317,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let trailingComma = node.trailingComma { after(trailingComma, tokens: .close, .break(.same)) } else { - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) arrangeAttributeList(node.attributes) before( node.secondName, @@ -1333,7 +1333,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let trailingComma = node.trailingComma { after(trailingComma, tokens: .close, .break(.same)) } else { - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -1345,7 +1345,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // `ReturnClauseSyntax`. To maintain the previous formatting behavior, // add a special case. before(node.arrow, tokens: .break) - before(node.returnType.firstToken, tokens: .break) + before(node.returnType.firstToken(viewMode: .sourceAccurate), tokens: .break) } else { after(node.arrow, tokens: .space) } @@ -1353,8 +1353,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Member type identifier is used when the return type is a member of another type. Add a group // here so that the base, dot, and member type are kept together when they fit. if node.returnType.is(MemberTypeIdentifierSyntax.self) { - before(node.returnType.firstToken, tokens: .open) - after(node.returnType.lastToken, tokens: .close) + before(node.returnType.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(node.returnType.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -1383,13 +1383,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { breakKindClose = .same } - let tokenToOpenWith = node.condition?.lastToken ?? node.poundKeyword + let tokenToOpenWith = node.condition?.lastToken(viewMode: .sourceAccurate) ?? node.poundKeyword after(tokenToOpenWith, tokens: .break(breakKindOpen), .open) // Unlike other code blocks, where we may want a single statement to be laid out on the same // line as a parent construct, the content of an `#if` block must always be on its own line; // the newline token inserted at the end enforces this. - if let lastElemTok = node.elements?.lastToken { + if let lastElemTok = node.elements?.lastToken(viewMode: .sourceAccurate) { after(lastElemTok, tokens: .break(breakKindClose, newlines: .soft), .close) } else { before(tokenToOpenWith.nextToken(viewMode: .all), tokens: .break(breakKindClose, newlines: .soft), .close) @@ -1400,7 +1400,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { let currentIfConfigDecl = node.parent?.parent?.as(IfConfigDeclSyntax.self) if let currentIfConfigDecl = currentIfConfigDecl, - let tokenBeforeCurrentIfConfigDecl = currentIfConfigDecl.previousToken, + let tokenBeforeCurrentIfConfigDecl = currentIfConfigDecl.previousToken(viewMode: .sourceAccurate), isNestedInIfConfig(node: Syntax(tokenBeforeCurrentIfConfigDecl)) || tokenBeforeCurrentIfConfigDecl.text == "}" { breakToken = .break(.reset) @@ -1410,16 +1410,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } before( - node.firstToken, + node.firstToken(viewMode: .sourceAccurate), tokens: [ .printerControl(kind: .enableBreaking), breakToken, ] ) } else if let condition = node.condition { - before(condition.firstToken, tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: true))) + before(condition.firstToken(viewMode: .sourceAccurate), tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: true))) after( - condition.lastToken, + condition.lastToken(viewMode: .sourceAccurate), tokens: .printerControl(kind: .enableBreaking), .break(.reset, size: 0)) } @@ -1434,11 +1434,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Skip ignored items, because the tokens after `item.lastToken` would be ignored and leave // unclosed open tokens. for item in node where !shouldFormatterIgnore(node: Syntax(item)) { - before(item.firstToken, tokens: .open) + before(item.firstToken(viewMode: .sourceAccurate), tokens: .open) let newlines: NewlineBehavior = item != node.last && shouldInsertNewline(basedOn: item.semicolon) ? .soft : .elective let resetSize = item.semicolon != nil ? 1 : 0 - after(item.lastToken, tokens: .close, .break(.reset, size: resetSize, newlines: newlines)) + after(item.lastToken(viewMode: .sourceAccurate), tokens: .close, .break(.reset, size: resetSize, newlines: newlines)) } return .visitChildren } @@ -1461,12 +1461,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: EnumCaseDeclSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) arrangeAttributeList(node.attributes) after(node.caseKeyword, tokens: .break) - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } @@ -1478,7 +1478,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: OperatorPrecedenceAndTypesSyntax) -> SyntaxVisitorContinueKind { before(node.colon, tokens: .space) after(node.colon, tokens: .break(.open), .open) - after(node.designatedTypes.lastToken ?? node.lastToken, tokens: .break(.close, size: 0), .close) + after(node.designatedTypes.lastToken(viewMode: .sourceAccurate) ?? node.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0), .close) return .visitChildren } @@ -1512,13 +1512,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: PrecedenceGroupRelationSyntax) -> SyntaxVisitorContinueKind { after(node.colon, tokens: .break(.open)) - after(node.lastToken, tokens: .break(.close, newlines: .soft)) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, newlines: .soft)) return .visitChildren } override func visit(_ node: PrecedenceGroupAssignmentSyntax) -> SyntaxVisitorContinueKind { after(node.colon, tokens: .break(.open)) - after(node.lastToken, tokens: .break(.close, newlines: .soft)) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, newlines: .soft)) return .visitChildren } @@ -1529,7 +1529,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: PrecedenceGroupAssociativitySyntax) -> SyntaxVisitorContinueKind { after(node.colon, tokens: .break(.open)) - after(node.lastToken, tokens: .break(.close, newlines: .soft)) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, newlines: .soft)) return .visitChildren } @@ -1541,11 +1541,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Skip ignored items, because the tokens after `item.lastToken` would be ignored and leave // unclosed open tokens. for item in node where !shouldFormatterIgnore(node: Syntax(item)) { - before(item.firstToken, tokens: .open) + before(item.firstToken(viewMode: .sourceAccurate), tokens: .open) let newlines: NewlineBehavior = item != node.last && shouldInsertNewline(basedOn: item.semicolon) ? .soft : .elective let resetSize = item.semicolon != nil ? 1 : 0 - after(item.lastToken, tokens: .close, .break(.reset, size: resetSize, newlines: newlines)) + after(item.lastToken(viewMode: .sourceAccurate), tokens: .close, .break(.reset, size: resetSize, newlines: newlines)) } return .visitChildren } @@ -1560,8 +1560,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // breaking behavior. if let exprStmt = node.item.as(ExpressionStmtSyntax.self), let ifStmt = exprStmt.expression.as(IfExprSyntax.self) { - before(ifStmt.conditions.firstToken, tokens: .open(.consistent)) - after(ifStmt.lastToken, tokens: .close) + before(ifStmt.conditions.firstToken(viewMode: .sourceAccurate), tokens: .open(.consistent)) + after(ifStmt.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -1589,7 +1589,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: TupleTypeElementSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) before( node.secondName, tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) @@ -1598,7 +1598,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let trailingComma = node.trailingComma { after(trailingComma, tokens: .close, .break(.same)) } else { - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -1625,7 +1625,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: TryExprSyntax) -> SyntaxVisitorContinueKind { before( - node.expression.firstToken, + node.expression.firstToken(viewMode: .sourceAccurate), tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) // Check for an anchor token inside of the expression to group with the try keyword. @@ -1639,7 +1639,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AwaitExprSyntax) -> SyntaxVisitorContinueKind { before( - node.expression.firstToken, + node.expression.firstToken(viewMode: .sourceAccurate), tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) // Check for an anchor token inside of the expression to group with the await keyword. @@ -1672,12 +1672,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // sequence. This check has to happen here so that the `MemberAccessExprSyntax.name` is // available. if base.is(IdentifierExprSyntax.self) { - return memberAccessExpr.name.lastToken + return memberAccessExpr.name.lastToken(viewMode: .sourceAccurate) } return findTryAwaitExprConnectingToken(inExpr: base) } if expr.is(IdentifierExprSyntax.self) { - return expr.lastToken + return expr.lastToken(viewMode: .sourceAccurate) } return nil } @@ -1687,7 +1687,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: AttributeSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) switch node.argument { case .argumentList(let argumentList)?: if let leftParen = node.leftParen, let rightParen = node.rightParen { @@ -1707,7 +1707,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { case nil: break } - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } @@ -1719,24 +1719,24 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AvailabilityLabeledArgumentSyntax) -> SyntaxVisitorContinueKind { before(node.label, tokens: .open) after(node.colon, tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) - after(node.value.lastToken, tokens: .close) + after(node.value.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } override func visit(_ node: AvailabilityVersionRestrictionSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) after(node.platform, tokens: .break(.continue, size: 1)) - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } override func visit(_ node: ConditionElementSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) if let comma = node.trailingComma { after(comma, tokens: .close, .break(.same)) closingDelimiterTokens.insert(comma) } else { - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -1747,13 +1747,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind { // Import declarations should never be wrapped. - before(node.firstToken, tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: false))) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: false))) arrangeAttributeList(node.attributes) after(node.importTok, tokens: .space) after(node.importKind, tokens: .space) - after(node.lastToken, tokens: .printerControl(kind: .enableBreaking)) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .printerControl(kind: .enableBreaking)) return .visitChildren } @@ -1777,9 +1777,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // that it is glued to the last token of the ternary. let closeScopeToken: TokenSyntax? if let parenExpr = outermostEnclosingNode(from: Syntax(node.secondChoice)) { - closeScopeToken = parenExpr.lastToken + closeScopeToken = parenExpr.lastToken(viewMode: .sourceAccurate) } else { - closeScopeToken = node.secondChoice.lastToken + closeScopeToken = node.secondChoice.lastToken(viewMode: .sourceAccurate) } after(closeScopeToken, tokens: .break(.close(mustBreak: false), size: 0), .close, .close) return .visitChildren @@ -1813,7 +1813,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } before(node.whereKeyword, tokens: wherePrecedingBreak, .open) after(node.whereKeyword, tokens: .break) - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } @@ -1827,7 +1827,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } else { breakOrSpace = .break } - after(node.lastToken, tokens: breakOrSpace) + after(node.lastToken(viewMode: .sourceAccurate), tokens: breakOrSpace) return .visitChildren } @@ -1840,7 +1840,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(asyncOrReasyncKeyword, tokens: .open) after(throwsOrRethrowsKeyword, tokens: .close) } - before(node.output?.firstToken, tokens: .break) + before(node.output?.firstToken(viewMode: .sourceAccurate), tokens: .break) return .visitChildren } @@ -1864,7 +1864,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // If there were spaces in the trailing trivia of the previous token, they would have been // ignored (since this expression would be expected to insert its own preceding breaks). // Preserve that whitespace verbatim for now. - if let previousToken = node.firstToken?.previousToken { + if let previousToken = node.firstToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .sourceAccurate) { appendTrailingTrivia(previousToken, forced: true) } verbatimToken(Syntax(node), indentingBehavior: .none) @@ -1886,7 +1886,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let (unindentingNode, _, breakKind) = stackedIndentationBehavior(after: binOp, rhs: rhs) { beforeTokens = [.break(.open(kind: breakKind))] - after(unindentingNode.lastToken, tokens: [.break(.close(mustBreak: false), size: 0)]) + after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: [.break(.close(mustBreak: false), size: 0)]) } else { beforeTokens = [.break(.continue)] } @@ -1896,10 +1896,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // operator token. if isCompoundExpression(rhs) { beforeTokens.append(.open) - after(rhs.lastToken, tokens: .close) + after(rhs.lastToken(viewMode: .sourceAccurate), tokens: .close) } - after(binOp.lastToken, tokens: beforeTokens) + after(binOp.lastToken(viewMode: .sourceAccurate), tokens: beforeTokens) } else if let (unindentingNode, shouldReset, breakKind) = stackedIndentationBehavior(after: binOp, rhs: rhs) { @@ -1912,27 +1912,27 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // usual single-level "continuation line or not" behavior. let openBreakTokens: [Token] = [.break(.open(kind: breakKind)), .open] if wrapsBeforeOperator { - before(binOp.firstToken, tokens: openBreakTokens) + before(binOp.firstToken(viewMode: .sourceAccurate), tokens: openBreakTokens) } else { - after(binOp.lastToken, tokens: openBreakTokens) + after(binOp.lastToken(viewMode: .sourceAccurate), tokens: openBreakTokens) } let closeBreakTokens: [Token] = (shouldReset ? [.break(.reset, size: 0)] : []) + [.break(.close(mustBreak: false), size: 0), .close] - after(unindentingNode.lastToken, tokens: closeBreakTokens) + after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: closeBreakTokens) } else { if wrapsBeforeOperator { - before(binOp.firstToken, tokens: .break(.continue)) + before(binOp.firstToken(viewMode: .sourceAccurate), tokens: .break(.continue)) } else { - after(binOp.lastToken, tokens: .break(.continue)) + after(binOp.lastToken(viewMode: .sourceAccurate), tokens: .break(.continue)) } } if wrapsBeforeOperator { - after(binOp.lastToken, tokens: .space) + after(binOp.lastToken(viewMode: .sourceAccurate), tokens: .space) } else { - before(binOp.firstToken, tokens: .space) + before(binOp.firstToken(viewMode: .sourceAccurate), tokens: .space) } } @@ -1949,15 +1949,15 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { before(node.asTok, tokens: .break(.continue), .open) - before(node.typeName.firstToken, tokens: .space) - after(node.lastToken, tokens: .close) + before(node.typeName.firstToken(viewMode: .sourceAccurate), tokens: .space) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } override func visit(_ node: IsExprSyntax) -> SyntaxVisitorContinueKind { before(node.isTok, tokens: .break(.continue), .open) - before(node.typeName.firstToken, tokens: .space) - after(node.lastToken, tokens: .close) + before(node.typeName.firstToken(viewMode: .sourceAccurate), tokens: .space) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } @@ -2002,12 +2002,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.bindingKeyword, tokens: .break(.open)) for binding in node.bindings { - before(binding.firstToken, tokens: .open) + before(binding.firstToken(viewMode: .sourceAccurate), tokens: .open) after(binding.trailingComma, tokens: .break(.same)) - after(binding.lastToken, tokens: .close) + after(binding.lastToken(viewMode: .sourceAccurate), tokens: .close) } - after(node.lastToken, tokens: .break(.close, size: 0)) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0)) } return .visitChildren @@ -2026,25 +2026,25 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { typeAnnotation.colon, tokens: .break(.open(kind: .continuation), newlines: .elective(ignoresDiscretionary: true))) closesNeeded += 1 - closeAfterToken = typeAnnotation.lastToken + closeAfterToken = typeAnnotation.lastToken(viewMode: .sourceAccurate) } if let initializer = node.initializer { let expr = initializer.value if let (unindentingNode, _, breakKind) = stackedIndentationBehavior(rhs: expr) { after(initializer.equal, tokens: .break(.open(kind: breakKind))) - after(unindentingNode.lastToken, tokens: .break(.close(mustBreak: false), size: 0)) + after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) } else { after(initializer.equal, tokens: .break(.continue)) } - closeAfterToken = initializer.lastToken + closeAfterToken = initializer.lastToken(viewMode: .sourceAccurate) // When the RHS is a simple expression, even if is requires multiple lines, we don't add a // group so that as much of the expression as possible can stay on the same line as the // operator token. if isCompoundExpression(expr) { - before(expr.firstToken, tokens: .open) - after(expr.lastToken, tokens: .close) + before(expr.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(expr.lastToken(viewMode: .sourceAccurate), tokens: .close) } } @@ -2086,8 +2086,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.typealiasKeyword, tokens: .break) if let genericWhereClause = node.genericWhereClause { - before(genericWhereClause.firstToken, tokens: .break(.same), .open) - after(node.lastToken, tokens: .close) + before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -2119,8 +2119,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: TypeAnnotationSyntax) -> SyntaxVisitorContinueKind { - before(node.type.firstToken, tokens: .open) - after(node.type.lastToken, tokens: .close) + before(node.type.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(node.type.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } @@ -2142,11 +2142,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: GenericArgumentSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) if let trailingComma = node.trailingComma { after(trailingComma, tokens: .close, .break(.same)) } else { - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -2164,22 +2164,22 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: GenericParameterSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) after(node.colon, tokens: .break) if let trailingComma = node.trailingComma { after(trailingComma, tokens: .close, .break(.same)) } else { - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } override func visit(_ node: PrimaryAssociatedTypeSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) if let trailingComma = node.trailingComma { after(trailingComma, tokens: .close, .break(.same)) } else { - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -2260,8 +2260,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.associatedtypeKeyword, tokens: .break) if let genericWhereClause = node.genericWhereClause { - before(genericWhereClause.firstToken, tokens: .break(.same), .open) - after(node.lastToken, tokens: .close) + before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -2271,16 +2271,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: GenericWhereClauseSyntax) -> SyntaxVisitorContinueKind { - guard node.whereKeyword != node.lastToken else { + guard node.whereKeyword != node.lastToken(viewMode: .sourceAccurate) else { verbatimToken(Syntax(node)) return .skipChildren } after(node.whereKeyword, tokens: .break(.open)) - after(node.lastToken, tokens: .break(.close, size: 0)) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0)) - before(node.requirementList.firstToken, tokens: .open(genericRequirementListConsistency())) - after(node.requirementList.lastToken, tokens: .close) + before(node.requirementList.firstToken(viewMode: .sourceAccurate), tokens: .open(genericRequirementListConsistency())) + after(node.requirementList.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } @@ -2294,11 +2294,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: GenericRequirementSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) if let trailingComma = node.trailingComma { after(trailingComma, tokens: .close, .break(.same)) } else { - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -2350,8 +2350,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // 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 // breaks if the first item in the list would overflow the column limit. - before(node.inheritedTypeCollection.firstToken, tokens: .open, .break(.open, size: 1)) - after(node.inheritedTypeCollection.lastToken, tokens: .break(.close, size: 0), .close) + before(node.inheritedTypeCollection.firstToken(viewMode: .sourceAccurate), tokens: .open, .break(.open, size: 1)) + after(node.inheritedTypeCollection.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0), .close) return .visitChildren } @@ -2366,9 +2366,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: MatchingPatternConditionSyntax) -> SyntaxVisitorContinueKind { - before(node.firstToken, tokens: .open) + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) after(node.caseKeyword, tokens: .break) - after(node.lastToken, tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } @@ -2379,7 +2379,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after( typeAnnotation.colon, tokens: .break(.open(kind: .continuation), newlines: .elective(ignoresDiscretionary: true))) - after(typeAnnotation.lastToken, tokens: .break(.close(mustBreak: false), size: 0)) + after(typeAnnotation.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) } return .visitChildren @@ -2403,10 +2403,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let whereClause = node.whereClause { if needsBreakBeforeWhereClause { - before(whereClause.firstToken, tokens: .break(.same)) + before(whereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same)) } - before(whereClause.firstToken, tokens: .open) - after(whereClause.lastToken, tokens: .close) + before(whereClause.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(whereClause.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -2588,13 +2588,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { suppressFinalBreak: Bool = false ) { if let attributes = attributes { - before(attributes.firstToken, tokens: .open) + before(attributes.firstToken(viewMode: .sourceAccurate), tokens: .open) insertTokens(.break(.same), betweenElementsOf: attributes) var afterAttributeTokens = [Token.close] if !suppressFinalBreak { afterAttributeTokens.append(.break(.same)) } - after(attributes.lastToken, tokens: afterAttributeTokens) + after(attributes.lastToken(viewMode: .sourceAccurate), tokens: afterAttributeTokens) } } @@ -2860,7 +2860,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func afterTokensForTrailingComment(_ token: TokenSyntax) -> (isLineComment: Bool, tokens: [Token]) { - let nextToken = token.nextToken + let nextToken = token.nextToken(viewMode: .sourceAccurate) guard let trivia = nextToken?.leadingTrivia, let firstPiece = trivia.first else { @@ -3146,7 +3146,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// Returns true if the first token of the given node is an open delimiter that may desire /// special breaking behavior in some cases. private func startsWithOpenDelimiter(_ node: Syntax) -> Bool { - guard let token = node.firstToken else { return false } + guard let token = node.firstToken(viewMode: .sourceAccurate) else { return false } switch token.tokenKind { case .leftBrace, .leftParen, .leftSquareBracket: return true default: return false @@ -3222,8 +3222,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) { switch Syntax(expr).as(SyntaxEnum.self) { case .memberAccessExpr, .subscriptExpr: - before(expr.firstToken, tokens: .open) - after(expr.lastToken, tokens: .close) + before(expr.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(expr.lastToken(viewMode: .sourceAccurate), tokens: .close) default: break } @@ -3235,8 +3235,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if expr.is(FunctionCallExprSyntax.self), let operatorExpr = operatorExpr, !isAssigningOperator(operatorExpr) { - before(expr.firstToken, tokens: .open) - after(expr.lastToken, tokens: .close) + before(expr.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(expr.lastToken(viewMode: .sourceAccurate), tokens: .close) } } @@ -3321,12 +3321,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// alongside the last token of the given node. Any tokens between `node.lastToken` and the /// returned node's `lastToken` are delimiter tokens that shouldn't be preceded by a break. private func outermostEnclosingNode(from node: Syntax) -> Syntax? { - guard let afterToken = node.lastToken?.nextToken(viewMode: .all), closingDelimiterTokens.contains(afterToken) + guard let afterToken = node.lastToken(viewMode: .sourceAccurate)?.nextToken(viewMode: .all), closingDelimiterTokens.contains(afterToken) else { return nil } var parenthesizedExpr = afterToken.parent - while let nextToken = parenthesizedExpr?.lastToken?.nextToken(viewMode: .all), + while let nextToken = parenthesizedExpr?.lastToken(viewMode: .sourceAccurate)?.nextToken(viewMode: .all), closingDelimiterTokens.contains(nextToken), let nextExpr = nextToken.parent { @@ -3463,7 +3463,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // `verbatim` token in order for the first token to be printed with correct indentation. All // following lines in the ignored node are printed as-is with no changes to indentation. var nodeText = node.description - if let firstToken = node.firstToken { + if let firstToken = node.firstToken(viewMode: .sourceAccurate) { extractLeadingTrivia(firstToken) let leadingTriviaText = firstToken.leadingTrivia.reduce(into: "") { $1.write(to: &$0) } nodeText = String(nodeText.dropFirst(leadingTriviaText.count)) @@ -3472,7 +3472,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // The leading trivia of the next token, after the ignored node, may contain content that // belongs with the ignored node. The trivia extraction that is performed for `lastToken` later // excludes that content so it needs to be extracted and added to the token stream here. - if let next = node.lastToken?.nextToken(viewMode: .all), let trivia = next.leadingTrivia.first { + if let next = node.lastToken(viewMode: .sourceAccurate)?.nextToken(viewMode: .all), let trivia = next.leadingTrivia.first { switch trivia { case .lineComment, .blockComment: trivia.write(to: &nodeText) @@ -3531,8 +3531,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { (hasCompoundExpression, _) = insertContextualBreaks(base, isTopLevel: false) } if isTopLevel { - before(expr.firstToken, tokens: .contextualBreakingStart) - after(expr.lastToken, tokens: .contextualBreakingEnd) + before(expr.firstToken(viewMode: .sourceAccurate), tokens: .contextualBreakingStart) + after(expr.lastToken(viewMode: .sourceAccurate), tokens: .contextualBreakingEnd) } return (hasCompoundExpression, true) } else if let callingExpr = expr.asProtocol(CallingExprSyntaxProtocol.self) { @@ -3557,14 +3557,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } before(calledMemberAccessExpr.dot, tokens: beforeTokens) - after(expr.lastToken, tokens: afterTokens) + after(expr.lastToken(viewMode: .sourceAccurate), tokens: afterTokens) if isTopLevel { - before(expr.firstToken, tokens: .contextualBreakingStart) - after(expr.lastToken, tokens: .contextualBreakingEnd) + before(expr.firstToken(viewMode: .sourceAccurate), tokens: .contextualBreakingStart) + after(expr.lastToken(viewMode: .sourceAccurate), tokens: .contextualBreakingEnd) } } else { - before(expr.firstToken, tokens: beforeTokens) - after(expr.lastToken, tokens: afterTokens) + before(expr.firstToken(viewMode: .sourceAccurate), tokens: beforeTokens) + after(expr.lastToken(viewMode: .sourceAccurate), tokens: afterTokens) } return (true, hasMemberAccess) } @@ -3572,8 +3572,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Otherwise, it's an expression that isn't calling another expression (e.g. array or // dictionary, identifier, etc.). Wrap it in a breaking context but don't try to pre-visit // children nodes. - before(expr.firstToken, tokens: .contextualBreakingStart) - after(expr.lastToken, tokens: .contextualBreakingEnd) + before(expr.firstToken(viewMode: .sourceAccurate), tokens: .contextualBreakingStart) + after(expr.lastToken(viewMode: .sourceAccurate), tokens: .contextualBreakingEnd) let hasCompoundExpression = !expr.is(IdentifierExprSyntax.self) return (hasCompoundExpression, false) } @@ -3767,7 +3767,7 @@ fileprivate func isFormatterIgnorePresent(inTrivia trivia: Trivia, isWholeFile: fileprivate func shouldFormatterIgnore(node: Syntax) -> Bool { // Regardless of the level of nesting, if the ignore directive is present on the first token // contained within the node then the entire node is eligible for ignoring. - if let firstTrivia = node.firstToken?.leadingTrivia { + if let firstTrivia = node.firstToken(viewMode: .sourceAccurate)?.leadingTrivia { return isFormatterIgnorePresent(inTrivia: firstTrivia, isWholeFile: false) } return false @@ -3779,7 +3779,7 @@ fileprivate func shouldFormatterIgnore(node: Syntax) -> Bool { /// /// - Parameter file: The root syntax node for a source file. fileprivate func shouldFormatterIgnore(file: SourceFileSyntax) -> Bool { - if let firstTrivia = file.firstToken?.leadingTrivia { + if let firstTrivia = file.firstToken(viewMode: .sourceAccurate)?.leadingTrivia { return isFormatterIgnorePresent(inTrivia: firstTrivia, isWholeFile: true) } return false diff --git a/Sources/SwiftFormatRules/AddModifierRewriter.swift b/Sources/SwiftFormatRules/AddModifierRewriter.swift index 4f10b4c85..f58a4472e 100644 --- a/Sources/SwiftFormatRules/AddModifierRewriter.swift +++ b/Sources/SwiftFormatRules/AddModifierRewriter.swift @@ -166,18 +166,18 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { for modifiersProvider: (NodeType) -> ModifierListSyntax? ) -> NodeType { guard let modifier = modifiersProvider(node)?.firstAndOnly, - let movingLeadingTrivia = modifier.nextToken?.leadingTrivia + let movingLeadingTrivia = modifier.nextToken(viewMode: .sourceAccurate)?.leadingTrivia else { // Otherwise, there's no trivia that needs to be relocated so the node is fine. return node } let nodeWithTrivia = replaceTrivia( on: node, - token: modifier.firstToken, + token: modifier.firstToken(viewMode: .sourceAccurate), leadingTrivia: movingLeadingTrivia) return replaceTrivia( on: nodeWithTrivia, - token: modifiersProvider(nodeWithTrivia)?.first?.nextToken, + token: modifiersProvider(nodeWithTrivia)?.first?.nextToken(viewMode: .sourceAccurate), leadingTrivia: []) } } diff --git a/Sources/SwiftFormatRules/DeclSyntaxProtocol+Comments.swift b/Sources/SwiftFormatRules/DeclSyntaxProtocol+Comments.swift index a496c92c8..b695ed7fe 100644 --- a/Sources/SwiftFormatRules/DeclSyntaxProtocol+Comments.swift +++ b/Sources/SwiftFormatRules/DeclSyntaxProtocol+Comments.swift @@ -15,7 +15,7 @@ import SwiftSyntax extension DeclSyntaxProtocol { /// Searches through the leading trivia of this decl for a documentation comment. var docComment: String? { - guard let tok = firstToken else { return nil } + guard let tok = firstToken(viewMode: .sourceAccurate) else { return nil } var comment = [String]() // We need to skip trivia until we see the first comment. This trivia will include all the diff --git a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift index 0aa080a3b..604ece259 100644 --- a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift @@ -56,7 +56,7 @@ public final class DoNotUseSemicolons: SyntaxFormatRule { defer { newItems[idx] = newItem } // Check if the leading trivia for this statement needs a new line. - if previousHadSemicolon, let firstToken = newItem.firstToken, + if previousHadSemicolon, let firstToken = newItem.firstToken(viewMode: .sourceAccurate), !firstToken.leadingTrivia.containsNewlines { let leadingTrivia = .newlines(1) + firstToken.leadingTrivia diff --git a/Sources/SwiftFormatRules/FullyIndirectEnum.swift b/Sources/SwiftFormatRules/FullyIndirectEnum.swift index aab82ed1f..880445836 100644 --- a/Sources/SwiftFormatRules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormatRules/FullyIndirectEnum.swift @@ -53,14 +53,14 @@ public final class FullyIndirectEnum: SyntaxFormatRule { // If the `indirect` keyword being added would be the first token in the decl, we need to move // the leading trivia from the `enum` keyword to the new modifier to preserve the existing // line breaks/comments/indentation. - let firstTok = node.firstToken! + let firstTok = node.firstToken(viewMode: .sourceAccurate)! let leadingTrivia: Trivia let newEnumDecl: EnumDeclSyntax if firstTok.tokenKind == .keyword(.enum) { leadingTrivia = firstTok.leadingTrivia newEnumDecl = replaceTrivia( - on: node, token: node.firstToken, leadingTrivia: []) + on: node, token: node.firstToken(viewMode: .sourceAccurate), leadingTrivia: []) } else { leadingTrivia = [] newEnumDecl = node @@ -97,7 +97,7 @@ public final class FullyIndirectEnum: SyntaxFormatRule { ) -> EnumCaseDeclSyntax { if let modifiers = unformattedCase.modifiers, let first = modifiers.first { return replaceTrivia( - on: unformattedCase, token: first.firstToken, leadingTrivia: leadingTrivia + on: unformattedCase, token: first.firstToken(viewMode: .sourceAccurate), leadingTrivia: leadingTrivia ) } else { return replaceTrivia( diff --git a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift index eb7cebab9..7a4b82564 100644 --- a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift +++ b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift @@ -72,12 +72,12 @@ extension ModifierListSyntax { if index == 0 { guard formatTrivia else { return inserting(modifier, at: index) } - guard let firstMod = first, let firstTok = firstMod.firstToken else { + guard let firstMod = first, let firstTok = firstMod.firstToken(viewMode: .sourceAccurate) else { return inserting(modifier, at: index) } let formattedMod = replaceTrivia( on: modifier, - token: modifier.firstToken, + token: modifier.firstToken(viewMode: .sourceAccurate), leadingTrivia: firstTok.leadingTrivia) newModifiers[0] = replaceTrivia( on: firstMod, diff --git a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift b/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift index cb5f6dc01..f8966ec2c 100644 --- a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift +++ b/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift @@ -150,7 +150,7 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { // Check for any comments that are inline on the fallthrough statement. Inline comments are // always stored in the next token's leading trivia. - if let nextLeadingTrivia = onlyStatement.nextToken?.leadingTrivia, + if let nextLeadingTrivia = onlyStatement.nextToken(viewMode: .sourceAccurate)?.leadingTrivia, nextLeadingTrivia.prefix(while: { !$0.isNewline }).contains(where: { $0.isComment }) { return false diff --git a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift index 2df45668d..6bc786e98 100644 --- a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift +++ b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift @@ -29,7 +29,7 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { { return super.visit(node) } - guard let name = node.calledExpression.lastToken?.with(\.leadingTrivia, []).with(\.trailingTrivia, []) else { + guard let name = node.calledExpression.lastToken(viewMode: .sourceAccurate)?.with(\.leadingTrivia, []).with(\.trailingTrivia, []) else { return super.visit(node) } @@ -42,7 +42,7 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { } let formattedExp = replaceTrivia( on: rewrittenCalledExpr, - token: rewrittenCalledExpr.lastToken, + token: rewrittenCalledExpr.lastToken(viewMode: .sourceAccurate), trailingTrivia: .spaces(1)) let formattedClosure = visit(trailingClosure).as(ClosureExprSyntax.self) let result = node.with(\.leftParen, nil).with(\.rightParen, nil).with(\.calledExpression, formattedExp) diff --git a/Sources/SwiftFormatRules/NoParensAroundConditions.swift b/Sources/SwiftFormatRules/NoParensAroundConditions.swift index 1ee8678a7..b860b4590 100644 --- a/Sources/SwiftFormatRules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormatRules/NoParensAroundConditions.swift @@ -52,7 +52,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { } return replaceTrivia( on: visitedExpr, - token: visitedExpr.lastToken, + token: visitedExpr.lastToken(viewMode: .sourceAccurate), leadingTrivia: visitedTuple.leftParen.leadingTrivia, trailingTrivia: visitedTuple.rightParen.trailingTrivia ) diff --git a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift b/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift index b21207b74..ff35a1ed1 100644 --- a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift +++ b/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift @@ -163,7 +163,7 @@ private struct VariableDeclSplitter { // lines because the pretty printer will re-indent them correctly; we just // need to ensure that a newline is inserted before new decls. varDecl = replaceTrivia( - on: varDecl, token: varDecl.firstToken, leadingTrivia: .newlines(1)) + on: varDecl, token: varDecl.firstToken(viewMode: .sourceAccurate), leadingTrivia: .newlines(1)) fixedUpTrivia = true } diff --git a/Sources/SwiftFormatRules/OrderedImports.swift b/Sources/SwiftFormatRules/OrderedImports.swift index 187b79673..9b3e9730a 100644 --- a/Sources/SwiftFormatRules/OrderedImports.swift +++ b/Sources/SwiftFormatRules/OrderedImports.swift @@ -362,7 +362,7 @@ fileprivate func convertToCodeBlockItems(lines: [Line]) -> [CodeBlockItemSyntax] output.append( replaceTrivia( on: codeBlockItem, - token: codeBlockItem.firstToken, + token: codeBlockItem.firstToken(viewMode: .sourceAccurate), leadingTrivia: Trivia(pieces: triviaBuffer) ) ) @@ -509,17 +509,17 @@ fileprivate class Line { guard let syntaxNode = syntaxNode else { return nil } switch syntaxNode { case .importCodeBlock(let codeBlock, _): - return codeBlock.firstToken + return codeBlock.firstToken(viewMode: .sourceAccurate) case .nonImportCodeBlocks(let codeBlocks): - return codeBlocks.first?.firstToken + return codeBlocks.first?.firstToken(viewMode: .sourceAccurate) } } /// Returns a `LineType` the represents the type of import from the given import decl. private func importType(of importDecl: ImportDeclSyntax) -> LineType { - if let attr = importDecl.attributes?.firstToken, + if let attr = importDecl.attributes?.firstToken(viewMode: .sourceAccurate), attr.tokenKind == .atSign, - attr.nextToken?.text == "testable" + attr.nextToken(viewMode: .sourceAccurate)?.text == "testable" { return .testableImport } diff --git a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift index b1e29c06e..baa0a8223 100644 --- a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift @@ -105,8 +105,8 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { return SimpleTypeIdentifierSyntax( name: TokenSyntax.identifier( "Void", - leadingTrivia: node.firstToken?.leadingTrivia ?? [], - trailingTrivia: node.lastToken?.trailingTrivia ?? []), + leadingTrivia: node.firstToken(viewMode: .sourceAccurate)?.leadingTrivia ?? [], + trailingTrivia: node.lastToken(viewMode: .sourceAccurate)?.trailingTrivia ?? []), genericArgumentClause: nil) } } diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 8def63c71..e1844b898 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -258,7 +258,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // instead of discarding the comment. wrappedType = replaceTrivia( - on: wrappedType, token: wrappedType.firstToken, leadingTrivia: leadingTrivia) + on: wrappedType, token: wrappedType.firstToken(viewMode: .sourceAccurate), leadingTrivia: leadingTrivia) } let optionalType = OptionalTypeSyntax( @@ -346,7 +346,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // the comment. wrappedTypeExpr = replaceTrivia( - on: wrappedTypeExpr, token: wrappedTypeExpr.firstToken, leadingTrivia: leadingTrivia) + on: wrappedTypeExpr, token: wrappedTypeExpr.firstToken(viewMode: .sourceAccurate), leadingTrivia: leadingTrivia) } return OptionalChainingExprSyntax( @@ -410,7 +410,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { case .optionalType(let optionalType): let result = makeOptionalTypeExpression( wrapping: optionalType.wrappedType, - leadingTrivia: optionalType.firstToken?.leadingTrivia, + leadingTrivia: optionalType.firstToken(viewMode: .sourceAccurate)?.leadingTrivia, questionMark: optionalType.questionMark) return ExprSyntax(result) @@ -493,8 +493,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { -> (leadingTrivia: Trivia, trailingTrivia: Trivia) { return ( - leadingTrivia: node.firstToken?.leadingTrivia ?? [], - trailingTrivia: node.lastToken?.trailingTrivia ?? [] + leadingTrivia: node.firstToken(viewMode: .sourceAccurate)?.leadingTrivia ?? [], + trailingTrivia: node.lastToken(viewMode: .sourceAccurate)?.trailingTrivia ?? [] ) } diff --git a/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift b/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift index d2c981433..4a976541b 100644 --- a/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift +++ b/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift @@ -98,7 +98,7 @@ public final class UseTripleSlashForDocumentationComments: SyntaxFormatRule { return !hasFoundDocComment ? decl : replaceTrivia( on: decl, - token: decl.firstToken, + token: decl.firstToken(viewMode: .sourceAccurate), leadingTrivia: Trivia(pieces: pieces.reversed()) ) } diff --git a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift index 415929be7..25365e629 100644 --- a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift @@ -100,7 +100,7 @@ fileprivate func updateWithWhereCondition( statements: CodeBlockItemListSyntax ) -> ForInStmtSyntax { // Construct a new `where` clause with the condition. - let lastToken = node.sequenceExpr.lastToken + let lastToken = node.sequenceExpr.lastToken(viewMode: .sourceAccurate) var whereLeadingTrivia = Trivia() if lastToken?.trailingTrivia.containsSpaces == false { whereLeadingTrivia = .spaces(1) diff --git a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift index cbf1bab18..c00921e8e 100644 --- a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift @@ -124,7 +124,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { let needsThrowsDesc = throwsOrRethrowsKeyword?.tokenKind == .keyword(.throws) if !needsThrowsDesc && throwsDesc != nil { - diagnose(.removeThrowsComment(funcName: name), on: throwsOrRethrowsKeyword ?? node.firstToken) + diagnose(.removeThrowsComment(funcName: name), on: throwsOrRethrowsKeyword ?? node.firstToken(viewMode: .sourceAccurate)) } else if needsThrowsDesc && throwsDesc == nil { diagnose(.documentErrorsThrown(funcName: name), on: throwsOrRethrowsKeyword) } From 01a08845708c876cd44b016f857dbbdb285982b5 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 12 Apr 2023 18:35:55 -0700 Subject: [PATCH 032/332] Adjustment for SwiftSyntax rename `members` -> `memberBlock` Companion of https://github.com/apple/swift-syntax/pull/1524 --- .../TokenStreamCreator.swift | 18 +++++++++--------- .../AlwaysUseLowerCamelCase.swift | 2 +- .../DontRepeatTypeInStaticProperties.swift | 10 +++++----- .../SwiftFormatRules/FullyIndirectEnum.swift | 6 +++--- .../NoAccessLevelOnExtensionDeclaration.swift | 8 ++++---- Sources/SwiftFormatRules/OneCasePerLine.swift | 6 +++--- .../UseSynthesizedInitializer.swift | 4 ++-- Sources/generate-pipeline/RuleCollector.swift | 4 ++-- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 4a74b9ed6..382e31a44 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -162,7 +162,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { genericParameterOrPrimaryAssociatedTypeClause: node.genericParameterClause.map(Syntax.init), inheritanceClause: node.inheritanceClause, genericWhereClause: node.genericWhereClause, - members: node.members) + memberBlock: node.memberBlock) return .visitChildren } @@ -176,7 +176,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { genericParameterOrPrimaryAssociatedTypeClause: node.genericParameterClause.map(Syntax.init), inheritanceClause: node.inheritanceClause, genericWhereClause: node.genericWhereClause, - members: node.members) + memberBlock: node.memberBlock) return .visitChildren } @@ -190,7 +190,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { genericParameterOrPrimaryAssociatedTypeClause: node.genericParameterClause.map(Syntax.init), inheritanceClause: node.inheritanceClause, genericWhereClause: node.genericWhereClause, - members: node.members) + memberBlock: node.memberBlock) return .visitChildren } @@ -204,7 +204,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { genericParameterOrPrimaryAssociatedTypeClause: node.genericParameters.map(Syntax.init), inheritanceClause: node.inheritanceClause, genericWhereClause: node.genericWhereClause, - members: node.members) + memberBlock: node.memberBlock) return .visitChildren } @@ -219,7 +219,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { node.primaryAssociatedTypeClause.map(Syntax.init), inheritanceClause: node.inheritanceClause, genericWhereClause: node.genericWhereClause, - members: node.members) + memberBlock: node.memberBlock) return .visitChildren } @@ -236,7 +236,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { genericParameterOrPrimaryAssociatedTypeClause: nil, inheritanceClause: node.inheritanceClause, genericWhereClause: node.genericWhereClause, - members: node.members) + memberBlock: node.memberBlock) return .visitChildren } @@ -251,7 +251,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { genericParameterOrPrimaryAssociatedTypeClause: Syntax?, inheritanceClause: TypeInheritanceClauseSyntax?, genericWhereClause: GenericWhereClauseSyntax?, - members: MemberDeclBlockSyntax + memberBlock: MemberDeclBlockSyntax ) { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) @@ -263,11 +263,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(firstTokenAfterAttributes, tokens: .open) after(typeKeyword, tokens: .break) - arrangeBracesAndContents(of: members, contentsKeyPath: \.members) + arrangeBracesAndContents(of: memberBlock, contentsKeyPath: \.members) if let genericWhereClause = genericWhereClause { before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) - after(members.leftBrace, tokens: .close) + after(memberBlock.leftBrace, tokens: .close) } let lastTokenBeforeBrace = inheritanceClause?.colon diff --git a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift index 8be86313b..0c8380c37 100644 --- a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift @@ -31,7 +31,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { guard context.importsXCTest == .importsXCTest else { return .visitChildren } - collectTestMethods(from: node.members.members, into: &testCaseFuncs) + collectTestMethods(from: node.memberBlock.members, into: &testCaseFuncs) return .visitChildren } diff --git a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift index caeab7393..9bb3c4946 100644 --- a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift @@ -23,27 +23,27 @@ import SwiftSyntax public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.members.members, endingWith: node.identifier.text) + diagnoseStaticMembers(node.memberBlock.members, endingWith: node.identifier.text) return .skipChildren } public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.members.members, endingWith: node.identifier.text) + diagnoseStaticMembers(node.memberBlock.members, endingWith: node.identifier.text) return .skipChildren } public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.members.members, endingWith: node.identifier.text) + diagnoseStaticMembers(node.memberBlock.members, endingWith: node.identifier.text) return .skipChildren } public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.members.members, endingWith: node.identifier.text) + diagnoseStaticMembers(node.memberBlock.members, endingWith: node.identifier.text) return .skipChildren } public override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { - let members = node.members.members + let members = node.memberBlock.members switch Syntax(node.extendedType).as(SyntaxEnum.self) { case .simpleTypeIdentifier(let simpleType): diff --git a/Sources/SwiftFormatRules/FullyIndirectEnum.swift b/Sources/SwiftFormatRules/FullyIndirectEnum.swift index 880445836..2853f55ec 100644 --- a/Sources/SwiftFormatRules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormatRules/FullyIndirectEnum.swift @@ -23,7 +23,7 @@ import SwiftSyntax public final class FullyIndirectEnum: SyntaxFormatRule { public override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { - let enumMembers = node.members.members + let enumMembers = node.memberBlock.members guard let enumModifiers = node.modifiers, !enumModifiers.has(modifier: "indirect"), allCasesAreIndirect(in: enumMembers) @@ -70,8 +70,8 @@ public final class FullyIndirectEnum: SyntaxFormatRule { name: TokenSyntax.identifier( "indirect", leadingTrivia: leadingTrivia, trailingTrivia: .spaces(1)), detail: nil) - let newMemberBlock = node.members.with(\.members, MemberDeclListSyntax(newMembers)) - return DeclSyntax(newEnumDecl.addModifier(newModifier).with(\.members, newMemberBlock)) + let newMemberBlock = node.memberBlock.with(\.members, MemberDeclListSyntax(newMembers)) + return DeclSyntax(newEnumDecl.addModifier(newModifier).with(\.memberBlock, newMemberBlock)) } /// Returns a value indicating whether all enum cases in the given list are indirect. diff --git a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift index b8058fd7a..8c1163a09 100644 --- a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift @@ -45,14 +45,14 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { } let newMembers = MemberDeclBlockSyntax( - leftBrace: node.members.leftBrace, - members: addMemberAccessKeywords(memDeclBlock: node.members, keyword: accessKeywordToAdd), - rightBrace: node.members.rightBrace) + leftBrace: node.memberBlock.leftBrace, + members: addMemberAccessKeywords(memDeclBlock: node.memberBlock, keyword: accessKeywordToAdd), + rightBrace: node.memberBlock.rightBrace) let newKeyword = replaceTrivia( on: node.extensionKeyword, token: node.extensionKeyword, leadingTrivia: accessKeyword.leadingTrivia) - let result = node.with(\.members, newMembers) + let result = node.with(\.memberBlock, newMembers) .with(\.modifiers, modifiers.remove(name: accessKeyword.name.text)) .with(\.extensionKeyword, newKeyword) return DeclSyntax(result) diff --git a/Sources/SwiftFormatRules/OneCasePerLine.swift b/Sources/SwiftFormatRules/OneCasePerLine.swift index c30c1a8a1..d97210ef0 100644 --- a/Sources/SwiftFormatRules/OneCasePerLine.swift +++ b/Sources/SwiftFormatRules/OneCasePerLine.swift @@ -87,7 +87,7 @@ public final class OneCasePerLine: SyntaxFormatRule { public override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { var newMembers: [MemberDeclListItemSyntax] = [] - for member in node.members.members { + for member in node.memberBlock.members { // If it's not a case declaration, or it's a case declaration with only one element, leave it // alone. guard let caseDecl = member.decl.as(EnumCaseDeclSyntax.self), caseDecl.elements.count > 1 else { @@ -123,8 +123,8 @@ public final class OneCasePerLine: SyntaxFormatRule { } } - let newMemberBlock = node.members.with(\.members, MemberDeclListSyntax(newMembers)) - return DeclSyntax(node.with(\.members, newMemberBlock)) + let newMemberBlock = node.memberBlock.with(\.members, MemberDeclListSyntax(newMembers)) + return DeclSyntax(node.with(\.memberBlock, newMemberBlock)) } } diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift index 88b7e7eb1..9a5a654c2 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift @@ -27,7 +27,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { var storedProperties: [VariableDeclSyntax] = [] var initializers: [InitializerDeclSyntax] = [] - for memberItem in node.members.members { + for memberItem in node.memberBlock.members { let member = memberItem.decl // Collect all stored variables into a list if let varDecl = member.as(VariableDeclSyntax.self) { @@ -67,7 +67,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { // The synthesized memberwise initializer(s) are only created when there are no initializers. // If there are other initializers that cannot be replaced by a synthesized memberwise // initializer, then all of the initializers must remain. - let initializersCount = node.members.members.filter { $0.decl.is(InitializerDeclSyntax.self) }.count + let initializersCount = node.memberBlock.members.filter { $0.decl.is(InitializerDeclSyntax.self) }.count if extraneousInitializers.count == initializersCount { extraneousInitializers.forEach { diagnose(.removeRedundantInitializer, on: $0) } } diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index dc5830bcb..6b7db1228 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -88,11 +88,11 @@ final class RuleCollector { if let classDecl = statement.item.as(ClassDeclSyntax.self) { typeName = classDecl.identifier.text - members = classDecl.members.members + members = classDecl.memberBlock.members maybeInheritanceClause = classDecl.inheritanceClause } else if let structDecl = statement.item.as(StructDeclSyntax.self) { typeName = structDecl.identifier.text - members = structDecl.members.members + members = structDecl.memberBlock.members maybeInheritanceClause = structDecl.inheritanceClause } else { return nil From a0c13d469608b78039b91f6fe690e9113812094b Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Fri, 14 Apr 2023 12:18:25 +0200 Subject: [PATCH 033/332] Remove force unwrapping for source location Companion of https://github.com/apple/swift-syntax/pull/1532 --- Sources/SwiftFormatCore/Finding+Convenience.swift | 15 ++------------- Sources/swift-format/Utilities/Diagnostic.swift | 6 +++--- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/Sources/SwiftFormatCore/Finding+Convenience.swift b/Sources/SwiftFormatCore/Finding+Convenience.swift index 78dc2905c..3962c7265 100644 --- a/Sources/SwiftFormatCore/Finding+Convenience.swift +++ b/Sources/SwiftFormatCore/Finding+Convenience.swift @@ -14,18 +14,7 @@ import SwiftSyntax extension Finding.Location { /// Creates a new `Finding.Location` by converting the given `SourceLocation` from `SwiftSyntax`. - /// - /// If the source location is invalid (i.e., any of its fields are nil), then the initializer will - /// return nil. - public init?(_ sourceLocation: SourceLocation) { - guard - let file = sourceLocation.file, - let line = sourceLocation.line, - let column = sourceLocation.column - else { - return nil - } - - self.init(file: file, line: line, column: column) + public init(_ sourceLocation: SourceLocation) { + self.init(file: sourceLocation.file, line: sourceLocation.line, column: sourceLocation.column) } } diff --git a/Sources/swift-format/Utilities/Diagnostic.swift b/Sources/swift-format/Utilities/Diagnostic.swift index 3a80333a9..42305eab9 100644 --- a/Sources/swift-format/Utilities/Diagnostic.swift +++ b/Sources/swift-format/Utilities/Diagnostic.swift @@ -36,9 +36,9 @@ struct Diagnostic { /// Creates a new diagnostic location from the given source location. init(_ sourceLocation: SourceLocation) { - self.file = sourceLocation.file! - self.line = sourceLocation.line! - self.column = sourceLocation.column! + self.file = sourceLocation.file + self.line = sourceLocation.line + self.column = sourceLocation.column } /// Creates a new diagnostic location with the given finding location. From bb2e85d14e14075ff40205685550049220e2afda Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 20 Apr 2023 17:34:18 -0700 Subject: [PATCH 034/332] Change version dependency on `swift-argument-parser` to from `upToNextMinor` to `upToNextMajor` This will make swift-format more tolerant with regard to which swift-argument-parser version it needs, resulting in fewer version conflicts for packages that depend on swift-format. --- Package.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Package.swift b/Package.swift index 68d0d41e9..d5943efe8 100644 --- a/Package.swift +++ b/Package.swift @@ -213,10 +213,8 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { // Building standalone. package.dependencies += [ .package( - url: "https://github.com/apple/swift-argument-parser.git", - // This should be kept in sync with the same dependency used by - // swift-syntax. - .upToNextMinor(from: "1.2.2") + url: "https://github.com/apple/swift-argument-parser.git", + from: "1.2.2" ), .package( url: "https://github.com/apple/swift-syntax.git", From 3b6a0a9d3efd8bb67166b3f0d2f167875f29b99e Mon Sep 17 00:00:00 2001 From: TTOzzi Date: Thu, 25 May 2023 02:10:46 +0900 Subject: [PATCH 035/332] Replace deprecated code of swift-syntax with the latest code to remove warning --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 382e31a44..27a41f90e 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -201,7 +201,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { modifiers: node.modifiers, typeKeyword: node.enumKeyword, identifier: node.identifier, - genericParameterOrPrimaryAssociatedTypeClause: node.genericParameters.map(Syntax.init), + genericParameterOrPrimaryAssociatedTypeClause: node.genericParameterClause.map(Syntax.init), inheritanceClause: node.inheritanceClause, genericWhereClause: node.genericWhereClause, memberBlock: node.memberBlock) @@ -2289,7 +2289,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: AccessPathComponentSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: ImportPathComponentSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } From 55dcf0515a5bd632cb9edbe712e3c5300f7fd389 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 25 May 2023 15:12:14 -0700 Subject: [PATCH 036/332] Fix indentation of multiline strings when part of a larger expression. The stacked indentation behavior wasn't robust enough to catch other kinds of subexpressions that a multiline string could be in and our tests weren't thorough enough to catch this. The most common occurrence was when a multiline string was followed by a dotted member access, which caused the string to become unindented one level from where it should have been. --- .../TokenStreamCreator.swift | 23 ++++++- .../StringTests.swift | 68 +++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 27a41f90e..24d210a11 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -3298,7 +3298,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } /// Walks the expression and returns the leftmost multiline string literal (which might be the - /// expression itself) if the leftmost child is a multiline string literal. + /// expression itself) if the leftmost child is a multiline string literal or if it is a unary + /// operation applied to a multiline string literal. /// /// - Parameter expr: The expression whose leftmost multiline string literal should be returned. /// - Returns: The leftmost multiline string literal, or nil if the leftmost subexpression was @@ -3310,8 +3311,28 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return stringLiteralExpr case .infixOperatorExpr(let infixOperatorExpr): return leftmostMultilineStringLiteral(of: infixOperatorExpr.leftOperand) + case .asExpr(let asExpr): + return leftmostMultilineStringLiteral(of: asExpr.expression) + case .isExpr(let isExpr): + return leftmostMultilineStringLiteral(of: isExpr.expression) + case .forcedValueExpr(let forcedValueExpr): + return leftmostMultilineStringLiteral(of: forcedValueExpr.expression) + case .optionalChainingExpr(let optionalChainingExpr): + return leftmostMultilineStringLiteral(of: optionalChainingExpr.expression) + case .postfixUnaryExpr(let postfixUnaryExpr): + return leftmostMultilineStringLiteral(of: postfixUnaryExpr.expression) + case .prefixOperatorExpr(let prefixOperatorExpr): + return leftmostMultilineStringLiteral(of: prefixOperatorExpr.postfixExpression) case .ternaryExpr(let ternaryExpr): return leftmostMultilineStringLiteral(of: ternaryExpr.conditionExpression) + case .functionCallExpr(let functionCallExpr): + return leftmostMultilineStringLiteral(of: functionCallExpr.calledExpression) + case .subscriptExpr(let subscriptExpr): + return leftmostMultilineStringLiteral(of: subscriptExpr.calledExpression) + case .memberAccessExpr(let memberAccessExpr): + return memberAccessExpr.base.flatMap { leftmostMultilineStringLiteral(of: $0) } + case .postfixIfConfigExpr(let postfixIfConfigExpr): + return postfixIfConfigExpr.base.flatMap { leftmostMultilineStringLiteral(of: $0) } default: return nil } diff --git a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift index 13644ea0d..12129d5fe 100644 --- a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift @@ -327,4 +327,72 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 20) } + + func testLeadingMultilineStringsInOtherExpressions() { + // The stacked indentation behavior needs to drill down into different node types to find the + // leftmost multiline string literal. This makes sure that we cover various cases. + let input = + #""" + let bytes = """ + { + "key": "value" + } + """.utf8.count + let json = """ + { + "key": "value" + } + """.data(using: .utf8) + let slice = """ + { + "key": "value" + } + """[...] + let forceUnwrap = """ + { + "key": "value" + } + """! + let optionalChaining = """ + { + "key": "value" + } + """? + let postfix = """ + { + "key": "value" + } + """^*^ + let prefix = +""" + { + "key": "value" + } + """ + let postfixIf = """ + { + "key": "value" + } + """ + #if FLAG + .someMethod + #endif + + // Like the infix operator cases, cast operations force the string's open quotes to wrap. + // This could be considered consistent if you look at it through the right lens. Let's make + // sure to test it so that we can see if the behavior ever changes accidentally. + let cast = + """ + { + "key": "value" + } + """ as NSString + let typecheck = + """ + { + "key": "value" + } + """ is NSString + """# + assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 100) + } } From 4fb999d84abcf960a1dfd165d7b25d99cc746f24 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 25 May 2023 16:11:26 -0700 Subject: [PATCH 037/332] Fix `try`/`await` expressions in `NoAssignmentInExpressions`. This rule only checked whether the _immediate_ parent of an expression like `x = y` was a `CodeBlockItem`. In cases like `try x = y`, the `try` wraps the entire expression and the `CodeBlockItem` would be the parent of that. So we look through any `TryExpr`s or `AwaitExpr`s we see when searching for the `CodeBlockItem`. --- .../NoAssignmentInExpressions.swift | 29 +++++++++++++++++-- .../NoAssignmentInExpressionsTests.swift | 19 ++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift index e327b59d7..0ae58473e 100644 --- a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift @@ -27,7 +27,7 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { public override func visit(_ node: InfixOperatorExprSyntax) -> ExprSyntax { // Diagnose any assignment that isn't directly a child of a `CodeBlockItem` (which would be the // case if it was its own statement). - if isAssignmentExpression(node) && node.parent?.is(CodeBlockItemSyntax.self) == false { + if isAssignmentExpression(node) && !isStandaloneAssignmentStatement(node) { diagnose(.moveAssignmentToOwnStatement, on: node) } return ExprSyntax(node) @@ -59,7 +59,8 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { item: .expr(ExprSyntax(assignmentExpr)), semicolon: nil ) - .with(\.leadingTrivia, + .with( + \.leadingTrivia, (returnStmt.leadingTrivia) + (assignmentExpr.leadingTrivia)) .with(\.trailingTrivia, [])) newItems.append( @@ -106,6 +107,30 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { return context.operatorTable.infixOperator(named: binaryOp.operatorToken.text)?.precedenceGroup == "AssignmentPrecedence" } + + /// Returns a value indicating whether the given node is a standalone assignment statement. + /// + /// This function considers try/await expressions and automatically walks up through them as + /// needed. This is because `try f().x = y` should still be a standalone assignment for our + /// purposes, even though a `TryExpr` will wrap the `InfixOperatorExpr` and thus would not be + /// considered a standalone assignment if we only checked the infix expression for a + /// `CodeBlockItem` parent. + private func isStandaloneAssignmentStatement(_ node: InfixOperatorExprSyntax) -> Bool { + var node = Syntax(node) + while + let parent = node.parent, + parent.is(TryExprSyntax.self) || parent.is(AwaitExprSyntax.self) + { + node = parent + } + + guard let parent = node.parent else { + // This shouldn't happen under normal circumstances (i.e., unless the expression is detached + // from the rest of a tree). In that case, we may as well consider it to be "standalone". + return true + } + return parent.is(CodeBlockItemSyntax.self) + } } extension Finding.Message { diff --git a/Tests/SwiftFormatRulesTests/NoAssignmentInExpressionsTests.swift b/Tests/SwiftFormatRulesTests/NoAssignmentInExpressionsTests.swift index 238c2f92f..dfeeefbf8 100644 --- a/Tests/SwiftFormatRulesTests/NoAssignmentInExpressionsTests.swift +++ b/Tests/SwiftFormatRulesTests/NoAssignmentInExpressionsTests.swift @@ -161,4 +161,23 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { ) XCTAssertDiagnosed(.moveAssignmentToOwnStatement, line: 2, column: 29) } + + func testTryAndAwaitAssignmentExpressionsAreUnchanged() { + XCTAssertFormatting( + NoAssignmentInExpressions.self, + input: """ + func foo() { + try a.b = c + await a.b = c + } + """, + expected: """ + func foo() { + try a.b = c + await a.b = c + } + """ + ) + XCTAssertNotDiagnosed(.moveAssignmentToOwnStatement) + } } From 02f4db7e1a077804d6a45cd021ef97c8fe0e533b Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Fri, 26 May 2023 11:03:05 -0700 Subject: [PATCH 038/332] Further improve multiline string formatting. These changes remove unnecessary grouping around multiline string literals that were forcing subexpressions to wrap in less than ideal ways. Since multiline strings force hard line breaks after the open quotes, we can remove the grouping and produce better results when complex expressions are involved. For example, ```swift let x = """ abc def """ + """ ghi jkl """ ``` Before this change, we were forcing breaks after the `=` and before the `+`. Now, we only do so if the open quotes would overflow the line. --- .../TokenStreamCreator.swift | 74 ++++++++++++++----- .../StringTests.swift | 38 ++++++++-- 2 files changed, 85 insertions(+), 27 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 24d210a11..0ed166de0 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1883,10 +1883,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // If the rhs starts with a parenthesized expression, stack indentation around it. // Otherwise, use regular continuation breaks. - if let (unindentingNode, _, breakKind) = stackedIndentationBehavior(after: binOp, rhs: rhs) + if let (unindentingNode, _, breakKind, _) = + stackedIndentationBehavior(after: binOp, rhs: rhs) { beforeTokens = [.break(.open(kind: breakKind))] - after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: [.break(.close(mustBreak: false), size: 0)]) + after( + unindentingNode.lastToken(viewMode: .sourceAccurate), + tokens: [.break(.close(mustBreak: false), size: 0)]) } else { beforeTokens = [.break(.continue)] } @@ -1894,13 +1897,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // When the RHS is a simple expression, even if is requires multiple lines, we don't add a // group so that as much of the expression as possible can stay on the same line as the // operator token. - if isCompoundExpression(rhs) { + if isCompoundExpression(rhs) && leftmostMultilineStringLiteral(of: rhs) == nil { beforeTokens.append(.open) after(rhs.lastToken(viewMode: .sourceAccurate), tokens: .close) } after(binOp.lastToken(viewMode: .sourceAccurate), tokens: beforeTokens) - } else if let (unindentingNode, shouldReset, breakKind) = + } else if let (unindentingNode, shouldReset, breakKind, shouldGroup) = stackedIndentationBehavior(after: binOp, rhs: rhs) { // For parenthesized expressions and for unparenthesized usages of `&&` and `||`, we don't @@ -1910,16 +1913,22 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // use open-continuation/close pairs around such operators and their right-hand sides so // that the continuation breaks inside those scopes "stack", instead of receiving the // usual single-level "continuation line or not" behavior. - let openBreakTokens: [Token] = [.break(.open(kind: breakKind)), .open] + var openBreakTokens: [Token] = [.break(.open(kind: breakKind))] + if shouldGroup { + openBreakTokens.append(.open) + } if wrapsBeforeOperator { before(binOp.firstToken(viewMode: .sourceAccurate), tokens: openBreakTokens) } else { after(binOp.lastToken(viewMode: .sourceAccurate), tokens: openBreakTokens) } - let closeBreakTokens: [Token] = + var closeBreakTokens: [Token] = (shouldReset ? [.break(.reset, size: 0)] : []) - + [.break(.close(mustBreak: false), size: 0), .close] + + [.break(.close(mustBreak: false), size: 0)] + if shouldGroup { + closeBreakTokens.append(.close) + } after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: closeBreakTokens) } else { if wrapsBeforeOperator { @@ -2031,7 +2040,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let initializer = node.initializer { let expr = initializer.value - if let (unindentingNode, _, breakKind) = stackedIndentationBehavior(rhs: expr) { + if let (unindentingNode, _, breakKind, _) = stackedIndentationBehavior(rhs: expr) { after(initializer.equal, tokens: .break(.open(kind: breakKind))) after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) } else { @@ -2042,7 +2051,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // When the RHS is a simple expression, even if is requires multiple lines, we don't add a // group so that as much of the expression as possible can stay on the same line as the // operator token. - if isCompoundExpression(expr) { + if isCompoundExpression(expr) && leftmostMultilineStringLiteral(of: expr) == nil { before(expr.firstToken(viewMode: .sourceAccurate), tokens: .open) after(expr.lastToken(viewMode: .sourceAccurate), tokens: .close) } @@ -3357,8 +3366,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } /// Determines if indentation should be stacked around a subexpression to the right of the given - /// operator, and, if so, returns the node after which indentation stacking should be closed and - /// whether or not the continuation state should be reset as well. + /// operator, and, if so, returns the node after which indentation stacking should be closed, + /// whether or not the continuation state should be reset as well, and whether or not a group + /// should be placed around the operator and the expression. /// /// Stacking is applied around parenthesized expressions, but also for low-precedence operators /// that frequently occur in long chains, such as logical AND (`&&`) and OR (`||`) in conditional @@ -3367,7 +3377,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func stackedIndentationBehavior( after operatorExpr: ExprSyntax? = nil, rhs: ExprSyntax - ) -> (unindentingNode: Syntax, shouldReset: Bool, breakKind: OpenBreakKind)? { + ) -> (unindentingNode: Syntax, shouldReset: Bool, breakKind: OpenBreakKind, shouldGroup: Bool)? { // Check for logical operators first, and if it's that kind of operator, stack indentation // around the entire right-hand-side. We have to do this check before checking the RHS for // parentheses because if the user writes something like `... && (foo) > bar || ...`, we don't @@ -3387,9 +3397,18 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // the paren to the last token of `rhs`. if let unindentingParenExpr = outermostEnclosingNode(from: Syntax(rhs)) { return ( - unindentingNode: unindentingParenExpr, shouldReset: true, breakKind: .continuation) + unindentingNode: unindentingParenExpr, + shouldReset: true, + breakKind: .continuation, + shouldGroup: true + ) } - return (unindentingNode: Syntax(rhs), shouldReset: true, breakKind: .continuation) + return ( + unindentingNode: Syntax(rhs), + shouldReset: true, + breakKind: .continuation, + shouldGroup: true + ) } } @@ -3399,8 +3418,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // We don't try to absorb any parens in this case, because the condition of a ternary cannot // be grouped with any exprs outside of the condition. return ( - unindentingNode: Syntax(ternaryExpr.conditionExpression), shouldReset: false, - breakKind: .continuation) + unindentingNode: Syntax(ternaryExpr.conditionExpression), + shouldReset: false, + breakKind: .continuation, + shouldGroup: true + ) } // If the right-hand-side of the operator is or starts with a parenthesized expression, stack @@ -3411,7 +3433,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // paren into the right hand side by unindenting after the final closing paren. This glues the // paren to the last token of `rhs`. if let unindentingParenExpr = outermostEnclosingNode(from: Syntax(rhs)) { - return (unindentingNode: unindentingParenExpr, shouldReset: true, breakKind: .continuation) + return ( + unindentingNode: unindentingParenExpr, + shouldReset: true, + breakKind: .continuation, + shouldGroup: true + ) } if let innerExpr = parenthesizedExpr.elementList.first?.expression, @@ -3423,14 +3450,23 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } return ( - unindentingNode: Syntax(parenthesizedExpr), shouldReset: false, breakKind: .continuation) + unindentingNode: Syntax(parenthesizedExpr), + shouldReset: false, + breakKind: .continuation, + shouldGroup: true + ) } // If the expression is a multiline string that is unparenthesized, create a block-based // indentation scope and have the segments aligned inside it. if let stringLiteralExpr = leftmostMultilineStringLiteral(of: rhs) { pendingMultilineStringBreakKinds[stringLiteralExpr] = .same - return (unindentingNode: Syntax(stringLiteralExpr), shouldReset: false, breakKind: .block) + return ( + unindentingNode: Syntax(stringLiteralExpr), + shouldReset: false, + breakKind: .block, + shouldGroup: false + ) } // Otherwise, don't stack--use regular continuation breaks instead. diff --git a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift index 12129d5fe..271a83606 100644 --- a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift @@ -296,10 +296,35 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 20) } + func testMultilineStringsInExpressionWithNarrowMargins() { + let input = + #""" + x = """ + abcdefg + hijklmn + """ + """ + abcde + hijkl + """ + """# + + let expected = + #""" + x = """ + abcdefg + hijklmn + """ + + """ + abcde + hijkl + """ + + """# + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 9) + } + func testMultilineStringsInExpression() { - // This output could probably be improved, but it's also a fairly unlikely occurrence. The - // important part of this test is that the first string in the expression is indented relative - // to the `let`. let input = #""" let x = """ @@ -313,12 +338,10 @@ final class StringTests: PrettyPrintTestCase { let expected = #""" - let x = - """ + let x = """ this is a multiline string - """ - + """ + """ + """ this is more multiline string """ @@ -327,7 +350,6 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 20) } - func testLeadingMultilineStringsInOtherExpressions() { // The stacked indentation behavior needs to drill down into different node types to find the // leftmost multiline string literal. This makes sure that we cover various cases. From bc445996db8d88b95231669279204a52e872e8b9 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Fri, 26 May 2023 11:36:11 -0700 Subject: [PATCH 039/332] Allow exceptions to `NoAssignmentInExpressions`. This is mostly intended to carve out a really useful exception for testing: `XCTAssertNoThrow(x = try ...)` is quite a bit clearer about intent than `x = try XCTUnwrap(try? ...)` if you need the value `x` later in the test. In an effort to not be *too* specific in this rule, I've opted to make this a configurable list of exceptions that by default contains `XCTAssertNoThrow`. So it only handles function calls right now, but this could be made more flexible in the future. --- .../Configuration.swift | 21 +++++++++++++ .../NoAssignmentInExpressions.swift | 30 +++++++++++++++++- .../NoAssignmentInExpressionsTests.swift | 31 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormatConfiguration/Configuration.swift b/Sources/SwiftFormatConfiguration/Configuration.swift index f698a3982..f5caaaabc 100644 --- a/Sources/SwiftFormatConfiguration/Configuration.swift +++ b/Sources/SwiftFormatConfiguration/Configuration.swift @@ -36,6 +36,7 @@ public struct Configuration: Codable, Equatable { case indentSwitchCaseLabels case rules case spacesAroundRangeFormationOperators + case noAssignmentInExpressions } /// The version of this configuration. @@ -147,6 +148,9 @@ public struct Configuration: Codable, Equatable { /// `...` and `..<`. public var spacesAroundRangeFormationOperators = false + /// Contains exceptions for the `NoAssignmentInExpressions` rule. + public var noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() + /// Constructs a Configuration with all default values. public init() { self.version = highestSupportedConfigurationVersion @@ -208,6 +212,10 @@ public struct Configuration: Codable, Equatable { ?? FileScopedDeclarationPrivacyConfiguration() self.indentSwitchCaseLabels = try container.decodeIfPresent(Bool.self, forKey: .indentSwitchCaseLabels) ?? false + self.noAssignmentInExpressions = + try container.decodeIfPresent( + NoAssignmentInExpressionsConfiguration.self, forKey: .noAssignmentInExpressions) + ?? NoAssignmentInExpressionsConfiguration() // If the `rules` key is not present at all, default it to the built-in set // so that the behavior is the same as if the configuration had been @@ -238,6 +246,7 @@ public struct Configuration: Codable, Equatable { spacesAroundRangeFormationOperators, forKey: .spacesAroundRangeFormationOperators) try container.encode(fileScopedDeclarationPrivacy, forKey: .fileScopedDeclarationPrivacy) try container.encode(indentSwitchCaseLabels, forKey: .indentSwitchCaseLabels) + try container.encode(noAssignmentInExpressions, forKey: .noAssignmentInExpressions) try container.encode(rules, forKey: .rules) } @@ -287,3 +296,15 @@ public struct FileScopedDeclarationPrivacyConfiguration: Codable, Equatable { /// private access. public var accessLevel: AccessLevel = .private } + +/// Configuration for the `NoAssignmentInExpressions` rule. +public struct NoAssignmentInExpressionsConfiguration: Codable, Equatable { + /// A list of function names where assignments are allowed to be embedded in expressions that are + /// passed as parameters to that function. + public var allowedFunctions: [String] = [ + // Allow `XCTAssertNoThrow` because `XCTAssertNoThrow(x = try ...)` is clearer about intent than + // `x = try XCTUnwrap(try? ...)` or force-unwrapped if you need to use the value `x` later on + // in the test. + "XCTAssertNoThrow" + ] +} diff --git a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift index 0ae58473e..7384c5f36 100644 --- a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import SwiftFormatConfiguration import SwiftFormatCore import SwiftSyntax @@ -27,7 +28,10 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { public override func visit(_ node: InfixOperatorExprSyntax) -> ExprSyntax { // Diagnose any assignment that isn't directly a child of a `CodeBlockItem` (which would be the // case if it was its own statement). - if isAssignmentExpression(node) && !isStandaloneAssignmentStatement(node) { + if isAssignmentExpression(node) + && !isStandaloneAssignmentStatement(node) + && !isInAllowedFunction(node) + { diagnose(.moveAssignmentToOwnStatement, on: node) } return ExprSyntax(node) @@ -131,6 +135,30 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { } return parent.is(CodeBlockItemSyntax.self) } + + /// Returns true if the infix operator expression is in the (non-closure) parameters of an allowed + /// function call. + private func isInAllowedFunction(_ node: InfixOperatorExprSyntax) -> Bool { + let allowedFunctions = context.configuration.noAssignmentInExpressions.allowedFunctions + // Walk up the tree until we find a FunctionCallExprSyntax, and if the name matches, return + // true. However, stop early if we hit a CodeBlockItemSyntax first; this would represent a + // closure context where we *don't* want the exception to apply (for example, in + // `someAllowedFunction(a, b) { return c = d }`, the `c = d` is a descendent of a function call + // but we want it to be evaluated in its own context. + var node = Syntax(node) + while let parent = node.parent { + node = parent + if node.is(CodeBlockItemSyntax.self) { + break + } + if let functionCallExpr = node.as(FunctionCallExprSyntax.self), + allowedFunctions.contains(functionCallExpr.calledExpression.trimmedDescription) + { + return true + } + } + return false + } } extension Finding.Message { diff --git a/Tests/SwiftFormatRulesTests/NoAssignmentInExpressionsTests.swift b/Tests/SwiftFormatRulesTests/NoAssignmentInExpressionsTests.swift index dfeeefbf8..6ba554a0f 100644 --- a/Tests/SwiftFormatRulesTests/NoAssignmentInExpressionsTests.swift +++ b/Tests/SwiftFormatRulesTests/NoAssignmentInExpressionsTests.swift @@ -180,4 +180,35 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { ) XCTAssertNotDiagnosed(.moveAssignmentToOwnStatement) } + + func testAssignmentExpressionsInAllowedFunctions() { + XCTAssertFormatting( + NoAssignmentInExpressions.self, + input: """ + // These should not diagnose. + XCTAssertNoThrow(a = try b()) + XCTAssertNoThrow { a = try b() } + XCTAssertNoThrow({ a = try b() }) + someRegularFunction({ a = b }) + someRegularFunction { a = b } + + // This should be diagnosed. + someRegularFunction(a = b) + """, + expected: """ + // These should not diagnose. + XCTAssertNoThrow(a = try b()) + XCTAssertNoThrow { a = try b() } + XCTAssertNoThrow({ a = try b() }) + someRegularFunction({ a = b }) + someRegularFunction { a = b } + + // This should be diagnosed. + someRegularFunction(a = b) + """ + ) + XCTAssertDiagnosed(.moveAssignmentToOwnStatement, line: 9, column: 21) + // Make sure no other expressions were diagnosed. + XCTAssertNotDiagnosed(.moveAssignmentToOwnStatement) + } } From 5563d44c4c6d671669481246f3e0f421b92da55e Mon Sep 17 00:00:00 2001 From: stevenwong Date: Mon, 29 May 2023 13:12:15 +0800 Subject: [PATCH 040/332] Add `Token.break` after fixity in operator declaration --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 0ed166de0..6638e990e 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1471,6 +1471,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: OperatorDeclSyntax) -> SyntaxVisitorContinueKind { + after(node.fixity, tokens: .break) after(node.operatorKeyword, tokens: .break) return .visitChildren } From aed5f54dec3108e826b872dc55869425d4764ca7 Mon Sep 17 00:00:00 2001 From: stevenwong Date: Thu, 1 Jun 2023 15:46:58 +0800 Subject: [PATCH 041/332] Rename `elementList` in `TupleExprSyntax` to `elements` --- .../TokenStreamCreator.swift | 14 +++++++------- .../NoParensAroundConditions.swift | 12 ++++++------ .../SwiftFormatRules/UseShorthandTypeNames.swift | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 6638e990e..f97497262 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -786,7 +786,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: TupleExprSyntax) -> SyntaxVisitorContinueKind { // We'll do nothing if it's a zero-element tuple, because we just want to keep the empty `()` // together. - let elementCount = node.elementList.count + let elementCount = node.elements.count if elementCount == 1 { // A tuple with one element is a parenthesized expression; add a group around it to keep it @@ -808,9 +808,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.leftParen, tokens: .break(.open, size: 0), .open) before(node.rightParen, tokens: .break(.close, size: 0), .close) - insertTokens(.break(.same), betweenElementsOf: node.elementList) + insertTokens(.break(.same), betweenElementsOf: node.elements) - for element in node.elementList { + for element in node.elements { arrangeAsTupleExprElement(element) } } @@ -3261,8 +3261,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return true case .tryExpr(let tryExpr): return isCompoundExpression(tryExpr.expression) - case .tupleExpr(let tupleExpr) where tupleExpr.elementList.count == 1: - return isCompoundExpression(tupleExpr.elementList.first!.expression) + case .tupleExpr(let tupleExpr) where tupleExpr.elements.count == 1: + return isCompoundExpression(tupleExpr.elements.first!.expression) default: return false } @@ -3296,7 +3296,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// not parenthesized. private func parenthesizedLeftmostExpr(of expr: ExprSyntax) -> TupleExprSyntax? { switch Syntax(expr).as(SyntaxEnum.self) { - case .tupleExpr(let tupleExpr) where tupleExpr.elementList.count == 1: + case .tupleExpr(let tupleExpr) where tupleExpr.elements.count == 1: return tupleExpr case .infixOperatorExpr(let infixOperatorExpr): return parenthesizedLeftmostExpr(of: infixOperatorExpr.leftOperand) @@ -3442,7 +3442,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) } - if let innerExpr = parenthesizedExpr.elementList.first?.expression, + if let innerExpr = parenthesizedExpr.elements.first?.expression, let stringLiteralExpr = innerExpr.as(StringLiteralExprSyntax.self), stringLiteralExpr.openQuote.tokenKind == .multilineStringQuote { diff --git a/Sources/SwiftFormatRules/NoParensAroundConditions.swift b/Sources/SwiftFormatRules/NoParensAroundConditions.swift index b860b4590..3a69c37b4 100644 --- a/Sources/SwiftFormatRules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormatRules/NoParensAroundConditions.swift @@ -27,8 +27,8 @@ import SwiftSyntax /// call with a trailing closure. public final class NoParensAroundConditions: SyntaxFormatRule { private func extractExpr(_ tuple: TupleExprSyntax) -> ExprSyntax { - assert(tuple.elementList.count == 1) - let expr = tuple.elementList.first!.expression + assert(tuple.elements.count == 1) + let expr = tuple.elements.first!.expression // If the condition is a function with a trailing closure or if it's an immediately called // closure, removing the outer set of parentheses introduces a parse ambiguity. @@ -46,7 +46,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { guard let visitedTuple = visit(tuple).as(TupleExprSyntax.self), - let visitedExpr = visitedTuple.elementList.first?.expression + let visitedExpr = visitedTuple.elements.first?.expression else { return expr } @@ -71,7 +71,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { public override func visit(_ node: ConditionElementSyntax) -> ConditionElementSyntax { guard let tup = node.condition.as(TupleExprSyntax.self), - tup.elementList.firstAndOnly != nil + tup.elements.firstAndOnly != nil else { return super.visit(node) } @@ -81,7 +81,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { /// FIXME(hbh): Parsing for SwitchExprSyntax is not implemented. public override func visit(_ node: SwitchExprSyntax) -> ExprSyntax { guard let tup = node.expression.as(TupleExprSyntax.self), - tup.elementList.firstAndOnly != nil + tup.elements.firstAndOnly != nil else { return super.visit(node) } @@ -91,7 +91,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { public override func visit(_ node: RepeatWhileStmtSyntax) -> StmtSyntax { guard let tup = node.condition.as(TupleExprSyntax.self), - tup.elementList.firstAndOnly != nil + tup.elements.firstAndOnly != nil else { return super.visit(node) } diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index e1844b898..95fc0c343 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -335,7 +335,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let tupleExprElementList = TupleExprElementListSyntax([tupleExprElement]) let tupleExpr = TupleExprSyntax( leftParen: TokenSyntax.leftParenToken(leadingTrivia: leadingTrivia ?? []), - elementList: tupleExprElementList, + elements: tupleExprElementList, rightParen: TokenSyntax.rightParenToken()) wrappedTypeExpr = ExprSyntax(tupleExpr) } else { @@ -429,7 +429,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { guard let elementExprs = expressionRepresentation(of: tupleType.elements) else { return nil } let result = TupleExprSyntax( leftParen: tupleType.leftParen, - elementList: elementExprs, + elements: elementExprs, rightParen: tupleType.rightParen) return ExprSyntax(result) @@ -473,7 +473,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let tupleExpr = TupleExprSyntax( leftParen: leftParen, - elementList: argumentTypeExprs, + elements: argumentTypeExprs, rightParen: rightParen) let arrowExpr = ArrowExprSyntax( effectSpecifiers: effectSpecifiers, From dabb463a9efa7b980ab976b7531ad483851164ff Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 5 Jun 2023 10:05:53 -0400 Subject: [PATCH 042/332] Fix `async throws` function types when they appear in an expression context. Also unify the way we handle `async throws` now that they are all contained inside effect specifier nodes that share a common `EffectSpecifiersSyntax` protocol conformance. Fixes #538. --- .../TokenStreamCreator.swift | 72 +++++----- .../ArrayDeclTests.swift | 57 +++++++- .../FunctionTypeTests.swift | 125 +++++++++++++++++- 3 files changed, 214 insertions(+), 40 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index f97497262..1818146a3 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -412,6 +412,21 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: AccessorEffectSpecifiersSyntax) -> SyntaxVisitorContinueKind { + arrangeEffectSpecifiers(node) + return .visitChildren + } + + override func visit(_ node: FunctionEffectSpecifiersSyntax) -> SyntaxVisitorContinueKind { + arrangeEffectSpecifiers(node) + return .visitChildren + } + + override func visit(_ node: TypeEffectSpecifiersSyntax) -> SyntaxVisitorContinueKind { + arrangeEffectSpecifiers(node) + return .visitChildren + } + /// Applies formatting tokens to the tokens in the given function or function-like declaration /// node (e.g., initializers, deinitiailizers, and subscripts). private func arrangeFunctionLikeDecl( @@ -434,6 +449,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) } + /// Arranges the `async` and `throws` effect specifiers of a function or accessor declaration. + private func arrangeEffectSpecifiers(_ node: Node) { + before(node.asyncSpecifier, tokens: .break) + before(node.throwsSpecifier, tokens: .break) + // Keep them together if both `async` and `throws` are present. + if let asyncSpecifier = node.asyncSpecifier, let throwsSpecifier = node.throwsSpecifier { + before(asyncSpecifier, tokens: .open) + after(throwsSpecifier, tokens: .close) + } + } + // MARK: - Property and subscript accessor block nodes override func visit(_ node: AccessorListSyntax) -> SyntaxVisitorContinueKind { @@ -449,22 +475,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AccessorDeclSyntax) -> SyntaxVisitorContinueKind { arrangeAttributeList(node.attributes) - - if let asyncKeyword = node.effectSpecifiers?.asyncSpecifier { - if node.effectSpecifiers?.throwsSpecifier != nil { - before(asyncKeyword, tokens: .break, .open) - } else { - before(asyncKeyword, tokens: .break) - } - } - - if let throwsKeyword = node.effectSpecifiers?.throwsSpecifier { - before(node.effectSpecifiers?.throwsSpecifier, tokens: .break) - if node.effectSpecifiers?.asyncSpecifier != nil { - after(throwsKeyword, tokens: .close) - } - } - arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) return .visitChildren } @@ -1160,13 +1170,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } - before(node.effectSpecifiers?.asyncSpecifier, tokens: .break) - before(node.effectSpecifiers?.throwsSpecifier, tokens: .break) - if let asyncKeyword = node.effectSpecifiers?.asyncSpecifier, let throwsTok = node.effectSpecifiers?.throwsSpecifier { - before(asyncKeyword, tokens: .open) - after(throwsTok, tokens: .close) - } - before(node.output?.arrow, tokens: .break) after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) before(node.inTok, tokens: .break(.same)) @@ -1607,8 +1610,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: FunctionTypeSyntax) -> SyntaxVisitorContinueKind { after(node.leftParen, tokens: .break(.open, size: 0), .open) before(node.rightParen, tokens: .break(.close, size: 0), .close) - before(node.effectSpecifiers?.asyncSpecifier, tokens: .break) - before(node.effectSpecifiers?.throwsSpecifier, tokens: .break) return .visitChildren } @@ -1833,14 +1834,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: FunctionSignatureSyntax) -> SyntaxVisitorContinueKind { - before(node.effectSpecifiers?.asyncSpecifier, tokens: .break) - before(node.effectSpecifiers?.throwsSpecifier, tokens: .break) - if let asyncOrReasyncKeyword = node.effectSpecifiers?.asyncSpecifier, - let throwsOrRethrowsKeyword = node.effectSpecifiers?.throwsSpecifier - { - before(asyncOrReasyncKeyword, tokens: .open) - after(throwsOrRethrowsKeyword, tokens: .close) - } before(node.output?.firstToken(viewMode: .sourceAccurate), tokens: .break) return .visitChildren } @@ -1873,6 +1866,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } let binOp = node.operatorOperand + if binOp.is(ArrowExprSyntax.self) { + // `ArrowExprSyntax` nodes occur when a function type is written in an expression context; + // for example, `let x = [(Int) throws -> Void]()`. We want to treat those consistently like + // we do other function return clauses and not treat them as regular binary operators, so + // handle that behavior there instead. + return .visitChildren + } + let rhs = node.rightOperand maybeGroupAroundSubexpression(rhs, combiningOperator: binOp) @@ -1986,9 +1987,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ArrowExprSyntax) -> SyntaxVisitorContinueKind { - // The break before the `throws` keyword is inserted at the `InfixOperatorExpr` level so that it - // is placed in the correct relative position to the group surrounding the "operator". - after(node.effectSpecifiers?.throwsSpecifier, tokens: .break) + before(node.arrowToken, tokens: .break) + after(node.arrowToken, tokens: .space) return .visitChildren } diff --git a/Tests/SwiftFormatPrettyPrintTests/ArrayDeclTests.swift b/Tests/SwiftFormatPrettyPrintTests/ArrayDeclTests.swift index c0e8d6576..03b092915 100644 --- a/Tests/SwiftFormatPrettyPrintTests/ArrayDeclTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/ArrayDeclTests.swift @@ -70,17 +70,70 @@ final class ArrayDeclTests: PrettyPrintTestCase { let input = """ let A = [(Int, Double) -> Bool]() + let A = [(Int, Double) async -> Bool]() let A = [(Int, Double) throws -> Bool]() + let A = [(Int, Double) async throws -> Bool]() """ - let expected = + let expected46 = """ let A = [(Int, Double) -> Bool]() + let A = [(Int, Double) async -> Bool]() let A = [(Int, Double) throws -> Bool]() + let A = [(Int, Double) async throws -> Bool]() """ + assertPrettyPrintEqual(input: input, expected: expected46, linelength: 46) - assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + let expected43 = + """ + let A = [(Int, Double) -> Bool]() + let A = [(Int, Double) async -> Bool]() + let A = [(Int, Double) throws -> Bool]() + let A = [ + (Int, Double) async throws -> Bool + ]() + + """ + assertPrettyPrintEqual(input: input, expected: expected43, linelength: 43) + + let expected35 = + """ + let A = [(Int, Double) -> Bool]() + let A = [ + (Int, Double) async -> Bool + ]() + let A = [ + (Int, Double) throws -> Bool + ]() + let A = [ + (Int, Double) async throws + -> Bool + ]() + + """ + assertPrettyPrintEqual(input: input, expected: expected35, linelength: 35) + + let expected27 = + """ + let A = [ + (Int, Double) -> Bool + ]() + let A = [ + (Int, Double) async + -> Bool + ]() + let A = [ + (Int, Double) throws + -> Bool + ]() + let A = [ + (Int, Double) + async throws -> Bool + ]() + + """ + assertPrettyPrintEqual(input: input, expected: expected27, linelength: 27) } func testNoTrailingCommasInTypes() { diff --git a/Tests/SwiftFormatPrettyPrintTests/FunctionTypeTests.swift b/Tests/SwiftFormatPrettyPrintTests/FunctionTypeTests.swift index d3e78ee4c..8ee6370ee 100644 --- a/Tests/SwiftFormatPrettyPrintTests/FunctionTypeTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/FunctionTypeTests.swift @@ -60,6 +60,127 @@ final class FunctionTypeTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 60) } + func testFunctionTypeAsync() { + let input = + """ + func f(g: (_ somevalue: Int) async -> String?) { + let a = 123 + let b = "abc" + } + func f(g: (currentLevel: Int) async -> String?) { + let a = 123 + let b = "abc" + } + func f(g: (currentLevel: inout Int) async -> String?) { + let a = 123 + let b = "abc" + } + func f(g: (variable1: Int, variable2: Double, variable3: Bool) async -> Double) { + let a = 123 + let b = "abc" + } + func f(g: (variable1: Int, variable2: Double, variable3: Bool, variable4: String) async -> Double) { + let a = 123 + let b = "abc" + } + """ + + let expected = + """ + func f(g: (_ somevalue: Int) async -> String?) { + let a = 123 + let b = "abc" + } + func f(g: (currentLevel: Int) async -> String?) { + let a = 123 + let b = "abc" + } + func f(g: (currentLevel: inout Int) async -> String?) { + let a = 123 + let b = "abc" + } + func f( + g: (variable1: Int, variable2: Double, variable3: Bool) async -> + Double + ) { + let a = 123 + let b = "abc" + } + func f( + g: ( + variable1: Int, variable2: Double, variable3: Bool, + variable4: String + ) async -> Double + ) { + let a = 123 + let b = "abc" + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 66) + } + + func testFunctionTypeAsyncThrows() { + let input = + """ + func f(g: (_ somevalue: Int) async throws -> String?) { + let a = 123 + let b = "abc" + } + func f(g: (currentLevel: Int) async throws -> String?) { + let a = 123 + let b = "abc" + } + func f(g: (currentLevel: inout Int) async throws -> String?) { + let a = 123 + let b = "abc" + } + func f(g: (variable1: Int, variable2: Double, variable3: Bool) async throws -> Double) { + let a = 123 + let b = "abc" + } + func f(g: (variable1: Int, variable2: Double, variable3: Bool, variable4: String) async throws -> Double) { + let a = 123 + let b = "abc" + } + """ + + let expected = + """ + func f(g: (_ somevalue: Int) async throws -> String?) { + let a = 123 + let b = "abc" + } + func f(g: (currentLevel: Int) async throws -> String?) { + let a = 123 + let b = "abc" + } + func f(g: (currentLevel: inout Int) async throws -> String?) { + let a = 123 + let b = "abc" + } + func f( + g: (variable1: Int, variable2: Double, variable3: Bool) async throws -> + Double + ) { + let a = 123 + let b = "abc" + } + func f( + g: ( + variable1: Int, variable2: Double, variable3: Bool, variable4: String + ) async throws -> Double + ) { + let a = 123 + let b = "abc" + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 73) + } + func testFunctionTypeThrows() { let input = """ @@ -84,7 +205,7 @@ final class FunctionTypeTests: PrettyPrintTestCase { let b = "abc" } """ - + let expected = """ func f(g: (_ somevalue: Int) throws -> String?) { @@ -117,7 +238,7 @@ final class FunctionTypeTests: PrettyPrintTestCase { } """ - + assertPrettyPrintEqual(input: input, expected: expected, linelength: 67) } From 1139f60905e3be1ef771e04fded366ed86c7fe6e Mon Sep 17 00:00:00 2001 From: TTOzzi Date: Tue, 6 Jun 2023 02:43:46 +0900 Subject: [PATCH 043/332] Replace deprecated code --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 2 +- .../SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift | 6 +++--- Sources/SwiftFormatRules/UseShorthandTypeNames.swift | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index f97497262..f2a3fcfc2 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1751,7 +1751,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.firstToken(viewMode: .sourceAccurate), tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: false))) arrangeAttributeList(node.attributes) - after(node.importTok, tokens: .space) + after(node.importKeyword, tokens: .space) after(node.importKind, tokens: .space) after(node.lastToken(viewMode: .sourceAccurate), tokens: .printerControl(kind: .enableBreaking)) diff --git a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift index baa0a8223..74d47d2bb 100644 --- a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift @@ -39,12 +39,12 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { return super.visit(node) } - // Make sure that function types nested in the argument list are also rewritten (for example, + // Make sure that function types nested in the parameter list are also rewritten (for example, // `(Int -> ()) -> ()` should become `(Int -> Void) -> Void`). - let arguments = visit(node.arguments) + let parameters = visit(node.parameters) let voidKeyword = makeVoidIdentifierType(toReplace: returnType) var rewrittenNode = node - rewrittenNode.arguments = arguments + rewrittenNode.parameters = parameters rewrittenNode.output.returnType = TypeSyntax(voidKeyword) return TypeSyntax(rewrittenNode) } diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 95fc0c343..9b6535173 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -417,7 +417,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { case .functionType(let functionType): let result = makeFunctionTypeExpression( leftParen: functionType.leftParen, - argumentTypes: functionType.arguments, + parameters: functionType.parameters, rightParen: functionType.rightParen, effectSpecifiers: functionType.effectSpecifiers, arrow: functionType.output.arrow, @@ -458,14 +458,14 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { private func makeFunctionTypeExpression( leftParen: TokenSyntax, - argumentTypes: TupleTypeElementListSyntax, + parameters: TupleTypeElementListSyntax, rightParen: TokenSyntax, effectSpecifiers: TypeEffectSpecifiersSyntax?, arrow: TokenSyntax, returnType: TypeSyntax ) -> SequenceExprSyntax? { guard - let argumentTypeExprs = expressionRepresentation(of: argumentTypes), + let parameterExprs = expressionRepresentation(of: parameters), let returnTypeExpr = expressionRepresentation(of: returnType) else { return nil @@ -473,7 +473,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let tupleExpr = TupleExprSyntax( leftParen: leftParen, - elements: argumentTypeExprs, + elements: parameterExprs, rightParen: rightParen) let arrowExpr = ArrowExprSyntax( effectSpecifiers: effectSpecifiers, From 9c41f463a028d764de867dc982c8c83c86eaf50f Mon Sep 17 00:00:00 2001 From: TTOzzi Date: Mon, 19 Jun 2023 01:59:26 +0900 Subject: [PATCH 044/332] Rename 'squareBracket' to 'square' --- .../TokenStreamCreator.swift | 2 +- .../UseShorthandTypeNames.swift | 42 +++++++++---------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index e1b7289f5..528a5d339 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -3158,7 +3158,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func startsWithOpenDelimiter(_ node: Syntax) -> Bool { guard let token = node.firstToken(viewMode: .sourceAccurate) else { return false } switch token.tokenKind { - case .leftBrace, .leftParen, .leftSquareBracket: return true + case .leftBrace, .leftParen, .leftSquare: return true default: return false } } diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 9b6535173..aadbb63f7 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -138,9 +138,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { } let arrayTypeExpr = makeArrayTypeExpression( elementType: typeArgument.argumentType, - leftSquareBracket: TokenSyntax.leftSquareBracketToken(leadingTrivia: leadingTrivia), - rightSquareBracket: - TokenSyntax.rightSquareBracketToken(trailingTrivia: trailingTrivia)) + leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), + rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) newNode = ExprSyntax(arrayTypeExpr) case "Dictionary": @@ -151,10 +150,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let dictTypeExpr = makeDictionaryTypeExpression( keyType: typeArguments.0.argumentType, valueType: typeArguments.1.argumentType, - leftSquareBracket: TokenSyntax.leftSquareBracketToken(leadingTrivia: leadingTrivia), + leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), colon: TokenSyntax.colonToken(trailingTrivia: .spaces(1)), - rightSquareBracket: - TokenSyntax.rightSquareBracketToken(trailingTrivia: trailingTrivia)) + rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) newNode = ExprSyntax(dictTypeExpr) case "Optional": @@ -205,9 +203,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { trailingTrivia: Trivia ) -> TypeSyntax { let result = ArrayTypeSyntax( - leftSquareBracket: TokenSyntax.leftSquareBracketToken(leadingTrivia: leadingTrivia), + leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), elementType: element, - rightSquareBracket: TokenSyntax.rightSquareBracketToken(trailingTrivia: trailingTrivia)) + rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) return TypeSyntax(result) } @@ -220,11 +218,11 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { trailingTrivia: Trivia ) -> TypeSyntax { let result = DictionaryTypeSyntax( - leftSquareBracket: TokenSyntax.leftSquareBracketToken(leadingTrivia: leadingTrivia), + leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), keyType: key, colon: TokenSyntax.colonToken(trailingTrivia: .spaces(1)), valueType: value, - rightSquareBracket: TokenSyntax.rightSquareBracketToken(trailingTrivia: trailingTrivia)) + rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) return TypeSyntax(result) } @@ -272,18 +270,18 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { /// have a valid expression representation. private func makeArrayTypeExpression( elementType: TypeSyntax, - leftSquareBracket: TokenSyntax, - rightSquareBracket: TokenSyntax + leftSquare: TokenSyntax, + rightSquare: TokenSyntax ) -> ArrayExprSyntax? { guard let elementTypeExpr = expressionRepresentation(of: elementType) else { return nil } return ArrayExprSyntax( - leftSquare: leftSquareBracket, + leftSquare: leftSquare, elements: ArrayElementListSyntax([ ArrayElementSyntax(expression: elementTypeExpr, trailingComma: nil), ]), - rightSquare: rightSquareBracket) + rightSquare: rightSquare) } /// Returns a `DictionaryExprSyntax` whose single key/value pair is the expression representations @@ -292,9 +290,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { private func makeDictionaryTypeExpression( keyType: TypeSyntax, valueType: TypeSyntax, - leftSquareBracket: TokenSyntax, + leftSquare: TokenSyntax, colon: TokenSyntax, - rightSquareBracket: TokenSyntax + rightSquare: TokenSyntax ) -> DictionaryExprSyntax? { guard let keyTypeExpr = expressionRepresentation(of: keyType), @@ -310,9 +308,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { trailingComma: nil), ]) return DictionaryExprSyntax( - leftSquare: leftSquareBracket, + leftSquare: leftSquare, content: .elements(dictElementList), - rightSquare: rightSquareBracket) + rightSquare: rightSquare) } /// Returns an `OptionalChainingExprSyntax` whose wrapped expression is the expression @@ -394,17 +392,17 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { case .arrayType(let arrayType): let result = makeArrayTypeExpression( elementType: arrayType.elementType, - leftSquareBracket: arrayType.leftSquareBracket, - rightSquareBracket: arrayType.rightSquareBracket) + leftSquare: arrayType.leftSquare, + rightSquare: arrayType.rightSquare) return ExprSyntax(result) case .dictionaryType(let dictionaryType): let result = makeDictionaryTypeExpression( keyType: dictionaryType.keyType, valueType: dictionaryType.valueType, - leftSquareBracket: dictionaryType.leftSquareBracket, + leftSquare: dictionaryType.leftSquare, colon: dictionaryType.colon, - rightSquareBracket: dictionaryType.rightSquareBracket) + rightSquare: dictionaryType.rightSquare) return ExprSyntax(result) case .optionalType(let optionalType): From a4de41576f4ad91d3d3efc44d000ff2877cf4ef7 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 20 Jun 2023 09:05:12 -0400 Subject: [PATCH 045/332] Wrap keypath literals appropriately. We were previously just letting these get printed verbatim; at some point, this behavior changed and we were getting rid of any breaks the user had put inside them (I observed this in internal code where a keypath was broken across lines; we don't have any test cases covering that). --- .../TokenStreamCreator.swift | 42 +++++----- .../KeyPathExprTests.swift | 78 ++++++++++++++++--- 2 files changed, 92 insertions(+), 28 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index e1b7289f5..b126a1912 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1760,6 +1760,30 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: KeyPathExprSyntax) -> SyntaxVisitorContinueKind { + before(node.backslash, tokens: .open) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) + return .visitChildren + } + + override func visit(_ node: KeyPathComponentSyntax) -> SyntaxVisitorContinueKind { + // If this is the first component (immediately after the backslash), allow a break after the + // slash only if a typename follows it. Do not break in the middle of `\.`. + var breakBeforePeriod = true + if let keyPathComponents = node.parent?.as(KeyPathComponentListSyntax.self), + let keyPathExpr = keyPathComponents.parent?.as(KeyPathExprSyntax.self), + node == keyPathExpr.components.first, keyPathExpr.root == nil + { + breakBeforePeriod = false + } + if breakBeforePeriod { + before(node.period, tokens: .break(.continue, size: 0)) + } + return .visitChildren + } + + override func visit(_ node: KeyPathSubscriptComponentSyntax) -> SyntaxVisitorContinueKind { + after(node.leftBracket, tokens: .break(.open, size: 0), .open) + before(node.rightBracket, tokens: .break(.close, size: 0), .close) return .visitChildren } @@ -1847,24 +1871,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: InfixOperatorExprSyntax) -> SyntaxVisitorContinueKind { - // FIXME: This is a workaround/hack for https://github.com/apple/swift-syntax/issues/928. For - // keypaths like `\.?.foo`, they get represented (after folding) as an infix operator expression - // with an empty keypath, followed by the "binary operator" `.?.`, followed by other - // expressions. We can detect this and treat the whole thing as a verbatim node, which mimics - // what we do today for keypaths (i.e., nothing). - if let keyPathExpr = node.leftOperand.as(KeyPathExprSyntax.self), - keyPathExpr.components.isEmpty - { - // If there were spaces in the trailing trivia of the previous token, they would have been - // ignored (since this expression would be expected to insert its own preceding breaks). - // Preserve that whitespace verbatim for now. - if let previousToken = node.firstToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .sourceAccurate) { - appendTrailingTrivia(previousToken, forced: true) - } - verbatimToken(Syntax(node), indentingBehavior: .none) - return .skipChildren - } - let binOp = node.operatorOperand if binOp.is(ArrowExprSyntax.self) { // `ArrowExprSyntax` nodes occur when a function type is written in an expression context; diff --git a/Tests/SwiftFormatPrettyPrintTests/KeyPathExprTests.swift b/Tests/SwiftFormatPrettyPrintTests/KeyPathExprTests.swift index 77a41f146..e0ab55364 100644 --- a/Tests/SwiftFormatPrettyPrintTests/KeyPathExprTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/KeyPathExprTests.swift @@ -1,5 +1,3 @@ -// TODO: Add more tests and figure out how we want to wrap keypaths. Right now, they just get -// printed without breaks. final class KeyPathExprTests: PrettyPrintTestCase { func testSimple() { let input = @@ -47,7 +45,7 @@ final class KeyPathExprTests: PrettyPrintTestCase { let z = a.map(\.foo!.bar) """# - let expected = + let expected80 = #""" let x = \.foo? let y = \.foo!.bar @@ -55,7 +53,23 @@ final class KeyPathExprTests: PrettyPrintTestCase { """# - assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + assertPrettyPrintEqual(input: input, expected: expected80, linelength: 80) + + let expected11 = + #""" + let x = + \.foo? + let y = + \.foo! + .bar + let z = + a.map( + \.foo! + .bar) + + """# + + assertPrettyPrintEqual(input: input, expected: expected11, linelength: 11) } func testSubscript() { @@ -80,19 +94,63 @@ final class KeyPathExprTests: PrettyPrintTestCase { func testImplicitSelfUnwrap() { let input = #""" - //let x = \.?.foo - //let y = \.?.foo.bar + let x = \.?.foo + let y = \.?.foo.bar let z = a.map(\.?.foo.bar) """# - let expected = + let expected80 = #""" - //let x = \.?.foo - //let y = \.?.foo.bar + let x = \.?.foo + let y = \.?.foo.bar let z = a.map(\.?.foo.bar) """# - assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + assertPrettyPrintEqual(input: input, expected: expected80, linelength: 80) + + let expected11 = + #""" + let x = + \.?.foo + let y = + \.?.foo + .bar + let z = + a.map( + \.?.foo + .bar) + + """# + + assertPrettyPrintEqual(input: input, expected: expected11, linelength: 11) + } + + func testWrapping() { + let input = + #""" + let x = \ReallyLongType.reallyLongProperty.anotherLongProperty + let x = \.reeeeallyLongProperty.anotherLongProperty + let x = \.longProperty.a.b.c[really + long + expression].anotherLongProperty + """# + + let expected = + #""" + let x = + \ReallyLongType + .reallyLongProperty + .anotherLongProperty + let x = + \.reeeeallyLongProperty + .anotherLongProperty + let x = + \.longProperty.a.b.c[ + really + long + + expression + ].anotherLongProperty + + """# + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 23) } } From c6f7a834ab8c9cf0ad269431e792a9b1a1046c64 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 20 Jun 2023 11:20:25 -0400 Subject: [PATCH 046/332] Format `macro` declarations. --- .../TokenStreamCreator.swift | 46 ++ .../MacroDeclTests.swift | 397 ++++++++++++++++++ 2 files changed, 443 insertions(+) create mode 100644 Tests/SwiftFormatPrettyPrintTests/MacroDeclTests.swift diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index e1b7289f5..26d12c460 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -240,6 +240,52 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: MacroDeclSyntax) -> SyntaxVisitorContinueKind { + // Macro declarations have a syntax that combines the best parts of types and functions while + // adding their own unique flavor, so we have to copy and adapt the relevant parts of those + // `arrange*` functions here. + before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) + + arrangeAttributeList(node.attributes) + + let hasArguments = !node.signature.input.parameterList.isEmpty + + // Prioritize keeping ") -> " together. We can only do this if the macro has + // arguments. + if hasArguments && config.prioritizeKeepingFunctionOutputTogether { + // Due to visitation order, the matching .open break is added in ParameterClauseSyntax. + after(node.signature.lastToken(viewMode: .sourceAccurate), tokens: .close) + } + + let mustBreak = node.signature.output != nil || node.definition != nil + arrangeParameterClause(node.signature.input, forcesBreakBeforeRightParen: mustBreak) + + // Prioritize keeping " macro (" together. Also include the ")" if the + // parameter list is empty. + let firstTokenAfterAttributes = + node.modifiers?.firstToken(viewMode: .sourceAccurate) ?? node.macroKeyword + before(firstTokenAfterAttributes, tokens: .open) + after(node.macroKeyword, tokens: .break) + if hasArguments || node.genericParameterClause != nil { + after(node.signature.input.leftParen, tokens: .close) + } else { + after(node.signature.input.rightParen, tokens: .close) + } + + if let genericWhereClause = node.genericWhereClause { + before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) + after(genericWhereClause.lastToken(viewMode: .sourceAccurate), tokens: .close) + } + if let definition = node.definition { + // Start the group *after* the `=` so that it all wraps onto its own line if it doesn't fit. + after(definition.equal, tokens: .open) + after(definition.lastToken(viewMode: .sourceAccurate), tokens: .close) + } + + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) + return .visitChildren + } + /// Applies formatting tokens to the tokens in the given type declaration node (i.e., a class, /// struct, enum, protocol, or extension). private func arrangeTypeDeclBlock( diff --git a/Tests/SwiftFormatPrettyPrintTests/MacroDeclTests.swift b/Tests/SwiftFormatPrettyPrintTests/MacroDeclTests.swift new file mode 100644 index 000000000..0529491c4 --- /dev/null +++ b/Tests/SwiftFormatPrettyPrintTests/MacroDeclTests.swift @@ -0,0 +1,397 @@ +import SwiftFormatConfiguration + +final class MacroDeclTests: PrettyPrintTestCase { + func testBasicMacroDeclarations_noPackArguments() { + let input = + """ + macro myFun(var1: Int, var2: Double) = #externalMacro(module: "Foo", type: "Bar") + macro reallyLongName(var1: Int, var2: Double, var3: Bool) = #externalMacro(module: "Foo", type: "Bar") + macro myFun() = #externalMacro(module: "Foo", type: "Bar") + """ + + let expected = + """ + macro myFun(var1: Int, var2: Double) = + #externalMacro(module: "Foo", type: "Bar") + macro reallyLongName( + var1: Int, + var2: Double, + var3: Bool + ) = #externalMacro(module: "Foo", type: "Bar") + macro myFun() = #externalMacro(module: "Foo", type: "Bar") + + """ + + var config = Configuration() + config.lineBreakBeforeEachArgument = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 58, configuration: config) + } + + func testBasicMacroDeclarations_packArguments() { + let input = + """ + macro myFun(var1: Int, var2: Double) = #externalMacro(module: "Foo", type: "Bar") + macro reallyLongName(var1: Int, var2: Double, var3: Bool) = #externalMacro(module: "Foo", type: "Bar") + macro myFun() = #externalMacro(module: "Foo", type: "Bar") + """ + + let expected = + """ + macro myFun(var1: Int, var2: Double) = + #externalMacro(module: "Foo", type: "Bar") + macro reallyLongName( + var1: Int, var2: Double, var3: Bool + ) = #externalMacro(module: "Foo", type: "Bar") + macro myFun() = #externalMacro(module: "Foo", type: "Bar") + + """ + + var config = Configuration() + config.lineBreakBeforeEachArgument = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 58, configuration: config) + } + + func testMacroDeclReturns() { + let input = + """ + macro myFun(var1: Int, var2: Double) -> Double = #externalMacro(module: "Foo", type: "Bar") + macro reallyLongName(var1: Int, var2: Double, var3: Bool) -> Double = #externalMacro(module: "Foo", type: "Bar") + macro reallyReallyLongName(var1: Int, var2: Double, var3: Bool) -> Double = #externalMacro(module: "Foo", type: "Bar") + macro tupleFunc() -> (one: Int, two: Double, three: Bool, four: String) = #externalMacro(module: "Foo", type: "Bar") + macro memberTypeReallyReallyLongNameFunc() -> Type.InnerMember = #externalMacro(module: "Foo", type: "Bar") + macro tupleMembersReallyLongNameFunc() -> (Type.Inner, Type2.Inner2) = #externalMacro(module: "Foo", type: "Bar") + """ + + let expected = + """ + macro myFun(var1: Int, var2: Double) -> Double = + #externalMacro(module: "Foo", type: "Bar") + macro reallyLongName(var1: Int, var2: Double, var3: Bool) + -> Double = #externalMacro(module: "Foo", type: "Bar") + macro reallyReallyLongName( + var1: Int, var2: Double, var3: Bool + ) -> Double = #externalMacro(module: "Foo", type: "Bar") + macro tupleFunc() -> ( + one: Int, two: Double, three: Bool, four: String + ) = #externalMacro(module: "Foo", type: "Bar") + macro memberTypeReallyReallyLongNameFunc() + -> Type.InnerMember = + #externalMacro(module: "Foo", type: "Bar") + macro tupleMembersReallyLongNameFunc() -> ( + Type.Inner, Type2.Inner2 + ) = #externalMacro(module: "Foo", type: "Bar") + + """ + + var config = Configuration() + config.lineBreakBeforeEachArgument = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 58, configuration: config) + } + + func testMacroGenericParameters_noPackArguments() { + let input = + """ + macro myFun(var1: S, var2: T) = #externalMacro(module: "Foo", type: "Bar") + macro myFun(var1: S) = #externalMacro(module: "Foo", type: "Bar") + macro longerNameFun(var1: ReallyLongTypeName, var2: TypeName) = #externalMacro(module: "Foo", type: "Bar") + """ + + let expected = + """ + macro myFun(var1: S, var2: T) = + #externalMacro(module: "Foo", type: "Bar") + macro myFun(var1: S) = + #externalMacro(module: "Foo", type: "Bar") + macro longerNameFun< + ReallyLongTypeName: Conform, + TypeName + >( + var1: ReallyLongTypeName, + var2: TypeName + ) = + #externalMacro(module: "Foo", type: "Bar") + + """ + + var config = Configuration() + config.lineBreakBeforeEachArgument = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 44, configuration: config) + } + + func testMacroGenericParameters_packArguments() { + let input = + """ + macro myFun(var1: S, var2: T) = #externalMacro(module: "Foo", type: "Bar") + macro myFun(var1: S) = #externalMacro(module: "Foo", type: "Bar") + macro longerNameFun(var1: ReallyLongTypeName, var2: TypeName) = #externalMacro(module: "Foo", type: "Bar") + """ + + let expected = + """ + macro myFun(var1: S, var2: T) = + #externalMacro(module: "Foo", type: "Bar") + macro myFun(var1: S) = + #externalMacro(module: "Foo", type: "Bar") + macro longerNameFun< + ReallyLongTypeName: Conform, TypeName + >( + var1: ReallyLongTypeName, var2: TypeName + ) = + #externalMacro(module: "Foo", type: "Bar") + + """ + + var config = Configuration() + config.lineBreakBeforeEachArgument = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 44, configuration: config) + } + + func testMacroWhereClause() { + let input = + """ + macro index( + of element: Element, in collection: Elements + ) -> Elements.Index? = #externalMacro(module: "Foo", type: "Bar") where Elements.Element == Element + + macro index( + of element: Element, + in collection: Elements + ) -> Elements.Index? = #externalMacro(module: "Foo", type: "Bar") where Elements.Element == Element, Element: Equatable + + macro index( + of element: Element, + in collection: Elements + ) -> Elements.Index? = #externalMacro(module: "Foo", type: "Bar") where Elements.Element == Element, Element: Equatable, Element: ReallyLongProtocolName + """ + + let expected = + """ + macro index( + of element: Element, in collection: Elements + ) -> Elements.Index? = + #externalMacro(module: "Foo", type: "Bar") + where Elements.Element == Element + + macro index( + of element: Element, + in collection: Elements + ) -> Elements.Index? = + #externalMacro(module: "Foo", type: "Bar") + where + Elements.Element == Element, Element: Equatable + + macro index( + of element: Element, + in collection: Elements + ) -> Elements.Index? = + #externalMacro(module: "Foo", type: "Bar") + where + Elements.Element == Element, Element: Equatable, + Element: ReallyLongProtocolName + + """ + + var config = Configuration() + config.lineBreakBeforeEachArgument = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 51, configuration: config) + } + + func testMacroWhereClause_lineBreakBeforeEachGenericRequirement() { + let input = + """ + public macro index( + of element: Element, in collection: Elements + ) -> Elements.Index? = #externalMacro(module: "Foo", type: "Bar") where Elements.Element == Element + + public macro index( + of element: Element, + in collection: Elements + ) -> Elements.Index? = #externalMacro(module: "Foo", type: "Bar") where Elements.Element == Element, Element: Equatable + + public macro index( + of element: Element, + in collection: Elements + ) -> Elements.Index? = #externalMacro(module: "Foo", type: "Bar") where Elements.Element == Element, Element: Equatable, Element: ReallyLongProtocolName + """ + + let expected = + """ + public macro index( + of element: Element, in collection: Elements + ) -> Elements.Index? = + #externalMacro(module: "Foo", type: "Bar") + where Elements.Element == Element + + public macro index( + of element: Element, + in collection: Elements + ) -> Elements.Index? = + #externalMacro(module: "Foo", type: "Bar") + where + Elements.Element == Element, + Element: Equatable + + public macro index( + of element: Element, + in collection: Elements + ) -> Elements.Index? = + #externalMacro(module: "Foo", type: "Bar") + where + Elements.Element == Element, + Element: Equatable, + Element: ReallyLongProtocolName + + """ + + var config = Configuration() + config.lineBreakBeforeEachArgument = false + config.lineBreakBeforeEachGenericRequirement = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) + } + + func testMacroAttributes() { + let input = + """ + @attached(accessor) public macro MyFun() = #externalMacro(module: "Foo", type: "Bar") + @attached(accessor) @attached(memberAttribute) public macro MyFun() = #externalMacro(module: "Foo", type: "Bar") + @attached(accessor) @attached(member, names: named(_storage)) public macro MyFun() = #externalMacro(module: "Foo", type: "Bar") + @attached(accessor) + @attached(member, names: named(_storage)) + public macro MyFun() = #externalMacro(module: "Foo", type: "Bar") + """ + + let expected = + """ + @attached(accessor) public macro MyFun() = + #externalMacro(module: "Foo", type: "Bar") + @attached(accessor) @attached(memberAttribute) public macro MyFun() = + #externalMacro(module: "Foo", type: "Bar") + @attached(accessor) @attached(member, names: named(_storage)) + public macro MyFun() = #externalMacro(module: "Foo", type: "Bar") + @attached(accessor) + @attached(member, names: named(_storage)) + public macro MyFun() = #externalMacro(module: "Foo", type: "Bar") + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 69) + } + + func testMacroDeclWithoutDefinition() { + let input = + """ + macro myFun() + macro myFun(arg1: Int) + macro myFun() -> Int + macro myFun(arg1: Int) + macro myFun(arg1: Int) where T: S + """ + + let expected = + """ + macro myFun() + macro myFun(arg1: Int) + macro myFun() -> Int + macro myFun(arg1: Int) + macro myFun(arg1: Int) where T: S + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 50) + } + + func testBreaksBeforeOrInsideOutput() { + let input = + """ + macro name(_ x: Int) -> R + """ + + let expected = + """ + macro name(_ x: Int) + -> R + + """ + assertPrettyPrintEqual(input: input, expected: expected, linelength: 23) + assertPrettyPrintEqual(input: input, expected: expected, linelength: 24) + assertPrettyPrintEqual(input: input, expected: expected, linelength: 27) + } + + func testBreaksBeforeOrInsideOutput_prioritizingKeepingOutputTogether() { + let input = + """ + macro name(_ x: Int) -> R + """ + + let expected = + """ + macro name( + _ x: Int + ) -> R + + """ + var config = Configuration() + config.prioritizeKeepingFunctionOutputTogether = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 23, configuration: config) + assertPrettyPrintEqual(input: input, expected: expected, linelength: 24, configuration: config) + assertPrettyPrintEqual(input: input, expected: expected, linelength: 27, configuration: config) + } + + func testBreaksBeforeOrInsideOutputWithAttributes() { + let input = + """ + @attached(member) @attached(memberAttribute) + macro name(_ x: Int) -> R + """ + + let expected = + """ + @attached(member) + @attached(memberAttribute) + macro name(_ x: Int) + -> R + + """ + assertPrettyPrintEqual(input: input, expected: expected, linelength: 26) + } + + func testBreaksBeforeOrInsideOutputWithAttributes_prioritizingKeepingOutputTogether() { + let input = + """ + @attached(member) @attached(memberAttribute) + macro name(_ x: Int) -> R + """ + + let expected = + """ + @attached(member) + @attached(memberAttribute) + macro name( + _ x: Int + ) -> R + + """ + var config = Configuration() + config.prioritizeKeepingFunctionOutputTogether = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 26, configuration: config) + } + + func testDoesNotBreakInsideEmptyParens() { + // If the macro name is so long that the parentheses of a no-argument parameter list would + // be pushed past the margin, don't break inside them. + let input = + """ + macro fooBarBaz() + + """ + + let expected = + """ + macro + fooBarBaz() + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 16) + } +} From 3bfed5e0e914f4ce394088f5c417894d79f59288 Mon Sep 17 00:00:00 2001 From: Kim Berninger Date: Tue, 20 Jun 2023 10:56:51 +0200 Subject: [PATCH 047/332] insert space before closures in macro expressions If a freestanding macro expression contains a trailing closure keep a, single space before its opening brace or insert one if none exists. Part of #542 --- .../TokenStreamCreator.swift | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index dfc6817dc..62ca4fe3b 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1296,11 +1296,23 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind { + let arguments = node.argumentList + + // If there is a trailing closure, force the right parenthesis down to the next line so it + // stays with the open curly brace. + let breakBeforeRightParen = + (node.trailingClosure != nil && !isCompactSingleFunctionCallArgument(arguments)) + || mustBreakBeforeClosingDelimiter(of: node, argumentListPath: \.argumentList) + + before( + node.trailingClosure?.leftBrace, + tokens: .break(.same, newlines: .elective(ignoresDiscretionary: true))) + arrangeFunctionCallArgumentList( - node.argumentList, + arguments, leftDelimiter: node.leftParen, rightDelimiter: node.rightParen, - forcesBreakBeforeRightDelimiter: false) + forcesBreakBeforeRightDelimiter: breakBeforeRightParen) return .visitChildren } From 13cb27be9749b806417deea21775d4f16345a00d Mon Sep 17 00:00:00 2001 From: Kim Berninger Date: Tue, 20 Jun 2023 10:57:24 +0200 Subject: [PATCH 048/332] test pretty printing macro expressions Add a missing test case for pretty printing freestanding macro expressions along with some basic tests checking that #542 has been properly resolved in commit 7a43205d552a845de4b8b2b8a53aed3c0c18b4d9. The tests are by far not exhaustive and only cover the bug discovered in issue #542. Closes #542 --- .../MacroCallTests.swift | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 Tests/SwiftFormatPrettyPrintTests/MacroCallTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/MacroCallTests.swift b/Tests/SwiftFormatPrettyPrintTests/MacroCallTests.swift new file mode 100644 index 000000000..413190a41 --- /dev/null +++ b/Tests/SwiftFormatPrettyPrintTests/MacroCallTests.swift @@ -0,0 +1,117 @@ +import SwiftFormatConfiguration + +final class MacroCallTests: PrettyPrintTestCase { + func testNoWhiteSpaceAfterMacroWithoutTrailingClosure() { + let input = + """ + func myFunction() { + print("Currently running \\(#function)") + } + + """ + + let expected = + """ + func myFunction() { + print("Currently running \\(#function)") + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 50) + } + + func testKeepWhiteSpaceBeforeTrailingClosure() { + let input = + """ + #Preview {} + #Preview("MyPreview") { + MyView() + } + let p = #Predicate { $0 == 0 } + """ + + let expected = + """ + #Preview {} + #Preview("MyPreview") { + MyView() + } + let p = #Predicate { $0 == 0 } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40) + } + + func testInsertWhiteSpaceBeforeTrailingClosure() { + let input = + """ + #Preview{} + #Preview("MyPreview"){ + MyView() + } + let p = #Predicate{ $0 == 0 } + """ + + let expected = + """ + #Preview {} + #Preview("MyPreview") { + MyView() + } + let p = #Predicate { $0 == 0 } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40) + } + + func testDiscretionaryLineBreakBeforeTrailingClosure() { + let input = + """ + #Preview("MyPreview") + { + MyView() + } + #Preview( + "MyPreview", traits: .landscapeLeft + ) + { + MyView() + } + #Preview("MyPreview", traits: .landscapeLeft, .sizeThatFitsLayout) + { + MyView() + } + #Preview("MyPreview", traits: .landscapeLeft) { + MyView() + } + """ + + let expected = + """ + #Preview("MyPreview") { + MyView() + } + #Preview( + "MyPreview", traits: .landscapeLeft + ) { + MyView() + } + #Preview( + "MyPreview", traits: .landscapeLeft, + .sizeThatFitsLayout + ) { + MyView() + } + #Preview("MyPreview", traits: .landscapeLeft) + { + MyView() + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } +} From 97dff6691a724ca3bad8e93a62e739c69f96c9cf Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 20 Jun 2023 15:55:41 -0400 Subject: [PATCH 049/332] Delete `UnknownNodeTests`. The nodes these were originally testing don't exist anymore; many of the tests were skipped anyway. --- .../UnknownNodeTests.swift | 93 ------------------- 1 file changed, 93 deletions(-) delete mode 100644 Tests/SwiftFormatPrettyPrintTests/UnknownNodeTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/UnknownNodeTests.swift b/Tests/SwiftFormatPrettyPrintTests/UnknownNodeTests.swift deleted file mode 100644 index 51f725cab..000000000 --- a/Tests/SwiftFormatPrettyPrintTests/UnknownNodeTests.swift +++ /dev/null @@ -1,93 +0,0 @@ -import XCTest - -/// Tests for unknown/malformed nodes that ensure that they are handled as verbatim text so that -/// their internal tokens do not get squashed together. -final class UnknownNodeTests: PrettyPrintTestCase { - func testUnknownDecl() throws { - throw XCTSkip("This is no longer an unknown declaration") - - let input = - """ - struct MyStruct where { - let a = 123 - } - """ - - assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 45) - } - - func testUnknownExpr() throws { - throw XCTSkip("This is no longer an unknown expression") - - let input = - """ - (foo where bar) - """ - - assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 45) - } - - func testUnknownPattern() throws { - let input = - """ - if case * ! = x { - bar() - } - """ - - let expected = - """ - if case - * ! = x - { - bar() - } - - """ - - assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) - } - - func testUnknownStmt() throws { - throw XCTSkip("This is no longer an unknown statement") - - let input = - """ - if foo where { - } - """ - - assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 45) - } - - func testUnknownType() throws { - // This one loses the space after the colon because the break would normally be inserted before - // the first token in the type name. - let input = - """ - let x: where - """ - - let expected = - """ - let x:where - - """ - - assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) - } - - func testNonEmptyTokenList() throws { - // The C++ parse modeled as a non-empty list of unparsed tokens. The Swift - // parser sees through this and treats it as an attribute with a missing - // name and some unexpected text after `foo!` in the arguments. - throw XCTSkip("This is no longer a non-empty token list") - - let input = - """ - @(foo ! @ # bar) - """ - - assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 45) - } -} From b9cd9071a6b32f5f989ff848328adc4cfa8e063a Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 26 Jun 2023 08:10:18 -0400 Subject: [PATCH 050/332] Fix formatting of `@backDeploy` attribute. Fixes #549. --- .../TokenStreamCreator.swift | 16 ++++++ .../BackDeployAttributeTests.swift | 53 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 Tests/SwiftFormatPrettyPrintTests/BackDeployAttributeTests.swift diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 7302fb659..d51a9bf78 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1783,6 +1783,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: AvailabilityVersionRestrictionListSyntax) + -> SyntaxVisitorContinueKind + { + insertTokens(.break(.same, size: 1), betweenElementsOf: node) + return .visitChildren + } + override func visit(_ node: AvailabilityVersionRestrictionSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) after(node.platform, tokens: .break(.continue, size: 1)) @@ -1790,6 +1797,15 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: BackDeployedAttributeSpecListSyntax) -> SyntaxVisitorContinueKind { + before( + node.platforms.firstToken(viewMode: .sourceAccurate), + tokens: .break(.open, size: 1), .open(argumentListConsistency())) + after( + node.platforms.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0), .close) + return .visitChildren + } + override func visit(_ node: ConditionElementSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) if let comma = node.trailingComma { diff --git a/Tests/SwiftFormatPrettyPrintTests/BackDeployAttributeTests.swift b/Tests/SwiftFormatPrettyPrintTests/BackDeployAttributeTests.swift new file mode 100644 index 000000000..5deef12bf --- /dev/null +++ b/Tests/SwiftFormatPrettyPrintTests/BackDeployAttributeTests.swift @@ -0,0 +1,53 @@ +final class BackDeployAttributeTests: PrettyPrintTestCase { + func testSpacingAndWrapping() { + let input = + """ + @backDeployed(before:iOS 17) + public func hello() {} + + @backDeployed(before:iOS 17,macOS 14) + public func hello() {} + + @backDeployed(before:iOS 17,macOS 14,tvOS 17) + public func hello() {} + """ + + let expected80 = + """ + @backDeployed(before: iOS 17) + public func hello() {} + + @backDeployed(before: iOS 17, macOS 14) + public func hello() {} + + @backDeployed(before: iOS 17, macOS 14, tvOS 17) + public func hello() {} + + """ + + assertPrettyPrintEqual(input: input, expected: expected80, linelength: 80) + + let expected28 = + """ + @backDeployed( + before: iOS 17 + ) + public func hello() {} + + @backDeployed( + before: iOS 17, macOS 14 + ) + public func hello() {} + + @backDeployed( + before: + iOS 17, macOS 14, + tvOS 17 + ) + public func hello() {} + + """ + + assertPrettyPrintEqual(input: input, expected: expected28, linelength: 28) + } +} From c71c89702e0a4b2fe0d77d78bd701bf97addaa44 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 20 Jun 2023 11:48:33 -0400 Subject: [PATCH 051/332] Downgrade `editor placeholder in source file` from error to warning. Fixes #526. --- Sources/SwiftFormat/Parsing.swift | 39 +++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/Parsing.swift b/Sources/SwiftFormat/Parsing.swift index a74b32414..8b7c20adf 100644 --- a/Sources/SwiftFormat/Parsing.swift +++ b/Sources/SwiftFormat/Parsing.swift @@ -43,18 +43,53 @@ func parseAndEmitDiagnostics( operatorTable.foldAll(Parser.parse(source: source)) { _ in }.as(SourceFileSyntax.self)! let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: sourceFile) + var hasErrors = false if let parsingDiagnosticHandler = parsingDiagnosticHandler { let expectedConverter = SourceLocationConverter(file: url?.path ?? "", tree: sourceFile) for diagnostic in diagnostics { let location = diagnostic.location(converter: expectedConverter) - parsingDiagnosticHandler(diagnostic, location) + + // Downgrade editor placeholders to warnings, because it is useful to support formatting + // in-progress files that contain those. + if diagnostic.diagnosticID == StaticTokenError.editorPlaceholder.diagnosticID { + parsingDiagnosticHandler(downgradedToWarning(diagnostic), location) + } else { + parsingDiagnosticHandler(diagnostic, location) + hasErrors = true + } } } - guard diagnostics.isEmpty else { + guard !hasErrors else { throw SwiftFormatError.fileContainsInvalidSyntax } return restoringLegacyTriviaBehavior(sourceFile) } + +// Wraps a `DiagnosticMessage` but forces its severity to be that of a warning instead of an error. +struct DowngradedDiagnosticMessage: DiagnosticMessage { + var originalDiagnostic: DiagnosticMessage + + var message: String { originalDiagnostic.message } + + var diagnosticID: SwiftDiagnostics.MessageID { originalDiagnostic.diagnosticID } + + var severity: DiagnosticSeverity { .warning } +} + +/// Returns a new `Diagnostic` that is identical to the given diagnostic, except that its severity +/// has been downgraded to a warning. +func downgradedToWarning(_ diagnostic: Diagnostic) -> Diagnostic { + // `Diagnostic` is immutable, so create a new one with the same values except for the + // severity-downgraded message. + return Diagnostic( + node: diagnostic.node, + position: diagnostic.position, + message: DowngradedDiagnosticMessage(originalDiagnostic: diagnostic.diagMessage), + highlights: diagnostic.highlights, + notes: diagnostic.notes, + fixIts: diagnostic.fixIts + ) +} From 4ddd702d854581b716b1531f59449bb386ff9143 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 26 Jun 2023 09:17:01 -0400 Subject: [PATCH 052/332] Don't insert an extra break inside empty multiline strings. --- .../TokenStreamCreator.swift | 4 +- .../StringTests.swift | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 7302fb659..807214d16 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -2287,7 +2287,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Looks up the correct break kind based on prior context. let breakKind = pendingMultilineStringBreakKinds[node, default: .same] after(node.openQuote, tokens: .break(breakKind, size: 0, newlines: .hard(count: 1))) - before(node.closeQuote, tokens: .break(breakKind, newlines: .hard(count: 1))) + if !node.segments.isEmpty { + before(node.closeQuote, tokens: .break(breakKind, newlines: .hard(count: 1))) + } } return .visitChildren } diff --git a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift index 271a83606..c31572648 100644 --- a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift @@ -350,6 +350,7 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 20) } + func testLeadingMultilineStringsInOtherExpressions() { // The stacked indentation behavior needs to drill down into different node types to find the // leftmost multiline string literal. This makes sure that we cover various cases. @@ -417,4 +418,44 @@ final class StringTests: PrettyPrintTestCase { """# assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 100) } + + func testEmptyMultilineStrings() { + let input = + ##""" + let x = """ + """ + let y = + """ + """ + let x = #""" + """# + let y = + #""" + """# + """## + + assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 20) + } + + func testOnlyBlankLinesMultilineStrings() { + let input = + ##""" + let x = """ + + """ + let y = + """ + + """ + let x = #""" + + """# + let y = + #""" + + """# + """## + + assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 20) + } } From 0a9b7c79c7dc0e14e85aa3646c07135293018708 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 27 Jun 2023 13:23:42 -0400 Subject: [PATCH 053/332] Improve wrapping of if/switch expressions. Also fix up multiline string expressions when they are used in an optional binding condition. --- .../TokenStreamCreator.swift | 128 +++++++++++++----- .../IfStmtTests.swift | 48 ++++++- .../StringTests.swift | 27 ++++ .../SwitchStmtTests.swift | 40 ++++-- 4 files changed, 195 insertions(+), 48 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 7ff4dcf66..8ab28b487 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1965,13 +1965,18 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // If the rhs starts with a parenthesized expression, stack indentation around it. // Otherwise, use regular continuation breaks. - if let (unindentingNode, _, breakKind, _) = + if let (unindentingNode, _, breakKind, shouldGroup) = stackedIndentationBehavior(after: binOp, rhs: rhs) { beforeTokens = [.break(.open(kind: breakKind))] + var afterTokens: [Token] = [.break(.close(mustBreak: false), size: 0)] + if shouldGroup { + beforeTokens.append(.open) + afterTokens.append(.close) + } after( unindentingNode.lastToken(viewMode: .sourceAccurate), - tokens: [.break(.close(mustBreak: false), size: 0)]) + tokens: afterTokens) } else { beforeTokens = [.break(.continue)] } @@ -2121,9 +2126,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let initializer = node.initializer { let expr = initializer.value - if let (unindentingNode, _, breakKind, _) = stackedIndentationBehavior(rhs: expr) { - after(initializer.equal, tokens: .break(.open(kind: breakKind))) - after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) + if let (unindentingNode, _, breakKind, shouldGroup) = stackedIndentationBehavior(rhs: expr) { + var openTokens: [Token] = [.break(.open(kind: breakKind))] + if shouldGroup { + openTokens.append(.open) + } + after(initializer.equal, tokens: openTokens) + var closeTokens: [Token] = [.break(.close(mustBreak: false), size: 0)] + if shouldGroup { + closeTokens.append(.close) + } + after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: closeTokens) } else { after(initializer.equal, tokens: .break(.continue)) } @@ -2290,9 +2303,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: InitializerClauseSyntax) -> SyntaxVisitorContinueKind { before(node.equal, tokens: .space) - // InitializerClauses that are children of a PatternBindingSyntax are already handled in the - // latter node, to ensure that continuations stack appropriately. - if node.parent == nil || !node.parent!.is(PatternBindingSyntax.self) { + // InitializerClauses that are children of a PatternBindingSyntax or + // OptionalBindingConditionSyntax are already handled in the latter node, to ensure that + // continuations stack appropriately. + if let parent = node.parent, + !parent.is(PatternBindingSyntax.self) + && !parent.is(OptionalBindingConditionSyntax.self) + { after(node.equal, tokens: .break) } return .visitChildren @@ -2474,6 +2491,26 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(typeAnnotation.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) } + if let initializer = node.initializer { + if let (unindentingNode, _, breakKind, shouldGroup) = + stackedIndentationBehavior(rhs: initializer.value) + { + var openTokens: [Token] = [.break(.open(kind: breakKind))] + if shouldGroup { + openTokens.append(.open) + } + after(initializer.equal, tokens: openTokens) + + var closeTokens: [Token] = [.break(.close(mustBreak: false), size: 0)] + if shouldGroup { + closeTokens.append(.close) + } + after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: closeTokens) + } else { + after(initializer.equal, tokens: .break(.continue)) + } + } + return .visitChildren } @@ -3389,47 +3426,63 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } - /// Walks the expression and returns the leftmost multiline string literal (which might be the - /// expression itself) if the leftmost child is a multiline string literal or if it is a unary - /// operation applied to a multiline string literal. + /// Walks the expression and returns the leftmost subexpression (which might be the expression + /// itself) if the leftmost child is a node of the given type or if it is a unary operation + /// applied to a node of the given type. /// - /// - Parameter expr: The expression whose leftmost multiline string literal should be returned. - /// - Returns: The leftmost multiline string literal, or nil if the leftmost subexpression was - /// not a multiline string literal. - private func leftmostMultilineStringLiteral(of expr: ExprSyntax) -> StringLiteralExprSyntax? { + /// - Parameter expr: The expression whose leftmost matching subexpression should be returned. + /// - Returns: The leftmost subexpression, or nil if the leftmost subexpression was not the + /// desired type. + private func leftmostExpr( + of expr: ExprSyntax, + ifMatching predicate: (ExprSyntax) -> Bool + ) -> ExprSyntax? { + if predicate(expr) { + return expr + } switch Syntax(expr).as(SyntaxEnum.self) { - case .stringLiteralExpr(let stringLiteralExpr) - where stringLiteralExpr.openQuote.tokenKind == .multilineStringQuote: - return stringLiteralExpr case .infixOperatorExpr(let infixOperatorExpr): - return leftmostMultilineStringLiteral(of: infixOperatorExpr.leftOperand) + return leftmostExpr(of: infixOperatorExpr.leftOperand, ifMatching: predicate) case .asExpr(let asExpr): - return leftmostMultilineStringLiteral(of: asExpr.expression) + return leftmostExpr(of: asExpr.expression, ifMatching: predicate) case .isExpr(let isExpr): - return leftmostMultilineStringLiteral(of: isExpr.expression) + return leftmostExpr(of: isExpr.expression, ifMatching: predicate) case .forcedValueExpr(let forcedValueExpr): - return leftmostMultilineStringLiteral(of: forcedValueExpr.expression) + return leftmostExpr(of: forcedValueExpr.expression, ifMatching: predicate) case .optionalChainingExpr(let optionalChainingExpr): - return leftmostMultilineStringLiteral(of: optionalChainingExpr.expression) + return leftmostExpr(of: optionalChainingExpr.expression, ifMatching: predicate) case .postfixUnaryExpr(let postfixUnaryExpr): - return leftmostMultilineStringLiteral(of: postfixUnaryExpr.expression) + return leftmostExpr(of: postfixUnaryExpr.expression, ifMatching: predicate) case .prefixOperatorExpr(let prefixOperatorExpr): - return leftmostMultilineStringLiteral(of: prefixOperatorExpr.postfixExpression) + return leftmostExpr(of: prefixOperatorExpr.postfixExpression, ifMatching: predicate) case .ternaryExpr(let ternaryExpr): - return leftmostMultilineStringLiteral(of: ternaryExpr.conditionExpression) + return leftmostExpr(of: ternaryExpr.conditionExpression, ifMatching: predicate) case .functionCallExpr(let functionCallExpr): - return leftmostMultilineStringLiteral(of: functionCallExpr.calledExpression) + return leftmostExpr(of: functionCallExpr.calledExpression, ifMatching: predicate) case .subscriptExpr(let subscriptExpr): - return leftmostMultilineStringLiteral(of: subscriptExpr.calledExpression) + return leftmostExpr(of: subscriptExpr.calledExpression, ifMatching: predicate) case .memberAccessExpr(let memberAccessExpr): - return memberAccessExpr.base.flatMap { leftmostMultilineStringLiteral(of: $0) } + return memberAccessExpr.base.flatMap { leftmostExpr(of: $0, ifMatching: predicate) } case .postfixIfConfigExpr(let postfixIfConfigExpr): - return postfixIfConfigExpr.base.flatMap { leftmostMultilineStringLiteral(of: $0) } + return postfixIfConfigExpr.base.flatMap { leftmostExpr(of: $0, ifMatching: predicate) } default: return nil } } + /// Walks the expression and returns the leftmost multiline string literal (which might be the + /// expression itself) if the leftmost child is a multiline string literal or if it is a unary + /// operation applied to a multiline string literal. + /// + /// - Parameter expr: The expression whose leftmost multiline string literal should be returned. + /// - Returns: The leftmost multiline string literal, or nil if the leftmost subexpression was + /// not a multiline string literal. + private func leftmostMultilineStringLiteral(of expr: ExprSyntax) -> StringLiteralExprSyntax? { + return leftmostExpr(of: expr) { + $0.as(StringLiteralExprSyntax.self)?.openQuote.tokenKind == .multilineStringQuote + }?.as(StringLiteralExprSyntax.self) + } + /// Returns the outermost node enclosing the given node whose closing delimiter(s) must be kept /// alongside the last token of the given node. Any tokens between `node.lastToken` and the /// returned node's `lastToken` are delimiter tokens that shouldn't be preceded by a break. @@ -3520,7 +3573,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { unindentingNode: unindentingParenExpr, shouldReset: true, breakKind: .continuation, - shouldGroup: true + shouldGroup: false ) } @@ -3536,7 +3589,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { unindentingNode: Syntax(parenthesizedExpr), shouldReset: false, breakKind: .continuation, - shouldGroup: true + shouldGroup: false ) } @@ -3552,6 +3605,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) } + if let leftmostExpr = leftmostExpr(of: rhs, ifMatching: { + $0.is(IfExprSyntax.self) || $0.is(SwitchExprSyntax.self) + }) { + return ( + unindentingNode: Syntax(leftmostExpr), + shouldReset: false, + breakKind: .block, + shouldGroup: true + ) + } + // Otherwise, don't stack--use regular continuation breaks instead. return nil } diff --git a/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift b/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift index a67dfc754..c9320884b 100644 --- a/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift @@ -206,13 +206,14 @@ final class IfStmtTests: PrettyPrintTestCase { let expected = """ func foo() -> Int { - let x = if var1 < var2 { - 23 - } else if d < e { - 24 - } else { - 0 - } + let x = + if var1 < var2 { + 23 + } else if d < e { + 24 + } else { + 0 + } return x } @@ -221,6 +222,39 @@ final class IfStmtTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 26) } + func testIfExpression3() { + let input = + """ + let x = if a { b } else { c } + xyzab = if a { b } else { c } + """ + assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 80) + + let expected28 = + """ + let x = + if a { b } else { c } + xyzab = + if a { b } else { c } + + """ + assertPrettyPrintEqual(input: input, expected: expected28, linelength: 28) + + let expected22 = + """ + let x = + if a { b } else { + c + } + xyzab = + if a { b } else { + c + } + + """ + assertPrettyPrintEqual(input: input, expected: expected22, linelength: 22) + } + func testMatchingPatternConditions() { let input = """ diff --git a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift index c31572648..d82aba30c 100644 --- a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift @@ -419,6 +419,33 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 100) } + func testMultilineStringsNestedInAnotherWrappingContext() { + let input = + #""" + guard + let x = """ + blah + blah + """.data(using: .utf8) { + print(x) + } + """# + + let expected = + #""" + guard + let x = """ + blah + blah + """.data(using: .utf8) + { + print(x) + } + + """# + assertPrettyPrintEqual(input: input, expected: expected, linelength: 100) + } + func testEmptyMultilineStrings() { let input = ##""" diff --git a/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift b/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift index 08e07d94e..c2b744a1c 100644 --- a/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift @@ -297,20 +297,42 @@ final class SwitchStmtTests: PrettyPrintTestCase { let expected = """ func foo() -> Int { - let x = switch value1 + value2 + value3 + value4 { - case "a": - 0 - case "b": - 1 - default: - 2 - } + let x = + switch value1 + value2 + value3 + value4 { + case "a": + 0 + case "b": + 1 + default: + 2 + } + return x + } + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 46) + + let expected43 = + """ + func foo() -> Int { + let x = + switch value1 + value2 + value3 + + value4 + { + case "a": + 0 + case "b": + 1 + default: + 2 + } return x } """ - assertPrettyPrintEqual(input: input, expected: expected, linelength: 52) + assertPrettyPrintEqual(input: input, expected: expected43, linelength: 43) } func testUnknownDefault() { From 09e7dd106fb402438171f8fef06f4244b237dc20 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 28 Jun 2023 10:04:14 -0400 Subject: [PATCH 054/332] Fix postfix-`#if` formatting when they come after a closing parenthesis. A previous change caused the following formatting, which is less than ideal: ```swift SomeView( args ) #if os(iOS) .someModifier() #endif ``` In this case, the `#if` block should be at the same identation level as the closing parenthesis, similar to how we handle closing braces (`}`). This change moves the treatment of breaks for postfix-`#if` out of the visitor for `#if` clauses and instead uses the same contextual break previsition we use for other member access expressions. The result handles more cases correctly, and is a simpler, less special-casey implementation. --- .../TokenStreamCreator.swift | 72 ++++++------- .../IfConfigTests.swift | 102 ++++++++++++++---- 2 files changed, 116 insertions(+), 58 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 8ab28b487..cb2fe31db 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -988,7 +988,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: MemberAccessExprSyntax) -> SyntaxVisitorContinueKind { preVisitInsertingContextualBreaks(node) - return .visitChildren } @@ -996,6 +995,15 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { clearContextualBreakState(node) } + override func visit(_ node: PostfixIfConfigExprSyntax) -> SyntaxVisitorContinueKind { + preVisitInsertingContextualBreaks(node) + return .visitChildren + } + + override func visitPost(_ node: PostfixIfConfigExprSyntax) { + clearContextualBreakState(node) + } + override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { preVisitInsertingContextualBreaks(node) @@ -1456,29 +1464,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(tokenToOpenWith.nextToken(viewMode: .all), tokens: .break(breakKindClose, newlines: .soft), .close) } - if isNestedInPostfixIfConfig(node: Syntax(node)) { - let breakToken: Token - let currentIfConfigDecl = node.parent?.parent?.as(IfConfigDeclSyntax.self) - - if let currentIfConfigDecl = currentIfConfigDecl, - let tokenBeforeCurrentIfConfigDecl = currentIfConfigDecl.previousToken(viewMode: .sourceAccurate), - isNestedInIfConfig(node: Syntax(tokenBeforeCurrentIfConfigDecl)) || - tokenBeforeCurrentIfConfigDecl.text == "}" { - breakToken = .break(.reset) - } else { - breakToken = .break - before(currentIfConfigDecl?.poundEndif, tokens: [.break]) - } - + if !isNestedInPostfixIfConfig(node: Syntax(node)), let condition = node.condition { before( - node.firstToken(viewMode: .sourceAccurate), - tokens: [ - .printerControl(kind: .enableBreaking), - breakToken, - ] - ) - } else if let condition = node.condition { - before(condition.firstToken(viewMode: .sourceAccurate), tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: true))) + condition.firstToken(viewMode: .sourceAccurate), + tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: true))) after( condition.lastToken(viewMode: .sourceAccurate), tokens: .printerControl(kind: .enableBreaking), .break(.reset, size: 0)) @@ -3304,7 +3293,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func mustBreakBeforeClosingDelimiter( of expr: T, argumentListPath: KeyPath ) -> Bool { - guard let parent = expr.parent, parent.is(MemberAccessExprSyntax.self) else { return false } + guard + let parent = expr.parent, + parent.is(MemberAccessExprSyntax.self) || parent.is(PostfixIfConfigExprSyntax.self) + else { return false } + let argumentList = expr[keyPath: argumentListPath] // When there's a single compact argument, there is no extra indentation for the argument and @@ -3739,6 +3732,23 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(expr.lastToken(viewMode: .sourceAccurate), tokens: .contextualBreakingEnd) } return (hasCompoundExpression, true) + } else if let postfixIfExpr = expr.as(PostfixIfConfigExprSyntax.self), + let base = postfixIfExpr.base + { + // For postfix-if expressions with bases (i.e., they aren't the first `#if` nested inside + // another `#if`), add contextual breaks before the top-level clauses (and the terminating + // `#endif`) so that they nest or line-up properly based on the preceding node. We don't do + // this for initial nested `#if`s because they will already get open/close breaks to control + // their indentation from their parent clause. + before(postfixIfExpr.firstToken(viewMode: .sourceAccurate), tokens: .contextualBreakingStart) + after(postfixIfExpr.lastToken(viewMode: .sourceAccurate), tokens: .contextualBreakingEnd) + + for clause in postfixIfExpr.config.clauses { + before(clause.poundKeyword, tokens: .break(.contextual, size: 0)) + } + before(postfixIfExpr.config.poundEndif, tokens: .break(.contextual, size: 0)) + + return insertContextualBreaks(base, isTopLevel: false) } else if let callingExpr = expr.asProtocol(CallingExprSyntaxProtocol.self) { let calledExpression = callingExpr.calledExpression let (hasCompoundExpression, hasMemberAccess) = @@ -3805,20 +3815,6 @@ private func isNestedInPostfixIfConfig(node: Syntax) -> Bool { return false } -private func isNestedInIfConfig(node: Syntax) -> Bool { - var this: Syntax? = node - - while this?.parent != nil { - if this?.is(IfConfigClauseSyntax.self) == true { - return true - } - - this = this?.parent - } - - return false -} - extension Syntax { /// Creates a pretty-printable token stream for the provided Syntax node. func makeTokenStream(configuration: Configuration, operatorTable: OperatorTable) -> [Token] { diff --git a/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift b/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift index 9a01fe631..5e8e1199f 100644 --- a/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift @@ -163,25 +163,25 @@ final class IfConfigTests: PrettyPrintTestCase { func testInvalidDiscretionaryLineBreaksRemoved() { let input = - """ - #if (canImport(SwiftUI) && - !(os(iOS) && - arch(arm)) && - ((canImport(AppKit) || - canImport(UIKit)) && !os(watchOS))) - conditionalFunc(foo, bar, baz) - #endif - """ - - let expected = - """ - #if (canImport(SwiftUI) && !(os(iOS) && arch(arm)) && ((canImport(AppKit) || canImport(UIKit)) && !os(watchOS))) - conditionalFunc(foo, bar, baz) - #endif - - """ - - assertPrettyPrintEqual(input: input, expected: expected, linelength: 40) + """ + #if (canImport(SwiftUI) && + !(os(iOS) && + arch(arm)) && + ((canImport(AppKit) || + canImport(UIKit)) && !os(watchOS))) + conditionalFunc(foo, bar, baz) + #endif + """ + + let expected = + """ + #if (canImport(SwiftUI) && !(os(iOS) && arch(arm)) && ((canImport(AppKit) || canImport(UIKit)) && !os(watchOS))) + conditionalFunc(foo, bar, baz) + #endif + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40) } func testValidDiscretionaryLineBreaksRetained() { @@ -299,6 +299,8 @@ final class IfConfigTests: PrettyPrintTestCase { #if os(iOS) || os(watchOS) #if os(iOS) .iOSModifier() + #elseif os(tvOS) + .tvOSModifier() #else .watchOSModifier() #endif @@ -314,6 +316,8 @@ final class IfConfigTests: PrettyPrintTestCase { #if os(iOS) || os(watchOS) #if os(iOS) .iOSModifier() + #elseif os(tvOS) + .tvOSModifier() #else .watchOSModifier() #endif @@ -326,7 +330,6 @@ final class IfConfigTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) } - func testPostfixPoundIfAfterVariables() { let input = """ @@ -398,6 +401,7 @@ final class IfConfigTests: PrettyPrintTestCase { .padding([.vertical]) #if os(iOS) .iOSSpecificModifier() + .anotherIOSSpecificModifier() #endif .commonModifier() """ @@ -408,6 +412,7 @@ final class IfConfigTests: PrettyPrintTestCase { .padding([.vertical]) #if os(iOS) .iOSSpecificModifier() + .anotherIOSSpecificModifier() #endif .commonModifier() @@ -454,4 +459,61 @@ final class IfConfigTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) } + + func testPostfixPoundIfNotIndentedIfClosingParenOnOwnLine() { + let input = + """ + SomeFunction( + foo, + bar + ) + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() + """ + + let expected = + """ + SomeFunction( + foo, + bar + ) + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } + + func testPostfixPoundIfForcesPrecedingClosingParenOntoNewLine() { + let input = + """ + SomeFunction( + foo, + bar) + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() + """ + + let expected = + """ + SomeFunction( + foo, + bar + ) + #if os(iOS) + .iOSSpecificModifier() + #endif + .commonModifier() + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } } From 36aab0ae0d113217fdee6611ce60185996ef81c2 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 28 Jun 2023 16:23:51 -0400 Subject: [PATCH 055/332] Fix indentation of multiline strings in enum case raw values. --- .../TokenStreamCreator.swift | 23 ++++++++++++++++++- .../StringTests.swift | 11 +++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 8ab28b487..89cd415f9 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1556,6 +1556,26 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { arrangeEnumCaseParameterClause(associatedValue, forcesBreakBeforeRightParen: false) } + if let initializer = node.rawValue { + if let (unindentingNode, _, breakKind, shouldGroup) = + stackedIndentationBehavior(rhs: initializer.value) + { + var openTokens: [Token] = [.break(.open(kind: breakKind))] + if shouldGroup { + openTokens.append(.open) + } + after(initializer.equal, tokens: openTokens) + + var closeTokens: [Token] = [.break(.close(mustBreak: false), size: 0)] + if shouldGroup { + closeTokens.append(.close) + } + after(unindentingNode.lastToken(viewMode: .sourceAccurate), tokens: closeTokens) + } else { + after(initializer.equal, tokens: .break(.continue)) + } + } + return .visitChildren } @@ -2303,12 +2323,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: InitializerClauseSyntax) -> SyntaxVisitorContinueKind { before(node.equal, tokens: .space) - // InitializerClauses that are children of a PatternBindingSyntax or + // InitializerClauses that are children of a PatternBindingSyntax, EnumCaseElementSyntax, or // OptionalBindingConditionSyntax are already handled in the latter node, to ensure that // continuations stack appropriately. if let parent = node.parent, !parent.is(PatternBindingSyntax.self) && !parent.is(OptionalBindingConditionSyntax.self) + && !parent.is(EnumCaseElementSyntax.self) { after(node.equal, tokens: .break) } diff --git a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift index d82aba30c..a9cc75f96 100644 --- a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift @@ -419,6 +419,17 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 100) } + func testMultilineStringsAsEnumRawValues() { + let input = #""" + enum E: String { + case x = """ + blah blah + """ + } + """# + assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 100) + } + func testMultilineStringsNestedInAnotherWrappingContext() { let input = #""" From b4088ace9b6c488537adb5030d5f8930edffdb30 Mon Sep 17 00:00:00 2001 From: kitasuke Date: Sat, 1 Jul 2023 22:58:01 +0900 Subject: [PATCH 056/332] Fix deprecated warnings for SwiftSyntax --- Sources/SwiftFormat/Pipelines+Generated.swift | 42 +++++------ Sources/SwiftFormat/SwiftFormatter.swift | 2 +- Sources/SwiftFormatCore/Context.swift | 6 +- .../TokenStreamCreator.swift | 72 +++++++++---------- .../AddModifierRewriter.swift | 2 +- .../SwiftFormatRules/DoNotUseSemicolons.swift | 2 +- .../NoAssignmentInExpressions.swift | 2 +- .../NoEmptyTrailingClosureParentheses.swift | 2 +- Sources/SwiftFormatRules/ReplaceTrivia.swift | 2 +- .../UseShorthandTypeNames.swift | 6 +- .../generate-pipeline/PipelineGenerator.swift | 4 +- .../SwiftFormatCoreTests/RuleMaskTests.swift | 2 +- 12 files changed, 72 insertions(+), 72 deletions(-) diff --git a/Sources/SwiftFormat/Pipelines+Generated.swift b/Sources/SwiftFormat/Pipelines+Generated.swift index bc748aa1a..8fb86cbd0 100644 --- a/Sources/SwiftFormat/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Pipelines+Generated.swift @@ -321,28 +321,28 @@ class LintPipeline: SyntaxVisitor { extension FormatPipeline { - func visit(_ node: Syntax) -> Syntax { + func rewrite(_ node: Syntax) -> Syntax { var node = node - node = DoNotUseSemicolons(context: context).visit(node) - node = FileScopedDeclarationPrivacy(context: context).visit(node) - node = FullyIndirectEnum(context: context).visit(node) - node = GroupNumericLiterals(context: context).visit(node) - node = NoAccessLevelOnExtensionDeclaration(context: context).visit(node) - node = NoAssignmentInExpressions(context: context).visit(node) - node = NoCasesWithOnlyFallthrough(context: context).visit(node) - node = NoEmptyTrailingClosureParentheses(context: context).visit(node) - node = NoLabelsInCasePatterns(context: context).visit(node) - node = NoParensAroundConditions(context: context).visit(node) - node = NoVoidReturnOnFunctionSignature(context: context).visit(node) - node = OneCasePerLine(context: context).visit(node) - node = OneVariableDeclarationPerLine(context: context).visit(node) - node = OrderedImports(context: context).visit(node) - node = ReturnVoidInsteadOfEmptyTuple(context: context).visit(node) - node = UseEarlyExits(context: context).visit(node) - node = UseShorthandTypeNames(context: context).visit(node) - node = UseSingleLinePropertyGetter(context: context).visit(node) - node = UseTripleSlashForDocumentationComments(context: context).visit(node) - node = UseWhereClausesInForLoops(context: context).visit(node) + node = DoNotUseSemicolons(context: context).rewrite(node) + node = FileScopedDeclarationPrivacy(context: context).rewrite(node) + node = FullyIndirectEnum(context: context).rewrite(node) + node = GroupNumericLiterals(context: context).rewrite(node) + node = NoAccessLevelOnExtensionDeclaration(context: context).rewrite(node) + node = NoAssignmentInExpressions(context: context).rewrite(node) + node = NoCasesWithOnlyFallthrough(context: context).rewrite(node) + node = NoEmptyTrailingClosureParentheses(context: context).rewrite(node) + node = NoLabelsInCasePatterns(context: context).rewrite(node) + node = NoParensAroundConditions(context: context).rewrite(node) + node = NoVoidReturnOnFunctionSignature(context: context).rewrite(node) + node = OneCasePerLine(context: context).rewrite(node) + node = OneVariableDeclarationPerLine(context: context).rewrite(node) + node = OrderedImports(context: context).rewrite(node) + node = ReturnVoidInsteadOfEmptyTuple(context: context).rewrite(node) + node = UseEarlyExits(context: context).rewrite(node) + node = UseShorthandTypeNames(context: context).rewrite(node) + node = UseSingleLinePropertyGetter(context: context).rewrite(node) + node = UseTripleSlashForDocumentationComments(context: context).rewrite(node) + node = UseWhereClausesInForLoops(context: context).rewrite(node) return node } } diff --git a/Sources/SwiftFormat/SwiftFormatter.swift b/Sources/SwiftFormat/SwiftFormatter.swift index a4a1654af..5902805e6 100644 --- a/Sources/SwiftFormat/SwiftFormatter.swift +++ b/Sources/SwiftFormat/SwiftFormatter.swift @@ -151,7 +151,7 @@ public final class SwiftFormatter { configuration: configuration, operatorTable: operatorTable, findingConsumer: findingConsumer, fileURL: assumedURL, sourceFileSyntax: syntax, source: source, ruleNameCache: ruleNameCache) let pipeline = FormatPipeline(context: context) - let transformedSyntax = pipeline.visit(Syntax(syntax)) + let transformedSyntax = pipeline.rewrite(Syntax(syntax)) if debugOptions.contains(.disablePrettyPrint) { outputStream.write(transformedSyntax.description) diff --git a/Sources/SwiftFormatCore/Context.swift b/Sources/SwiftFormatCore/Context.swift index 870a3ebc9..9dbd886c8 100644 --- a/Sources/SwiftFormatCore/Context.swift +++ b/Sources/SwiftFormatCore/Context.swift @@ -14,6 +14,7 @@ import Foundation import SwiftFormatConfiguration import SwiftOperators import SwiftSyntax +import SwiftParser /// Context contains the bits that each formatter and linter will need access to. /// @@ -74,9 +75,8 @@ public final class Context { self.findingEmitter = FindingEmitter(consumer: findingConsumer) self.fileURL = fileURL self.importsXCTest = .notDetermined - self.sourceLocationConverter = - source.map { SourceLocationConverter(file: fileURL.relativePath, source: $0) } - ?? SourceLocationConverter(file: fileURL.relativePath, tree: sourceFileSyntax) + let tree = source.map { Parser.parse(source: $0) } ?? sourceFileSyntax + self.sourceLocationConverter = SourceLocationConverter(file: fileURL.relativePath, tree: tree) self.ruleMask = RuleMask( syntaxNode: Syntax(sourceFileSyntax), sourceLocationConverter: sourceLocationConverter diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 16432baa8..5aaeed3df 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -532,7 +532,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // MARK: - Control flow statement nodes override func visit(_ node: LabeledStmtSyntax) -> SyntaxVisitorContinueKind { - after(node.labelColon, tokens: .space) + after(node.colon, tokens: .space) return .visitChildren } @@ -1156,9 +1156,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let signature = node.signature { after(node.leftBrace, tokens: .break(.open)) if node.statements.count > 0 { - after(signature.inTok, tokens: .break(.same, newlines: newlineBehavior)) + after(signature.inKeyword, tokens: .break(.same, newlines: newlineBehavior)) } else { - after(signature.inTok, tokens: .break(.same, size: 0, newlines: newlineBehavior)) + after(signature.inKeyword, tokens: .break(.same, size: 0, newlines: newlineBehavior)) } before(node.rightBrace, tokens: .break(.close)) } else { @@ -1207,7 +1207,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Due to visitation order, the matching .open break is added in ParameterClauseSyntax. // Since the output clause is optional but the in-token is required, placing the .close // before `inTok` ensures the close gets into the token stream. - before(node.inTok, tokens: .close) + before(node.inKeyword, tokens: .close) } else { // Group outside of the parens, so that the argument list together, preferring to break // between the argument list and the output. @@ -1226,7 +1226,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.output?.arrow, tokens: .break) after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) - before(node.inTok, tokens: .break(.same)) + before(node.inKeyword, tokens: .break(.same)) return .visitChildren } @@ -1239,8 +1239,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: ClosureCaptureItemSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) after(node.specifier?.lastToken(viewMode: .sourceAccurate), tokens: .break) - before(node.assignToken, tokens: .break) - after(node.assignToken, tokens: .break) + before(node.equal, tokens: .break) + after(node.equal, tokens: .break) if let trailingComma = node.trailingComma { before(trailingComma, tokens: .close) after(trailingComma, tokens: .break(.same)) @@ -1274,8 +1274,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { arrangeFunctionCallArgumentList( arguments, - leftDelimiter: node.leftBracket, - rightDelimiter: node.rightBracket, + leftDelimiter: node.leftSquare, + rightDelimiter: node.rightSquare, forcesBreakBeforeRightDelimiter: breakBeforeRightBracket) return .visitChildren @@ -1506,7 +1506,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { appendFormatterIgnored(node: Syntax(node)) return .skipChildren } - after(node.eofToken, tokens: .break(.same, newlines: .soft)) + after(node.endOfFileToken, tokens: .break(.same, newlines: .soft)) return .visitChildren } @@ -1638,14 +1638,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: GenericParameterClauseSyntax) -> SyntaxVisitorContinueKind { - after(node.leftAngleBracket, tokens: .break(.open, size: 0), .open(argumentListConsistency())) - before(node.rightAngleBracket, tokens: .break(.close, size: 0), .close) + after(node.leftAngle, tokens: .break(.open, size: 0), .open(argumentListConsistency())) + before(node.rightAngle, tokens: .break(.close, size: 0), .close) return .visitChildren } override func visit(_ node: PrimaryAssociatedTypeClauseSyntax) -> SyntaxVisitorContinueKind { - after(node.leftAngleBracket, tokens: .break(.open, size: 0), .open(argumentListConsistency())) - before(node.rightAngleBracket, tokens: .break(.close, size: 0), .close) + after(node.leftAngle, tokens: .break(.open, size: 0), .open(argumentListConsistency())) + before(node.rightAngle, tokens: .break(.close, size: 0), .close) return .visitChildren } @@ -1681,8 +1681,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: GenericArgumentClauseSyntax) -> SyntaxVisitorContinueKind { - after(node.leftAngleBracket, tokens: .break(.open, size: 0), .open) - before(node.rightAngleBracket, tokens: .break(.close, size: 0), .close) + after(node.leftAngle, tokens: .break(.open, size: 0), .open) + before(node.rightAngle, tokens: .break(.close, size: 0), .close) return .visitChildren } @@ -1865,8 +1865,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: KeyPathSubscriptComponentSyntax) -> SyntaxVisitorContinueKind { - after(node.leftBracket, tokens: .break(.open, size: 0), .open) - before(node.rightBracket, tokens: .break(.close, size: 0), .close) + after(node.leftSquare, tokens: .break(.open, size: 0), .open) + before(node.rightSquare, tokens: .break(.close, size: 0), .close) return .visitChildren } @@ -1878,9 +1878,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.questionMark, tokens: .break(.open(kind: .continuation)), .open) after(node.questionMark, tokens: .space) before( - node.colonMark, + node.colon, tokens: .break(.close(mustBreak: false), size: 0), .break(.open(kind: .continuation)), .open) - after(node.colonMark, tokens: .space) + after(node.colon, tokens: .space) // When the ternary is wrapped in parens, absorb the closing paren into the ternary's group so // that it is glued to the last token of the ternary. @@ -2053,14 +2053,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { - before(node.asTok, tokens: .break(.continue), .open) + before(node.asKeyword, tokens: .break(.continue), .open) before(node.typeName.firstToken(viewMode: .sourceAccurate), tokens: .space) after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } override func visit(_ node: IsExprSyntax) -> SyntaxVisitorContinueKind { - before(node.isTok, tokens: .break(.continue), .open) + before(node.isKeyword, tokens: .break(.continue), .open) before(node.typeName.firstToken(viewMode: .sourceAccurate), tokens: .space) after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren @@ -2081,8 +2081,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ArrowExprSyntax) -> SyntaxVisitorContinueKind { - before(node.arrowToken, tokens: .break) - after(node.arrowToken, tokens: .space) + before(node.arrow, tokens: .break) + after(node.arrow, tokens: .space) return .visitChildren } @@ -2563,7 +2563,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // `DerivativeRegistrationAttributeArguments` was added after the Swift 5.2 release was cut. #if HAS_DERIVATIVE_REGISTRATION_ATTRIBUTE - override func visit(_ node: DerivativeRegistrationAttributeArgumentsSyntax) + override func rewrite(_ node: DerivativeRegistrationAttributeArgumentsSyntax) -> SyntaxVisitorContinueKind { // This node encapsulates the entire list of arguments in a `@derivative(...)` or @@ -3411,7 +3411,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return true } if let binOpExpr = operatorExpr.as(BinaryOperatorExprSyntax.self) { - if let binOp = operatorTable.infixOperator(named: binOpExpr.operatorToken.text), + if let binOp = operatorTable.infixOperator(named: binOpExpr.operator.text), let precedenceGroup = binOp.precedenceGroup, precedenceGroup == "AssignmentPrecedence" { return true @@ -3537,7 +3537,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // We also want to reset after undoing the stacked indentation so that we have a visual // indication that the subexpression has ended. if let binOpExpr = operatorExpr?.as(BinaryOperatorExprSyntax.self) { - if let binOp = operatorTable.infixOperator(named: binOpExpr.operatorToken.text), + if let binOp = operatorTable.infixOperator(named: binOpExpr.operator.text), let precedenceGroup = binOp.precedenceGroup, precedenceGroup == "LogicalConjunctionPrecedence" || precedenceGroup == "LogicalDisjunctionPrecedence" @@ -3646,7 +3646,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // The token kind (spaced or unspaced operator) represents how the *user* wrote it, and we want // to ignore that and apply our own rules. if let binaryOperator = operatorExpr.as(BinaryOperatorExprSyntax.self) { - let token = binaryOperator.operatorToken + let token = binaryOperator.operator if !config.spacesAroundRangeFormationOperators, let binOp = operatorTable.infixOperator(named: token.text), let precedenceGroup = binOp.precedenceGroup, precedenceGroup == "RangeFormationPrecedence" @@ -3742,7 +3742,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // scoping tokens (e.g. `contextualBreakingStart`, `open`). if memberAccessExpr.base != nil && expr.parent?.isProtocol(CallingExprSyntaxProtocol.self) != true { - before(memberAccessExpr.dot, tokens: .break(.contextual, size: 0)) + before(memberAccessExpr.period, tokens: .break(.contextual, size: 0)) } var hasCompoundExpression = false if let base = memberAccessExpr.base { @@ -3786,12 +3786,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let calledMemberAccessExpr = calledExpression.as(MemberAccessExprSyntax.self) { if calledMemberAccessExpr.base != nil { if isNestedInPostfixIfConfig(node: Syntax(calledMemberAccessExpr)) { - before(calledMemberAccessExpr.dot, tokens: [.break(.same, size: 0)]) + before(calledMemberAccessExpr.period, tokens: [.break(.same, size: 0)]) } else { - before(calledMemberAccessExpr.dot, tokens: [.break(.contextual, size: 0)]) + before(calledMemberAccessExpr.period, tokens: [.break(.contextual, size: 0)]) } } - before(calledMemberAccessExpr.dot, tokens: beforeTokens) + before(calledMemberAccessExpr.period, tokens: beforeTokens) after(expr.lastToken(viewMode: .sourceAccurate), tokens: afterTokens) if isTopLevel { before(expr.firstToken(viewMode: .sourceAccurate), tokens: .contextualBreakingStart) @@ -3839,7 +3839,7 @@ private func isNestedInPostfixIfConfig(node: Syntax) -> Bool { extension Syntax { /// Creates a pretty-printable token stream for the provided Syntax node. func makeTokenStream(configuration: Configuration, operatorTable: OperatorTable) -> [Token] { - let commentsMoved = CommentMovingRewriter().visit(self) + let commentsMoved = CommentMovingRewriter().rewrite(self) return TokenStreamCreator(configuration: configuration, operatorTable: operatorTable) .makeStream(from: commentsMoved) } @@ -3884,13 +3884,13 @@ class CommentMovingRewriter: SyntaxRewriter { override func visit(_ node: InfixOperatorExprSyntax) -> ExprSyntax { if let binaryOperatorExpr = node.operatorOperand.as(BinaryOperatorExprSyntax.self), - let followingToken = binaryOperatorExpr.operatorToken.nextToken(viewMode: .all), + let followingToken = binaryOperatorExpr.operator.nextToken(viewMode: .all), followingToken.leadingTrivia.hasLineComment { // Rewrite the trivia so that the comment is in the operator token's leading trivia. let (remainingTrivia, extractedTrivia) = extractLineCommentTrivia(from: followingToken) - let combinedTrivia = binaryOperatorExpr.operatorToken.leadingTrivia + extractedTrivia - rewriteTokenTriviaMap[binaryOperatorExpr.operatorToken] = combinedTrivia + let combinedTrivia = binaryOperatorExpr.operator.leadingTrivia + extractedTrivia + rewriteTokenTriviaMap[binaryOperatorExpr.operator] = combinedTrivia rewriteTokenTriviaMap[followingToken] = remainingTrivia } return super.visit(node) diff --git a/Sources/SwiftFormatRules/AddModifierRewriter.swift b/Sources/SwiftFormatRules/AddModifierRewriter.swift index f58a4472e..5ea4aa30c 100644 --- a/Sources/SwiftFormatRules/AddModifierRewriter.swift +++ b/Sources/SwiftFormatRules/AddModifierRewriter.swift @@ -186,5 +186,5 @@ func addModifier( declaration: DeclSyntax, modifierKeyword: DeclModifierSyntax ) -> Syntax { - return AddModifierRewriter(modifierKeyword: modifierKeyword).visit(Syntax(declaration)) + return AddModifierRewriter(modifierKeyword: modifierKeyword).rewrite(Syntax(declaration)) } diff --git a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift index 604ece259..59cea384f 100644 --- a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift @@ -43,7 +43,7 @@ public final class DoNotUseSemicolons: SyntaxFormatRule { // Check for semicolons in statements inside of the item, because code blocks may be nested // inside of other code blocks. - guard let visitedItem = visit(Syntax(item)).as(ItemType.self) else { + guard let visitedItem = rewrite(Syntax(item)).as(ItemType.self) else { return node } diff --git a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift index 7384c5f36..fa27da83c 100644 --- a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift @@ -108,7 +108,7 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { guard let binaryOp = expr.operatorOperand.as(BinaryOperatorExprSyntax.self) else { return false } - return context.operatorTable.infixOperator(named: binaryOp.operatorToken.text)?.precedenceGroup + return context.operatorTable.infixOperator(named: binaryOp.operator.text)?.precedenceGroup == "AssignmentPrecedence" } diff --git a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift index 6bc786e98..d89365620 100644 --- a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift +++ b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift @@ -37,7 +37,7 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { // Need to visit `calledExpression` before creating a new node so that the location data (column // and line numbers) is available. - guard let rewrittenCalledExpr = ExprSyntax(visit(Syntax(node.calledExpression))) else { + guard let rewrittenCalledExpr = ExprSyntax(rewrite(Syntax(node.calledExpression))) else { return super.visit(node) } let formattedExp = replaceTrivia( diff --git a/Sources/SwiftFormatRules/ReplaceTrivia.swift b/Sources/SwiftFormatRules/ReplaceTrivia.swift index 4c3f53514..e389d9e77 100644 --- a/Sources/SwiftFormatRules/ReplaceTrivia.swift +++ b/Sources/SwiftFormatRules/ReplaceTrivia.swift @@ -59,5 +59,5 @@ func replaceTrivia( token: token, leadingTrivia: leadingTrivia, trailingTrivia: trailingTrivia - ).visit(Syntax(node)).as(SyntaxType.self)! + ).rewrite(Syntax(node)).as(SyntaxType.self)! } diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index aadbb63f7..4ce6dea3e 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -241,7 +241,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // otherwise the "?" applies to the return type instead of the function type. Attach the // leading trivia to the left-paren that we're adding in this case. let tupleTypeElement = TupleTypeElementSyntax( - inOut: nil, name: nil, secondName: nil, colon: nil, type: TypeSyntax(functionType), + inoutKeyword: nil, name: nil, secondName: nil, colon: nil, type: TypeSyntax(functionType), ellipsis: nil, initializer: nil, trailingComma: nil) let tupleTypeElementList = TupleTypeElementListSyntax([tupleTypeElement]) let tupleType = TupleTypeSyntax( @@ -384,7 +384,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { } let result = MemberAccessExprSyntax( base: baseType, - dot: memberTypeIdentifier.period, + period: memberTypeIdentifier.period, name: memberTypeIdentifier.name, declNameArguments: nil) return ExprSyntax(result) @@ -475,7 +475,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { rightParen: rightParen) let arrowExpr = ArrowExprSyntax( effectSpecifiers: effectSpecifiers, - arrowToken: arrow) + arrow: arrow) return SequenceExprSyntax( elements: ExprListSyntax([ diff --git a/Sources/generate-pipeline/PipelineGenerator.swift b/Sources/generate-pipeline/PipelineGenerator.swift index e0455f583..8a92036ce 100644 --- a/Sources/generate-pipeline/PipelineGenerator.swift +++ b/Sources/generate-pipeline/PipelineGenerator.swift @@ -95,7 +95,7 @@ final class PipelineGenerator: FileGenerator { extension FormatPipeline { - func visit(_ node: Syntax) -> Syntax { + func rewrite(_ node: Syntax) -> Syntax { var node = node """ @@ -104,7 +104,7 @@ final class PipelineGenerator: FileGenerator { for ruleName in ruleCollector.allFormatters.map({ $0.typeName }).sorted() { handle.write( """ - node = \(ruleName)(context: context).visit(node) + node = \(ruleName)(context: context).rewrite(node) """) } diff --git a/Tests/SwiftFormatCoreTests/RuleMaskTests.swift b/Tests/SwiftFormatCoreTests/RuleMaskTests.swift index b4303b850..8ba56b179 100644 --- a/Tests/SwiftFormatCoreTests/RuleMaskTests.swift +++ b/Tests/SwiftFormatCoreTests/RuleMaskTests.swift @@ -11,8 +11,8 @@ final class RuleMaskTests: XCTestCase { private func createMask(sourceText: String) -> RuleMask { let fileURL = URL(fileURLWithPath: "/tmp/test.swift") - converter = SourceLocationConverter(file: fileURL.path, source: sourceText) let syntax = Parser.parse(source: sourceText) + converter = SourceLocationConverter(file: fileURL.path, tree: syntax) return RuleMask(syntaxNode: Syntax(syntax), sourceLocationConverter: converter) } From 348976f278511d05fda593573892d63ad7c1e523 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 5 Jul 2023 10:28:32 -0400 Subject: [PATCH 057/332] Remove the compiler condition guarding `DerivativeRegistrationAttributeArgumentsSyntax`. This hasn't been relevant since... Swift 5.2. --- .../TokenStreamCreator.swift | 33 +++-- .../DifferentiationAttributeTests.swift | 124 +++++++++--------- 2 files changed, 75 insertions(+), 82 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 5aaeed3df..a9f2ff4ee 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -2561,26 +2561,23 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - // `DerivativeRegistrationAttributeArguments` was added after the Swift 5.2 release was cut. - #if HAS_DERIVATIVE_REGISTRATION_ATTRIBUTE - override func rewrite(_ node: DerivativeRegistrationAttributeArgumentsSyntax) - -> SyntaxVisitorContinueKind - { - // This node encapsulates the entire list of arguments in a `@derivative(...)` or - // `@transpose(...)` attribute. - before(node.ofLabel, tokens: .open) - after(node.colon, tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) - // The comma after originalDeclName is optional and is only present if there are diffParams. - after(node.comma ?? node.originalDeclName.lastToken, tokens: .close) - - if let diffParams = node.diffParams { - before(diffParams.firstToken, tokens: .break(.same), .open) - after(diffParams.lastToken, tokens: .close) - } + override func visit(_ node: DerivativeRegistrationAttributeArgumentsSyntax) + -> SyntaxVisitorContinueKind + { + // This node encapsulates the entire list of arguments in a `@derivative(...)` or + // `@transpose(...)` attribute. + before(node.ofLabel, tokens: .open) + after(node.colon, tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + // The comma after originalDeclName is optional and is only present if there are diffParams. + after(node.comma ?? node.originalDeclName.lastToken(viewMode: .sourceAccurate), tokens: .close) - return .visitChildren + if let diffParams = node.diffParams { + before(diffParams.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) + after(diffParams.lastToken(viewMode: .sourceAccurate), tokens: .close) } - #endif + + return .visitChildren + } override func visit(_ node: DifferentiabilityParamsClauseSyntax) -> SyntaxVisitorContinueKind { // This node encapsulates the `wrt:` label and value/variable in a `@differentiable`, diff --git a/Tests/SwiftFormatPrettyPrintTests/DifferentiationAttributeTests.swift b/Tests/SwiftFormatPrettyPrintTests/DifferentiationAttributeTests.swift index 5e3cf2b58..3c14742f9 100644 --- a/Tests/SwiftFormatPrettyPrintTests/DifferentiationAttributeTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/DifferentiationAttributeTests.swift @@ -96,84 +96,80 @@ final class DifferentiationAttributeTests: PrettyPrintTestCase { } func testDerivative() { - #if HAS_DERIVATIVE_REGISTRATION_ATTRIBUTE - let input = - """ - @derivative(of: foo) - func deriv() {} + let input = + """ + @derivative(of: foo) + func deriv() {} - @derivative(of: foo, wrt: x) - func deriv(_ x: T) {} + @derivative(of: foo, wrt: x) + func deriv(_ x: T) {} - @derivative(of: foobar, wrt: x) - func deriv(_ x: T) {} + @derivative(of: foobar, wrt: x) + func deriv(_ x: T) {} - @derivative(of: foobarbaz, wrt: theVariableNamedX) - func deriv(_ theVariableNamedX: T) {} - """ + @derivative(of: foobarbaz, wrt: theVariableNamedX) + func deriv(_ theVariableNamedX: T) {} + """ - let expected = - """ - @derivative(of: foo) - func deriv() {} + let expected = + """ + @derivative(of: foo) + func deriv() {} - @derivative(of: foo, wrt: x) - func deriv(_ x: T) {} + @derivative(of: foo, wrt: x) + func deriv(_ x: T) {} - @derivative( - of: foobar, wrt: x - ) - func deriv(_ x: T) {} + @derivative( + of: foobar, wrt: x + ) + func deriv(_ x: T) {} - @derivative( - of: foobarbaz, - wrt: theVariableNamedX - ) - func deriv( - _ theVariableNamedX: T - ) {} + @derivative( + of: foobarbaz, + wrt: theVariableNamedX + ) + func deriv( + _ theVariableNamedX: T + ) {} - """ + """ - assertPrettyPrintEqual(input: input, expected: expected, linelength: 28) - #endif + assertPrettyPrintEqual(input: input, expected: expected, linelength: 28) } func testTranspose() { - #if HAS_DERIVATIVE_REGISTRATION_ATTRIBUTE - let input = - """ - @transpose(of: foo, wrt: 0) - func trans(_ v: T) {} - - @transpose(of: foobar, wrt: 0) - func trans(_ v: T) {} - - @transpose(of: someReallyLongName, wrt: 0) - func trans(_ theVariableNamedV: T) {} - """ - - let expected = - """ - @transpose(of: foo, wrt: 0) - func trans(_ v: T) {} - - @transpose( - of: foobar, wrt: 0 - ) - func trans(_ v: T) {} + let input = + """ + @transpose(of: foo, wrt: 0) + func trans(_ v: T) {} - @transpose( - of: someReallyLongName, - wrt: 0 - ) - func trans( - _ theVariableNamedV: T - ) {} + @transpose(of: foobar, wrt: 0) + func trans(_ v: T) {} - """ + @transpose(of: someReallyLongName, wrt: 0) + func trans(_ theVariableNamedV: T) {} + """ + + let expected = + """ + @transpose(of: foo, wrt: 0) + func trans(_ v: T) {} + + @transpose( + of: foobar, wrt: 0 + ) + func trans(_ v: T) {} + + @transpose( + of: someReallyLongName, + wrt: 0 + ) + func trans( + _ theVariableNamedV: T + ) {} + + """ - assertPrettyPrintEqual(input: input, expected: expected, linelength: 27) - #endif + assertPrettyPrintEqual(input: input, expected: expected, linelength: 27) } } From 1ebb0103d7c662c247b5d4a1d44be56ee426490f Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 5 Jul 2023 10:20:33 -0400 Subject: [PATCH 058/332] Use swift-markdown to parse documentation comments. This change replaces the bespoke doc comment parsing code with Markdown tree walking that tries to match the implementation inside the compiler. --- Package.swift | 14 +- .../DocumentationComment.swift | 344 ++++++++++++++++++ .../DocumentationCommentText.swift | 177 +++++++++ ...lPublicDeclarationsHaveDocumentation.swift | 7 +- ...cumentationCommentWithOneLineSummary.swift | 24 +- .../DeclSyntaxProtocol+Comments.swift | 168 --------- ...eTripleSlashForDocumentationComments.swift | 67 ++-- .../ValidateDocumentationComments.swift | 62 ++-- .../DocumentationCommentTests.swift | 298 +++++++++++++++ .../DocumentationCommentTextTests.swift | 135 +++++++ ...leSlashForDocumentationCommentsTests.swift | 5 +- .../ValidateDocumentationCommentsTests.swift | 19 +- 12 files changed, 1045 insertions(+), 275 deletions(-) create mode 100644 Sources/SwiftFormatCore/DocumentationComment.swift create mode 100644 Sources/SwiftFormatCore/DocumentationCommentText.swift delete mode 100644 Sources/SwiftFormatRules/DeclSyntaxProtocol+Comments.swift create mode 100644 Tests/SwiftFormatCoreTests/DocumentationCommentTests.swift create mode 100644 Tests/SwiftFormatCoreTests/DocumentationCommentTextTests.swift diff --git a/Package.swift b/Package.swift index d5943efe8..d4023c8ce 100644 --- a/Package.swift +++ b/Package.swift @@ -67,13 +67,18 @@ let package = Package( name: "SwiftFormatCore", dependencies: [ "SwiftFormatConfiguration", + .product(name: "Markdown", package: "swift-markdown"), .product(name: "SwiftOperators", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), ] ), .target( name: "SwiftFormatRules", - dependencies: ["SwiftFormatCore", "SwiftFormatConfiguration"] + dependencies: [ + "SwiftFormatCore", + "SwiftFormatConfiguration", + .product(name: "Markdown", package: "swift-markdown"), + ] ), .target( name: "SwiftFormatPrettyPrint", @@ -155,7 +160,9 @@ let package = Package( dependencies: [ "SwiftFormatConfiguration", "SwiftFormatCore", + .product(name: "Markdown", package: "swift-markdown"), .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), ] ), @@ -216,6 +223,10 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2" ), + .package( + url: "https://github.com/apple/swift-markdown.git", + from: "0.2.0" + ), .package( url: "https://github.com/apple/swift-syntax.git", branch: "main" @@ -224,6 +235,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { } else { package.dependencies += [ .package(path: "../swift-argument-parser"), + .package(path: "../swift-markdown"), .package(path: "../swift-syntax"), ] } diff --git a/Sources/SwiftFormatCore/DocumentationComment.swift b/Sources/SwiftFormatCore/DocumentationComment.swift new file mode 100644 index 000000000..e6651f9bb --- /dev/null +++ b/Sources/SwiftFormatCore/DocumentationComment.swift @@ -0,0 +1,344 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Markdown +import SwiftSyntax + +/// A structured representation of information extracted from a documentation comment. +/// +/// This type represents both the top-level content of a documentation comment on a declaration and +/// also the nested information that can be provided on a parameter. For example, when a parameter +/// is a function type, it can provide not only a brief summary but also its own parameter and +/// return value descriptions. +public struct DocumentationComment { + /// A description of a parameter in a documentation comment. + public struct Parameter { + /// The name of the parameter. + public var name: String + + /// The documentation comment of the parameter. + /// + /// Typically, only the `briefSummary` field of this value will be populated. However, for more + /// complex cases like parameters whose types are functions, the grammar permits full + /// descriptions including `Parameter(s)`, `Returns`, and `Throws` fields to be present. + public var comment: DocumentationComment + } + + /// Describes the structural layout of the parameter descriptions in the comment. + public enum ParameterLayout { + /// All parameters were written under a single `Parameters` outline section at the top level of + /// the comment. + case outline + + /// All parameters were written as individual `Parameter` items at the top level of the comment. + case separated + + /// Parameters were written as a combination of one or more `Parameters` outlines and individual + /// `Parameter` items. + case mixed + } + + /// A single paragraph representing a brief summary of the declaration, if present. + public var briefSummary: Paragraph? = nil + + /// A collection of otherwise uncategorized body nodes at the top level of the comment text. + /// + /// If a brief summary paragraph was extracted from the comment, it will not be present in this + /// collection. + public var bodyNodes: [Markup] = [] + + /// The structural layout of the parameter descriptions in the comment. + public var parameterLayout: ParameterLayout? = nil + + /// Descriptions of parameters to a function, if any. + public var parameters: [Parameter] = [] + + /// A description of the return value of a function. + /// + /// If present, this value is a copy of the `Paragraph` node from the comment but with the + /// `Returns:` prefix removed for convenience. + public var returns: Paragraph? = nil + + /// A description of an error thrown by a function. + /// + /// If present, this value is a copy of the `Paragraph` node from the comment but with the + /// `Throws:` prefix removed for convenience. + public var `throws`: Paragraph? = nil + + /// Creates a new `DocumentationComment` with information extracted from the leading trivia of the + /// given syntax node. + /// + /// If the syntax node does not have a preceding documentation comment, this initializer returns + /// `nil`. + /// + /// - Parameter node: The syntax node from which the documentation comment should be extracted. + public init?(extractedFrom node: Node) { + guard let commentInfo = documentationCommentText(extractedFrom: node.leadingTrivia) else { + return nil + } + + // Disable smart quotes and dash conversion since we want to preserve the original content of + // the comments instead of doing documentation generation. + let doc = Document(parsing: commentInfo.text, options: [.disableSmartOpts]) + self.init(markup: doc) + } + + /// Creates a new `DocumentationComment` from the given `Markup` node. + private init(markup: Markup) { + // Extract the first paragraph as the brief summary. It will *not* be included in the body + // nodes. + let remainingChildren: DropFirstSequence + if let firstParagraph = markup.child(through: [(0, Paragraph.self)]) { + briefSummary = firstParagraph.detachedFromParent as? Paragraph + remainingChildren = markup.children.dropFirst() + } else { + briefSummary = nil + remainingChildren = markup.children.dropFirst(0) + } + + for child in remainingChildren { + if var list = child.detachedFromParent as? UnorderedList { + // An unordered list could be one of the following: + // + // 1. A parameter outline: + // - Parameters: + // - x: ... + // - y: ... + // + // 2. An exploded parameter list: + // - Parameter x: ... + // - Parameter y: ... + // + // 3. Some other simple field, like `Returns:`. + // + // Note that the order of execution of these two functions matters for the correct value of + // `parameterLayout` to be computed. If these ever change, make sure to update that + // computation inside the functions. + extractParameterOutline(from: &list) + extractSeparatedParameters(from: &list) + + extractSimpleFields(from: &list) + + // If the list is now empty, don't add it to the body nodes below. + guard !list.isEmpty else { continue } + } + + bodyNodes.append(child.detachedFromParent) + } + } + + /// Extracts parameter fields in an outlined parameters list (i.e., `- Parameters:` containing a + /// nested list of parameter fields) from the given unordered list. + /// + /// If parameters were successfully extracted, the provided list is mutated to remove them as a + /// side effect of this function. + private mutating func extractParameterOutline(from list: inout UnorderedList) { + var unprocessedChildren: [Markup] = [] + + for child in list.children { + guard + let listItem = child as? ListItem, + let firstText = listItem.child(through: [ + (0, Paragraph.self), + (0, Text.self), + ]) as? Text, + firstText.string.trimmingCharacters(in: .whitespaces).lowercased() == "parameters:" + else { + unprocessedChildren.append(child.detachedFromParent) + continue + } + + for index in 1..:` items in + /// a top-level list in the comment text) from the given unordered list. + /// + /// If parameters were successfully extracted, the provided list is mutated to remove them as a + /// side effect of this function. + private mutating func extractSeparatedParameters(from list: inout UnorderedList) { + var unprocessedChildren: [Markup] = [] + + for child in list.children { + guard + let listItem = child as? ListItem, + let paramField = parameterField(extractedFrom: listItem, expectParameterLabel: true) + else { + unprocessedChildren.append(child.detachedFromParent) + continue + } + + self.parameters.append(paramField) + + switch self.parameterLayout { + case nil: + self.parameterLayout = .separated + case .outline: + self.parameterLayout = .mixed + default: + break + } + } + + list = list.withUncheckedChildren(unprocessedChildren) as! UnorderedList + } + + /// Returns a new `ParameterField` containing parameter information extracted from the given list + /// item, or `nil` if it was not a valid parameter field. + private func parameterField( + extractedFrom listItem: ListItem, + expectParameterLabel: Bool + ) -> Parameter? { + var rewriter = ParameterOutlineMarkupRewriter( + origin: listItem, + expectParameterLabel: expectParameterLabel) + guard + let newListItem = listItem.accept(&rewriter) as? ListItem, + let name = rewriter.parameterName + else { return nil } + + return Parameter(name: name, comment: DocumentationComment(markup: newListItem)) + } + + /// Extracts simple fields like `- Returns:` and `- Throws:` from the top-level list in the + /// comment text. + /// + /// If fields were successfully extracted, the provided list is mutated to remove them. + private mutating func extractSimpleFields(from list: inout UnorderedList) { + var unprocessedChildren: [Markup] = [] + + for child in list.children { + guard + let listItem = child as? ListItem, + case var rewriter = SimpleFieldMarkupRewriter(origin: listItem), + listItem.accept(&rewriter) as? ListItem != nil, + let name = rewriter.fieldName, + let paragraph = rewriter.paragraph + else { + unprocessedChildren.append(child) + continue + } + + switch name.lowercased() { + case "returns": + self.returns = paragraph + case "throws": + self.throws = paragraph + default: + unprocessedChildren.append(child) + } + } + + list = list.withUncheckedChildren(unprocessedChildren) as! UnorderedList + } +} + +/// Visits a list item representing a parameter in a documentation comment and rewrites it to remove +/// any `Parameter` tag (if present), the name of the parameter, and the subsequent colon. +private struct ParameterOutlineMarkupRewriter: MarkupRewriter { + /// The list item to which the rewriter will be applied. + let origin: ListItem + + /// If true, the `Parameter` prefix is expected on the list item content and it should be dropped. + let expectParameterLabel: Bool + + /// Populated if the list item to which this is applied represents a valid parameter field. + private(set) var parameterName: String? = nil + + mutating func visitListItem(_ listItem: ListItem) -> Markup? { + // Only recurse into the exact list item we're applying this to; otherwise, return it unchanged. + guard listItem.isIdentical(to: origin) else { return listItem } + return defaultVisit(listItem) + } + + mutating func visitParagraph(_ paragraph: Paragraph) -> Markup? { + // Only recurse into the first paragraph in the list item. + guard paragraph.indexInParent == 0 else { return paragraph } + return defaultVisit(paragraph) + } + + mutating func visitText(_ text: Text) -> Markup? { + // Only manipulate the first text node (of the first paragraph). + guard text.indexInParent == 0 else { return text } + + let parameterPrefix = "parameter " + if expectParameterLabel && !text.string.lowercased().hasPrefix(parameterPrefix) { return text } + + let string = + expectParameterLabel ? text.string.dropFirst(parameterPrefix.count) : text.string[...] + let nameAndRemainder = string.split(separator: ":", maxSplits: 1) + guard nameAndRemainder.count == 2 else { return text } + + let name = nameAndRemainder[0].trimmingCharacters(in: .whitespaces) + guard !name.isEmpty else { return text } + + self.parameterName = name + return Text(String(nameAndRemainder[1])) + } +} + +/// Visits a list item representing a simple field in a documentation comment and rewrites it to +/// extract the field name, removing it and the subsequent colon from the item. +private struct SimpleFieldMarkupRewriter: MarkupRewriter { + /// The list item to which the rewriter will be applied. + let origin: ListItem + + /// Populated if the list item to which this is applied represents a valid simple field. + private(set) var fieldName: String? = nil + + /// Populated if the list item to which this is applied represents a valid simple field. + private(set) var paragraph: Paragraph? = nil + + mutating func visitListItem(_ listItem: ListItem) -> Markup? { + // Only recurse into the exact list item we're applying this to; otherwise, return it unchanged. + guard listItem.isIdentical(to: origin) else { return listItem } + return defaultVisit(listItem) + } + + mutating func visitParagraph(_ paragraph: Paragraph) -> Markup? { + // Only recurse into the first paragraph in the list item. + guard paragraph.indexInParent == 0 else { return paragraph } + guard let newNode = defaultVisit(paragraph) else { return nil } + guard let newParagraph = newNode as? Paragraph else { return newNode } + self.paragraph = newParagraph.detachedFromParent as? Paragraph + return newParagraph + } + + mutating func visitText(_ text: Text) -> Markup? { + // Only manipulate the first text node (of the first paragraph). + guard text.indexInParent == 0 else { return text } + + let nameAndRemainder = text.string.split(separator: ":", maxSplits: 1) + guard nameAndRemainder.count == 2 else { return text } + + let name = nameAndRemainder[0].trimmingCharacters(in: .whitespaces) + guard !name.isEmpty else { return text } + + self.fieldName = name + return Text(String(nameAndRemainder[1])) + } +} diff --git a/Sources/SwiftFormatCore/DocumentationCommentText.swift b/Sources/SwiftFormatCore/DocumentationCommentText.swift new file mode 100644 index 000000000..91a12142e --- /dev/null +++ b/Sources/SwiftFormatCore/DocumentationCommentText.swift @@ -0,0 +1,177 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// Extracts and returns the body text of a documentation comment represented as a trivia +/// collection. +/// +/// This function should be used when only the text of the comment is important, not the structural +/// organization. It automatically handles trimming leading indentation from comments as well as +/// "ASCII art" in block comments (i.e., leading asterisks on each line). +/// +/// This implementation is based on +/// https://github.com/apple/swift/blob/main/lib/Markup/LineList.cpp. +/// +/// - Parameter trivia: The trivia collection from which to extract the comment text. +/// - Returns: If a comment was found, a tuple containing the `String` containing the extracted text +/// and the index into the trivia collection where the comment began is returned. Otherwise, `nil` +/// is returned. +public func documentationCommentText(extractedFrom trivia: Trivia) + -> (text: String, startIndex: Trivia.Index)? +{ + /// Represents a line of text and its leading indentation. + struct Line { + var text: Substring + var firstNonspaceDistance: Int + + init(_ text: Substring) { + self.text = text + self.firstNonspaceDistance = indentationDistance(of: text) + } + } + + // Look backwards from the end of the trivia collection to find the logical start of the comment. + // We have to copy it into an array since `Trivia` doesn't support bidirectional indexing. + let triviaArray = Array(trivia) + let commentStartIndex: [TriviaPiece].Index + if + let lastNonDocCommentIndex = triviaArray.lastIndex(where: { + switch $0 { + case .docBlockComment, .docLineComment, + .newlines(1), .carriageReturns(1), .carriageReturnLineFeeds(1), + .spaces, .tabs: + return false + default: + return true + } + }), + lastNonDocCommentIndex != trivia.endIndex + { + commentStartIndex = triviaArray.index(after: lastNonDocCommentIndex) + } else { + commentStartIndex = triviaArray.startIndex + } + + // Determine the indentation level of the first line of the comment. This is used to adjust + // block comments, whose text spans multiple lines. + let leadingWhitespace = contiguousWhitespace(in: triviaArray, before: commentStartIndex) + var lines = [Line]() + + // Extract the raw lines of text (which will include their leading comment punctuation, which is + // stripped). + for triviaPiece in trivia[commentStartIndex...] { + switch triviaPiece { + case .docLineComment(let line): + lines.append(Line(line.dropFirst(3))) + + case .docBlockComment(let line): + var cleaned = line.dropFirst(3) + if cleaned.hasSuffix("*/") { + cleaned = cleaned.dropLast(2) + } + + var hasASCIIArt = false + if cleaned.hasPrefix("\n") { + cleaned = cleaned.dropFirst() + hasASCIIArt = asciiArtLength(of: cleaned, leadingSpaces: leadingWhitespace) != 0 + } + + while !cleaned.isEmpty { + var index = cleaned.firstIndex(where: \.isNewline) ?? cleaned.endIndex + if hasASCIIArt { + cleaned = cleaned.dropFirst(asciiArtLength(of: cleaned, leadingSpaces: leadingWhitespace)) + index = cleaned.firstIndex(where: \.isNewline) ?? cleaned.endIndex + } + + // Don't add an unnecessary blank line at the end when `*/` is on its own line. + guard cleaned.firstIndex(where: { !$0.isWhitespace }) != nil else { + break + } + + let line = cleaned.prefix(upTo: index) + lines.append(Line(line)) + cleaned = cleaned[index...].dropFirst() + } + + default: + break + } + } + + // Concatenate the lines into a single string, trimming any leading indentation that might be + // present. + guard + !lines.isEmpty, + let firstLineIndex = lines.firstIndex(where: { !$0.text.isEmpty }) + else { return nil } + + let initialIndentation = indentationDistance(of: lines[firstLineIndex].text) + var result = "" + for line in lines[firstLineIndex...] { + let countToDrop = min(initialIndentation, line.firstNonspaceDistance) + result.append(contentsOf: "\(line.text.dropFirst(countToDrop))\n") + } + + guard !result.isEmpty else { return nil } + + let commentStartDistance = + triviaArray.distance(from: triviaArray.startIndex, to: commentStartIndex) + return (text: result, startIndex: trivia.index(trivia.startIndex, offsetBy: commentStartDistance)) +} + +/// Returns the distance from the start of the string to the first non-whitespace character. +private func indentationDistance(of text: Substring) -> Int { + return text.distance( + from: text.startIndex, + to: text.firstIndex { !$0.isWhitespace } ?? text.endIndex) +} + +/// Returns the number of contiguous whitespace characters (spaces and tabs only) that precede the +/// given trivia piece. +private func contiguousWhitespace( + in trivia: [TriviaPiece], + before index: [TriviaPiece].Index +) -> Int { + var index = index + var whitespace = 0 + loop: while index != trivia.startIndex { + index = trivia.index(before: index) + switch trivia[index] { + case .spaces(let count): whitespace += count + case .tabs(let count): whitespace += count + default: break loop + } + } + return whitespace +} + +/// Returns the number of characters considered block comment "ASCII art" at the beginning of the +/// given string. +private func asciiArtLength(of string: Substring, leadingSpaces: Int) -> Int { + let spaces = string.prefix(leadingSpaces) + if spaces.count != leadingSpaces { + return 0 + } + if spaces.contains(where: { !$0.isWhitespace }) { + return 0 + } + + let string = string.dropFirst(leadingSpaces) + if string.hasPrefix(" * ") { + return leadingSpaces + 3 + } + if string.hasPrefix(" *\n") { + return leadingSpaces + 2 + } + return 0 +} diff --git a/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift b/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift index ed5b0ee4d..5bd6d6bee 100644 --- a/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift +++ b/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift @@ -75,10 +75,9 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { name: String, modifiers: ModifierListSyntax? ) { - guard decl.docComment == nil else { return } - guard let mods = modifiers, - mods.has(modifier: "public"), - !mods.has(modifier: "override") + guard + documentationCommentText(extractedFrom: decl.leadingTrivia) == nil, + let mods = modifiers, mods.has(modifier: "public") && !mods.has(modifier: "override") else { return } diff --git a/Sources/SwiftFormatRules/BeginDocumentationCommentWithOneLineSummary.swift b/Sources/SwiftFormatRules/BeginDocumentationCommentWithOneLineSummary.swift index 49b76b0e5..0034f0a5d 100644 --- a/Sources/SwiftFormatRules/BeginDocumentationCommentWithOneLineSummary.swift +++ b/Sources/SwiftFormatRules/BeginDocumentationCommentWithOneLineSummary.swift @@ -87,11 +87,14 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { /// Diagnose documentation comments that don't start with one sentence summary. private func diagnoseDocComments(in decl: DeclSyntax) { - guard let commentText = decl.docComment else { return } - let docComments = commentText.components(separatedBy: "\n") - guard let firstPart = firstParagraph(docComments) else { return } - - let trimmedText = firstPart.trimmingCharacters(in: .whitespacesAndNewlines) + guard + let docComment = DocumentationComment(extractedFrom: decl), + let briefSummary = docComment.briefSummary + else { return } + + // For the purposes of checking the sentence structure of the comment, we can operate on the + // plain text; we don't need any of the styling. + let trimmedText = briefSummary.plainText.trimmingCharacters(in: .whitespacesAndNewlines) let (commentSentences, trailingText) = sentences(in: trimmedText) if commentSentences.count == 0 { diagnose(.terminateSentenceWithPeriod(trimmedText), on: decl) @@ -103,17 +106,6 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { } } - /// Returns the text of the first part of the comment, - private func firstParagraph(_ comments: [String]) -> String? { - var text = [String]() - var index = 0 - while index < comments.count && comments[index] != "*" && comments[index] != "" { - text.append(comments[index]) - index += 1 - } - return comments.isEmpty ? nil : text.joined(separator: " ") - } - /// Returns all the sentences in the given text. /// /// This function uses linguistic APIs if they are available on the current platform; otherwise, diff --git a/Sources/SwiftFormatRules/DeclSyntaxProtocol+Comments.swift b/Sources/SwiftFormatRules/DeclSyntaxProtocol+Comments.swift deleted file mode 100644 index b695ed7fe..000000000 --- a/Sources/SwiftFormatRules/DeclSyntaxProtocol+Comments.swift +++ /dev/null @@ -1,168 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -extension DeclSyntaxProtocol { - /// Searches through the leading trivia of this decl for a documentation comment. - var docComment: String? { - guard let tok = firstToken(viewMode: .sourceAccurate) else { return nil } - var comment = [String]() - - // We need to skip trivia until we see the first comment. This trivia will include all the - // spaces and newlines before the doc comment. - var hasSeenFirstLineComment = false - - // Look through for discontiguous doc comments, separated by more than 1 newline. - gatherComments: for piece in tok.leadingTrivia.reversed() { - switch piece { - case .docBlockComment(let text): - // If we see a single doc block comment, then check to see if we've seen any line comments. - // If so, then use the line comments so far. Otherwise, return this block comment. - if hasSeenFirstLineComment { - break gatherComments - } - let blockComment = text.components(separatedBy: "\n") - // Removes the marks of the block comment. - var isTheFirstLine = true - let blockCommentWithoutMarks = blockComment.map { (line: String) -> String in - // Only the first line of the block comment start with '/**' - let markToRemove = isTheFirstLine ? "/**" : "* " - let trimmedLine = line.trimmingCharacters(in: .whitespaces) - if trimmedLine.starts(with: markToRemove) { - let numCharsToRemove = isTheFirstLine ? markToRemove.count : markToRemove.count - 1 - isTheFirstLine = false - return trimmedLine.hasSuffix("*/") - ? String(trimmedLine.dropFirst(numCharsToRemove).dropLast(3)) : String( - trimmedLine.dropFirst(numCharsToRemove)) - } else if trimmedLine == "*" { - return "" - } else if trimmedLine.hasSuffix("*/") { - return String(line.dropLast(3)) - } - isTheFirstLine = false - return line - } - - return blockCommentWithoutMarks.joined(separator: "\n").trimmingCharacters(in: .newlines) - case .docLineComment(let text): - // Mark that we've started grabbing sequential line comments and append it to the - // comment buffer. - hasSeenFirstLineComment = true - comment.append(text) - case .newlines(let n), .carriageReturns(let n), .carriageReturnLineFeeds(let n): - // Only allow for 1 newline between doc line comments, but allow for newlines between the - // doc comment and the declaration. - guard n == 1 || !hasSeenFirstLineComment else { break gatherComments } - case .spaces, .tabs: - // Skip all spaces/tabs. They're irrelevant here. - break - default: - if hasSeenFirstLineComment { - break gatherComments - } - } - } - - /// Removes the "///" from every line of comment - let docLineComments = comment.reversed().map { $0.dropFirst(3) } - return comment.isEmpty ? nil : docLineComments.joined(separator: "\n") - } - - var docCommentInfo: ParseComment? { - guard let docComment = self.docComment else { return nil } - let comments = docComment.components(separatedBy: .newlines) - var params = [ParseComment.Parameter]() - var commentParagraphs = [String]() - var currentSection: DocCommentSection = .commentParagraphs - var returnsDescription: String? - var throwsDescription: String? - // Takes the first sentence of the comment, and counts the number of lines it uses. - let oneSentenceSummary = docComment.components(separatedBy: ".").first - let numOfOneSentenceLines = oneSentenceSummary!.components(separatedBy: .newlines).count - - // Iterates to all the comments after the one sentence summary to find the parameter(s) - // return tags and get their description. - for line in comments.dropFirst(numOfOneSentenceLines) { - let trimmedLine = line.trimmingCharacters(in: .whitespaces) - - if trimmedLine.starts(with: "- Parameters") { - currentSection = .parameters - } else if trimmedLine.starts(with: "- Parameter") { - // If it's only a parameter it's information is inline with the parameter - // tag, just after the ':'. - guard let index = trimmedLine.firstIndex(of: ":") else { continue } - let name = trimmedLine.dropFirst("- Parameter".count)[.. DeclSyntax { - guard let commentText = decl.docComment else { return decl } - - let docComments = commentText.components(separatedBy: "\n") - var pieces = [TriviaPiece]() - - // Ensures the documentation comment is a docLineComment. - var hasFoundDocComment = false - for piece in decl.leadingTrivia.reversed() { - if case .docBlockComment(_) = piece, !hasFoundDocComment { - hasFoundDocComment = true - diagnose(.avoidDocBlockComment, on: decl) - pieces.append(contentsOf: separateDocBlockIntoPieces(docComments).reversed()) - } else if case .docLineComment(_) = piece, !hasFoundDocComment { - // The comment was a doc-line comment all along. Leave it alone. - // This intentionally only considers the comment closest to the decl. There may be other - // comments, including block or doc-block comments, which are left as-is because they aren't - // necessarily related to the decl and are unlikely part of the decl's documentation. - return decl - } else { - pieces.append(piece) - } + guard let commentInfo = documentationCommentText(extractedFrom: decl.leadingTrivia) else { + return decl } - return !hasFoundDocComment - ? decl : replaceTrivia( - on: decl, - token: decl.firstToken(viewMode: .sourceAccurate), - leadingTrivia: Trivia(pieces: pieces.reversed()) - ) - } + // Keep any trivia leading up to the doc comment. + var pieces = Array(decl.leadingTrivia[.. [TriviaPiece] { - var pieces = [TriviaPiece]() - for lineText in docComments.dropLast() { - // Adds an space as indentation for the lines that needed it. - let docLineMark = lineText.first == " " || lineText.trimmingCharacters(in: .whitespaces) == "" - ? "///" : "/// " - pieces.append(.docLineComment(docLineMark + lineText)) - pieces.append(.newlines(1)) + // If the comment text ends with a newline, remove it so that we don't end up with an extra + // blank line after splitting. + var text = commentInfo.text[...] + if text.hasSuffix("\n") { + text = text.dropLast(1) } - // The last piece doesn't need a newline after it. - if docComments.last!.trimmingCharacters(in: .whitespaces) != "" { - let docLineMark = docComments.last!.first == " " || docComments.last!.trimmingCharacters( - in: .whitespaces) == "" ? "///" : "/// " - pieces.append(.docLineComment(docLineMark + docComments.last!)) + // Append each line of the doc comment with `///` prefixes. + for line in text.split(separator: "\n", omittingEmptySubsequences: false) { + var newLine = "///" + if !line.isEmpty { + newLine.append(" \(line)") + } + pieces.append(.docLineComment(newLine)) + pieces.append(.newlines(1)) } - return pieces + + return replaceTrivia( + on: decl, + token: decl.firstToken(viewMode: .sourceAccurate), + leadingTrivia: Trivia(pieces: pieces) + ) } } diff --git a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift index c00921e8e..d4788265a 100644 --- a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation +import Markdown import SwiftFormatCore import SwiftSyntax @@ -21,7 +22,6 @@ import SwiftSyntax /// Lint: Documentation comments that are incomplete (e.g. missing parameter documentation) or /// invalid (uses `Parameters` when there is only one parameter) will yield a lint error. public final class ValidateDocumentationComments: SyntaxLintRule { - /// Identifies this rule as being opt-in. Accurate and complete documentation comments are /// important, but this rule isn't able to handle situations where portions of documentation are /// redundant. For example when the returns clause is redundant for a simple declaration. @@ -44,45 +44,50 @@ public final class ValidateDocumentationComments: SyntaxLintRule { signature: FunctionSignatureSyntax, returnClause: ReturnClauseSyntax? = nil ) -> SyntaxVisitorContinueKind { - guard let declComment = node.docComment else { return .skipChildren } - guard let commentInfo = node.docCommentInfo else { return .skipChildren } - guard let params = commentInfo.parameters else { return .skipChildren } + guard + let docComment = DocumentationComment(extractedFrom: node), + !docComment.parameters.isEmpty + else { + return .skipChildren + } // If a single sentence summary is the only documentation, parameter(s) and // returns tags may be omitted. - if commentInfo.oneSentenceSummary != nil && commentInfo.commentParagraphs!.isEmpty && params - .isEmpty && commentInfo.returnsDescription == nil + if docComment.briefSummary != nil + && docComment.bodyNodes.isEmpty + && docComment.parameters.isEmpty + && docComment.returns == nil { return .skipChildren } - // Indicates if the documentation uses 'Parameters' as description of the - // documented parameters. - let hasPluralDesc = declComment.components(separatedBy: .newlines).contains { - $0.trimmingCharacters(in: .whitespaces).starts(with: "- Parameters") - } - validateThrows( - signature.effectSpecifiers?.throwsSpecifier, name: name, throwsDesc: commentInfo.throwsDescription, node: node) + signature.effectSpecifiers?.throwsSpecifier, + name: name, + throwsDescription: docComment.throws, + node: node) validateReturn( - returnClause, name: name, returnDesc: commentInfo.returnsDescription, node: node) + returnClause, + name: name, + returnsDescription: docComment.returns, + node: node) let funcParameters = funcParametersIdentifiers(in: signature.input.parameterList) // If the documentation of the parameters is wrong 'docCommentInfo' won't // parse the parameters correctly. First the documentation has to be fix // in order to validate the other conditions. - if hasPluralDesc && funcParameters.count == 1 { + if docComment.parameterLayout != .separated && funcParameters.count == 1 { diagnose(.useSingularParameter, on: node) return .skipChildren - } else if !hasPluralDesc && funcParameters.count > 1 { + } else if docComment.parameterLayout != .outline && funcParameters.count > 1 { diagnose(.usePluralParameters, on: node) return .skipChildren } // Ensures that the parameters of the documentation and the function signature // are the same. - if (params.count != funcParameters.count) || !parametersAreEqual( - params: params, funcParam: funcParameters) + if (docComment.parameters.count != funcParameters.count) + || !parametersAreEqual(params: docComment.parameters, funcParam: funcParameters) { diagnose(.parametersDontMatch(funcName: name), on: node) } @@ -95,12 +100,12 @@ public final class ValidateDocumentationComments: SyntaxLintRule { private func validateReturn( _ returnClause: ReturnClauseSyntax?, name: String, - returnDesc: String?, + returnsDescription: Paragraph?, node: DeclSyntax ) { - if returnClause == nil && returnDesc != nil { + if returnClause == nil && returnsDescription != nil { diagnose(.removeReturnComment(funcName: name), on: node) - } else if let returnClause = returnClause, returnDesc == nil { + } else if let returnClause = returnClause, returnsDescription == nil { if let returnTypeIdentifier = returnClause.returnType.as(SimpleTypeIdentifierSyntax.self), returnTypeIdentifier.name.text == "Never" { @@ -115,7 +120,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { private func validateThrows( _ throwsOrRethrowsKeyword: TokenSyntax?, name: String, - throwsDesc: String?, + throwsDescription: Paragraph?, node: DeclSyntax ) { // If a function is marked as `rethrows`, it doesn't have any errors of its @@ -123,9 +128,11 @@ public final class ValidateDocumentationComments: SyntaxLintRule { // functions marked `throws`. let needsThrowsDesc = throwsOrRethrowsKeyword?.tokenKind == .keyword(.throws) - if !needsThrowsDesc && throwsDesc != nil { - diagnose(.removeThrowsComment(funcName: name), on: throwsOrRethrowsKeyword ?? node.firstToken(viewMode: .sourceAccurate)) - } else if needsThrowsDesc && throwsDesc == nil { + if !needsThrowsDesc && throwsDescription != nil { + diagnose( + .removeThrowsComment(funcName: name), + on: throwsOrRethrowsKeyword ?? node.firstToken(viewMode: .sourceAccurate)) + } else if needsThrowsDesc && throwsDescription == nil { diagnose(.documentErrorsThrown(funcName: name), on: throwsOrRethrowsKeyword) } } @@ -147,7 +154,10 @@ fileprivate func funcParametersIdentifiers(in paramList: FunctionParameterListSy /// Indicates if the parameters name from the documentation and the parameters /// from the declaration are the same. -fileprivate func parametersAreEqual(params: [ParseComment.Parameter], funcParam: [String]) -> Bool { +fileprivate func parametersAreEqual( + params: [DocumentationComment.Parameter], + funcParam: [String] +) -> Bool { for index in 0.. Int) {} + """ + let comment = try XCTUnwrap(DocumentationComment(extractedFrom: decl)) + XCTAssertNil(comment.briefSummary) + XCTAssertTrue(comment.bodyNodes.isEmpty) + XCTAssertEqual(comment.parameterLayout, .outline) + XCTAssertEqual(comment.parameters.count, 1) + XCTAssertEqual(comment.parameters[0].name, "g") + XCTAssertNil(comment.returns) + XCTAssertNil(comment.throws) + + let paramComment = comment.parameters[0].comment + XCTAssertEqual( + paramComment.briefSummary?.debugDescription(), + """ + Paragraph + └─ Text " A function." + """ + ) + XCTAssertTrue(paramComment.bodyNodes.isEmpty) + XCTAssertEqual(paramComment.parameterLayout, .separated) + XCTAssertEqual(paramComment.parameters.count, 2) + XCTAssertEqual(paramComment.parameters[0].name, "x") + XCTAssertEqual( + paramComment.parameters[0].comment.briefSummary?.debugDescription(), + """ + Paragraph + └─ Text " A value." + """ + ) + XCTAssertEqual(paramComment.parameters[1].name, "y") + XCTAssertEqual( + paramComment.parameters[1].comment.briefSummary?.debugDescription(), + """ + Paragraph + └─ Text " Another value." + """ + ) + XCTAssertEqual( + paramComment.returns?.debugDescription(), + """ + Paragraph + └─ Text " A result." + """ + ) + XCTAssertNil(paramComment.throws) + } +} diff --git a/Tests/SwiftFormatCoreTests/DocumentationCommentTextTests.swift b/Tests/SwiftFormatCoreTests/DocumentationCommentTextTests.swift new file mode 100644 index 000000000..136b447aa --- /dev/null +++ b/Tests/SwiftFormatCoreTests/DocumentationCommentTextTests.swift @@ -0,0 +1,135 @@ +import SwiftFormatCore +import SwiftSyntax +import SwiftSyntaxBuilder +import XCTest + +final class DocumentationCommentTextTests: XCTestCase { + func testSimpleDocLineComment() throws { + let decl: DeclSyntax = """ + /// A simple doc comment. + func f() {} + """ + XCTAssertEqual( + documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + """ + A simple doc comment. + + """ + ) + } + + func testOneLineDocBlockComment() throws { + let decl: DeclSyntax = """ + /** A simple doc comment. */ + func f() {} + """ + XCTAssertEqual( + documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + """ + A simple doc comment.\u{0020} + + """ + ) + } + + func testDocBlockCommentWithASCIIArt() throws { + let decl: DeclSyntax = """ + /** + * A simple doc comment. + */ + func f() {} + """ + XCTAssertEqual( + documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + """ + A simple doc comment. + + """ + ) + } + + func testDocBlockCommentWithoutASCIIArt() throws { + let decl: DeclSyntax = """ + /** + A simple doc comment. + */ + func f() {} + """ + XCTAssertEqual( + documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + """ + A simple doc comment. + + """ + ) + } + + func testMultilineDocLineComment() throws { + let decl: DeclSyntax = """ + /// A doc comment. + /// + /// This is a longer paragraph, + /// containing more detail. + /// + /// - Parameter x: A parameter. + /// - Returns: A value. + func f(x: Int) -> Int {} + """ + XCTAssertEqual( + documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + """ + A doc comment. + + This is a longer paragraph, + containing more detail. + + - Parameter x: A parameter. + - Returns: A value. + + """ + ) + } + + func testDocLineCommentStopsAtBlankLine() throws { + let decl: DeclSyntax = """ + /// This should not be part of the comment. + + /// A doc comment. + func f(x: Int) -> Int {} + """ + XCTAssertEqual( + documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + """ + A doc comment. + + """ + ) + } + + func testDocBlockCommentStopsAtBlankLine() throws { + let decl: DeclSyntax = """ + /** This should not be part of the comment. */ + + /** + * This is part of the comment. + */ + /** so is this */ + func f(x: Int) -> Int {} + """ + XCTAssertEqual( + documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + """ + This is part of the comment. + so is this\u{0020} + + """ + ) + } + + func testNilIfNoComment() throws { + let decl: DeclSyntax = """ + func f(x: Int) -> Int {} + """ + XCTAssertNil(documentationCommentText(extractedFrom: decl.leadingTrivia)) + } +} diff --git a/Tests/SwiftFormatRulesTests/UseTripleSlashForDocumentationCommentsTests.swift b/Tests/SwiftFormatRulesTests/UseTripleSlashForDocumentationCommentsTests.swift index bba730a97..f0626275a 100644 --- a/Tests/SwiftFormatRulesTests/UseTripleSlashForDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatRulesTests/UseTripleSlashForDocumentationCommentsTests.swift @@ -108,6 +108,9 @@ final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCas } func testManyDocComments() { + // Note that this retains the trailing space at the end of a single-line doc block comment + // (i.e., the space in `name. */`). It's fine to leave it here; the pretty printer will remove + // it later. XCTAssertFormatting( UseTripleSlashForDocumentationComments.self, input: """ @@ -140,7 +143,7 @@ final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCas /// Why are there so many comments? /// Who knows! But there are loads. - /// AClazz is a class with good name. + /// AClazz is a class with good name.\u{0020} public class AClazz { } """) diff --git a/Tests/SwiftFormatRulesTests/ValidateDocumentationCommentsTests.swift b/Tests/SwiftFormatRulesTests/ValidateDocumentationCommentsTests.swift index 4aafb32e2..5df864395 100644 --- a/Tests/SwiftFormatRulesTests/ValidateDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatRulesTests/ValidateDocumentationCommentsTests.swift @@ -11,22 +11,12 @@ final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { """ /// Uses 'Parameters' when it only has one parameter. /// - /// - Parameters singular: singular description. + /// - Parameters: + /// - singular: singular description. /// - Returns: A string containing the contents of a /// description func testPluralParamDesc(singular: String) -> Bool {} - /// Uses 'Parameter' with a list of parameters. - /// - /// - Parameter - /// - command: The command to execute in the shell environment. - /// - stdin: The string to use as standard input. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - func execute(command: String, stdin: String) -> String { - // ... - } - /// Returns the output generated by executing a command with the given string /// used as standard input. /// @@ -37,9 +27,8 @@ final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { func testInvalidParameterDesc(command: String, stdin: String) -> String {} """ performLint(ValidateDocumentationComments.self, input: input) - XCTAssertDiagnosed(.useSingularParameter, line: 6, column: 1) - XCTAssertDiagnosed(.usePluralParameters, line: 15, column: 1) - XCTAssertDiagnosed(.usePluralParameters, line: 26, column: 1) + XCTAssertDiagnosed(.useSingularParameter, line: 7, column: 1) + XCTAssertDiagnosed(.usePluralParameters, line: 16, column: 1) } func testParametersName() { From b72111836aaec9b5e8503f8a56414ae4bf447a84 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 9 Jul 2023 19:04:55 +0200 Subject: [PATCH 059/332] Adjustment because of a property rename in swift-syntax --- Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift index 74d47d2bb..8135db047 100644 --- a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift @@ -69,7 +69,7 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { let input: ClosureSignatureSyntax.Input? switch node.input { - case .input(let parameterClause)?: + case .parameterClause(let parameterClause)?: // If the closure input is a complete parameter clause (variables and types), make sure that // nested function types are also rewritten (for example, `label: (Int -> ()) -> ()` should // become `label: (Int -> Void) -> Void`). From abffe3cc844361492c4ba35bf4b41020708f972d Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 12 Jul 2023 11:20:37 -0400 Subject: [PATCH 060/332] Replace `[TriviaPiece].Index` with `Array.Index`. The former works with Swift 5.9, but fails in earlier versions. Fixes #564. --- Sources/SwiftFormatCore/DocumentationCommentText.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormatCore/DocumentationCommentText.swift b/Sources/SwiftFormatCore/DocumentationCommentText.swift index 91a12142e..0bb67c54f 100644 --- a/Sources/SwiftFormatCore/DocumentationCommentText.swift +++ b/Sources/SwiftFormatCore/DocumentationCommentText.swift @@ -43,7 +43,7 @@ public func documentationCommentText(extractedFrom trivia: Trivia) // Look backwards from the end of the trivia collection to find the logical start of the comment. // We have to copy it into an array since `Trivia` doesn't support bidirectional indexing. let triviaArray = Array(trivia) - let commentStartIndex: [TriviaPiece].Index + let commentStartIndex: Array.Index if let lastNonDocCommentIndex = triviaArray.lastIndex(where: { switch $0 { @@ -140,7 +140,7 @@ private func indentationDistance(of text: Substring) -> Int { /// given trivia piece. private func contiguousWhitespace( in trivia: [TriviaPiece], - before index: [TriviaPiece].Index + before index: Array.Index ) -> Int { var index = index var whitespace = 0 From f63ba9a563ac4afe00328c695b1ee968178d217f Mon Sep 17 00:00:00 2001 From: Ilia Kolo Date: Thu, 13 Jul 2023 10:28:02 +0300 Subject: [PATCH 061/332] Remove --mode flag for configuration dump --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f78af4826..349e20703 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ file is not found, then it looks in the parent directory, and so on. If no configuration file is found, a default configuration is used. The settings in the default configuration can be viewed by running -`swift-format --mode dump-configuration`, which will dump it to standard +`swift-format dump-configuration`, which will dump it to standard output. If the `--configuration ` option is passed to `swift-format`, then that From efa311a1f13b4977a6003da1bb56fbc6fcc32d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ba=CC=A8k?= <44930823+Matejkob@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:46:22 +0200 Subject: [PATCH 062/332] Use newer equivalents of deprecated node names --- .../TokenStreamCreator.swift | 98 +++++++++---------- .../AlwaysUseLowerCamelCase.swift | 24 ++--- .../AmbiguousTrailingClosureOverload.swift | 2 +- .../FunctionDeclSyntax+Convenience.swift | 2 +- .../NoVoidReturnOnFunctionSignature.swift | 12 +-- Sources/SwiftFormatRules/OrderedImports.swift | 2 +- .../ReturnVoidInsteadOfEmptyTuple.swift | 18 ++-- .../UseShorthandTypeNames.swift | 8 +- .../UseSingleLinePropertyGetter.swift | 2 +- .../UseSynthesizedInitializer.swift | 4 +- .../ValidateDocumentationComments.swift | 4 +- Sources/generate-pipeline/RuleCollector.swift | 2 +- 12 files changed, 89 insertions(+), 89 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index a9f2ff4ee..abb3aee56 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -248,7 +248,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { arrangeAttributeList(node.attributes) - let hasArguments = !node.signature.input.parameterList.isEmpty + let hasArguments = !node.signature.parameterClause.parameterList.isEmpty // Prioritize keeping ") -> " together. We can only do this if the macro has // arguments. @@ -257,8 +257,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.signature.lastToken(viewMode: .sourceAccurate), tokens: .close) } - let mustBreak = node.signature.output != nil || node.definition != nil - arrangeParameterClause(node.signature.input, forcesBreakBeforeRightParen: mustBreak) + let mustBreak = node.signature.returnClause != nil || node.definition != nil + arrangeParameterClause(node.signature.parameterClause, forcesBreakBeforeRightParen: mustBreak) // Prioritize keeping " macro (" together. Also include the ")" if the // parameter list is empty. @@ -267,9 +267,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(firstTokenAfterAttributes, tokens: .open) after(node.macroKeyword, tokens: .break) if hasArguments || node.genericParameterClause != nil { - after(node.signature.input.leftParen, tokens: .close) + after(node.signature.parameterClause.leftParen, tokens: .close) } else { - after(node.signature.input.rightParen, tokens: .close) + after(node.signature.parameterClause.rightParen, tokens: .close) } if let genericWhereClause = node.genericWhereClause { @@ -327,7 +327,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // MARK: - Function and function-like declaration nodes (initializers, deinitializers, subscripts) override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { - let hasArguments = !node.signature.input.parameterList.isEmpty + let hasArguments = !node.signature.parameterClause.parameterList.isEmpty // Prioritize keeping ") throws -> " together. We can only do this if the function // has arguments. @@ -336,8 +336,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.signature.lastToken(viewMode: .sourceAccurate), tokens: .close) } - let mustBreak = node.body != nil || node.signature.output != nil - arrangeParameterClause(node.signature.input, forcesBreakBeforeRightParen: mustBreak) + let mustBreak = node.body != nil || node.signature.returnClause != nil + arrangeParameterClause(node.signature.parameterClause, forcesBreakBeforeRightParen: mustBreak) // Prioritize keeping " func (" together. Also include the ")" if the parameter // list is empty. @@ -345,9 +345,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(firstTokenAfterAttributes, tokens: .open) after(node.funcKeyword, tokens: .break) if hasArguments || node.genericParameterClause != nil { - after(node.signature.input.leftParen, tokens: .close) + after(node.signature.parameterClause.leftParen, tokens: .close) } else { - after(node.signature.input.rightParen, tokens: .close) + after(node.signature.parameterClause.rightParen, tokens: .close) } // Add a non-breaking space after the identifier if it's an operator, to separate it visually @@ -369,7 +369,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { - let hasArguments = !node.signature.input.parameterList.isEmpty + let hasArguments = !node.signature.parameterClause.parameterList.isEmpty // Prioritize keeping ") throws" together. We can only do this if the function // has arguments. @@ -378,16 +378,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.signature.lastToken(viewMode: .sourceAccurate), tokens: .close) } - arrangeParameterClause(node.signature.input, forcesBreakBeforeRightParen: node.body != nil) + arrangeParameterClause(node.signature.parameterClause, forcesBreakBeforeRightParen: node.body != nil) // Prioritize keeping " init" together. let firstTokenAfterAttributes = node.modifiers?.firstToken(viewMode: .sourceAccurate) ?? node.initKeyword before(firstTokenAfterAttributes, tokens: .open) if hasArguments || node.genericParameterClause != nil { - after(node.signature.input.leftParen, tokens: .close) + after(node.signature.parameterClause.leftParen, tokens: .close) } else { - after(node.signature.input.rightParen, tokens: .close) + after(node.signature.parameterClause.rightParen, tokens: .close) } arrangeFunctionLikeDecl( @@ -411,7 +411,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind { - let hasArguments = !node.indices.parameterList.isEmpty + let hasArguments = !node.parameterClause.parameterList.isEmpty before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) @@ -420,9 +420,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(firstModifierToken, tokens: .open) if hasArguments || node.genericParameterClause != nil { - after(node.indices.leftParen, tokens: .close) + after(node.parameterClause.leftParen, tokens: .close) } else { - after(node.indices.rightParen, tokens: .close) + after(node.parameterClause.rightParen, tokens: .close) } } @@ -430,7 +430,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // arguments. if hasArguments && config.prioritizeKeepingFunctionOutputTogether { // Due to visitation order, the matching .open break is added in ParameterClauseSyntax. - after(node.result.lastToken(viewMode: .sourceAccurate), tokens: .close) + after(node.returnClause.lastToken(viewMode: .sourceAccurate), tokens: .close) } arrangeAttributeList(node.attributes) @@ -440,7 +440,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(genericWhereClause.lastToken(viewMode: .sourceAccurate), tokens: .close) } - before(node.result.firstToken(viewMode: .sourceAccurate), tokens: .break) + before(node.returnClause.firstToken(viewMode: .sourceAccurate), tokens: .break) if let accessorOrCodeBlock = node.accessor { switch accessorOrCodeBlock { @@ -453,7 +453,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) - arrangeParameterClause(node.indices, forcesBreakBeforeRightParen: true) + arrangeParameterClause(node.parameterClause, forcesBreakBeforeRightParen: true) return .visitChildren } @@ -1185,22 +1185,22 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) arrangeAttributeList( - node.attributes, suppressFinalBreak: node.input == nil && node.capture == nil) + node.attributes, suppressFinalBreak: node.parameterClause == nil && node.capture == nil) - if let input = node.input { + if let parameterClause = node.parameterClause { // We unconditionally put a break before the `in` keyword below, so we should only put a break // after the capture list's right bracket if there are arguments following it or we'll end up // with an extra space if the line doesn't wrap. after(node.capture?.rightSquare, tokens: .break(.same)) - // When it's parenthesized, the input is a `ParameterClauseSyntax`. Otherwise, it's a + // When it's parenthesized, the parameterClause is a `ParameterClauseSyntax`. Otherwise, it's a // `ClosureParamListSyntax`. The parenthesized version is wrapped in open/close breaks so that // the parens create an extra level of indentation. - if let parameterClause = input.as(ClosureParameterClauseSyntax.self) { + if let closureParameterClause = parameterClause.as(ClosureParameterClauseSyntax.self) { // Whether we should prioritize keeping ") throws -> " together. We can only do // this if the closure has arguments. let keepOutputTogether = - !parameterClause.parameterList.isEmpty && config.prioritizeKeepingFunctionOutputTogether + !closureParameterClause.parameterList.isEmpty && config.prioritizeKeepingFunctionOutputTogether // Keep the output together by grouping from the right paren to the end of the output. if keepOutputTogether { @@ -1211,20 +1211,20 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } else { // Group outside of the parens, so that the argument list together, preferring to break // between the argument list and the output. - before(input.firstToken(viewMode: .sourceAccurate), tokens: .open) - after(input.lastToken(viewMode: .sourceAccurate), tokens: .close) + before(parameterClause.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(parameterClause.lastToken(viewMode: .sourceAccurate), tokens: .close) } - arrangeClosureParameterClause(parameterClause, forcesBreakBeforeRightParen: true) + arrangeClosureParameterClause(closureParameterClause, forcesBreakBeforeRightParen: true) } else { // Group around the arguments, but don't use open/close breaks because there are no parens // to create a new scope. - before(input.firstToken(viewMode: .sourceAccurate), tokens: .open(argumentListConsistency())) - after(input.lastToken(viewMode: .sourceAccurate), tokens: .close) + before(parameterClause.firstToken(viewMode: .sourceAccurate), tokens: .open(argumentListConsistency())) + after(parameterClause.lastToken(viewMode: .sourceAccurate), tokens: .close) } } - before(node.output?.arrow, tokens: .break) + before(node.returnClause?.arrow, tokens: .break) after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) before(node.inKeyword, tokens: .break(.same)) return .visitChildren @@ -1521,7 +1521,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: OperatorDeclSyntax) -> SyntaxVisitorContinueKind { - after(node.fixity, tokens: .break) + after(node.fixitySpecifier, tokens: .break) after(node.operatorKeyword, tokens: .break) return .visitChildren } @@ -1836,7 +1836,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { arrangeAttributeList(node.attributes) after(node.importKeyword, tokens: .space) - after(node.importKind, tokens: .space) + after(node.importKindSpecifier, tokens: .space) after(node.lastToken(viewMode: .sourceAccurate), tokens: .printerControl(kind: .enableBreaking)) return .visitChildren @@ -1941,7 +1941,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: FunctionSignatureSyntax) -> SyntaxVisitorContinueKind { - before(node.output?.firstToken(viewMode: .sourceAccurate), tokens: .break) + before(node.returnClause?.firstToken(viewMode: .sourceAccurate), tokens: .break) return .visitChildren } @@ -2096,14 +2096,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if node.bindings.count == 1 { // If there is only a single binding, don't allow a break between the `let/var` keyword and // the identifier; there are better places to break later on. - after(node.bindingKeyword, tokens: .space) + after(node.bindingSpecifier, tokens: .space) } else { // If there is more than one binding, we permit an open-break after `let/var` so that each of // the comma-delimited items will potentially receive indentation. We also add a group around // the individual bindings to bind them together better. (This is done here, not in // `visit(_: PatternBindingSyntax)`, because we only want that behavior when there are // multiple bindings.) - after(node.bindingKeyword, tokens: .break(.open)) + after(node.bindingSpecifier, tokens: .break(.open)) for binding in node.bindings { before(binding.firstToken(viewMode: .sourceAccurate), tokens: .open) @@ -2301,7 +2301,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ValueBindingPatternSyntax) -> SyntaxVisitorContinueKind { - after(node.bindingKeyword, tokens: .break) + after(node.bindingSpecifier, tokens: .break) return .visitChildren } @@ -2492,7 +2492,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: OptionalBindingConditionSyntax) -> SyntaxVisitorContinueKind { - after(node.bindingKeyword, tokens: .break) + after(node.bindingSpecifier, tokens: .break) if let typeAnnotation = node.typeAnnotation { after( @@ -2532,20 +2532,20 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // This node encapsulates the entire list of arguments in a `@differentiable(...)` attribute. var needsBreakBeforeWhereClause = false - if let diffParamsComma = node.diffParamsComma { - after(diffParamsComma, tokens: .break(.same)) - } else if node.diffParams != nil { + if let parametersComma = node.parametersComma { + after(parametersComma, tokens: .break(.same)) + } else if node.parameters != nil { // If there were diff params but no comma following them, then we have "wrt: foo where ..." // and we need a break before the where clause. needsBreakBeforeWhereClause = true } - if let whereClause = node.whereClause { + if let genericWhereClause = node.genericWhereClause { if needsBreakBeforeWhereClause { - before(whereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same)) + before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same)) } - before(whereClause.firstToken(viewMode: .sourceAccurate), tokens: .open) - after(whereClause.lastToken(viewMode: .sourceAccurate), tokens: .close) + before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(genericWhereClause.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -2571,9 +2571,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // The comma after originalDeclName is optional and is only present if there are diffParams. after(node.comma ?? node.originalDeclName.lastToken(viewMode: .sourceAccurate), tokens: .close) - if let diffParams = node.diffParams { - before(diffParams.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) - after(diffParams.lastToken(viewMode: .sourceAccurate), tokens: .close) + if let parameters = node.parameters { + before(parameters.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) + after(parameters.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren @@ -3069,7 +3069,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // If we're at the end of the file, determine at which index to stop checking trivia pieces to // prevent trailing newlines. var cutoffIndex: Int? = nil - if token.tokenKind == TokenKind.eof { + if token.tokenKind == TokenKind.endOfFile { cutoffIndex = 0 for (index, piece) in trivia.enumerated() { switch piece { diff --git a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift index 0c8380c37..d159dd68c 100644 --- a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift @@ -67,14 +67,14 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { } public override func visit(_ node: ClosureSignatureSyntax) -> SyntaxVisitorContinueKind { - if let input = node.input { - if let closureParamList = input.as(ClosureParamListSyntax.self) { + if let parameterClause = node.parameterClause { + if let closureParamList = parameterClause.as(ClosureParamListSyntax.self) { for param in closureParamList { diagnoseLowerCamelCaseViolations( param.name, allowUnderscores: false, description: identifierDescription(for: node)) } - } else if let parameterClause = input.as(ClosureParameterClauseSyntax.self) { - for param in parameterClause.parameterList { + } else if let closureParameterClause = parameterClause.as(ClosureParameterClauseSyntax.self) { + for param in closureParameterClause.parameterList { diagnoseLowerCamelCaseViolations( param.firstName, allowUnderscores: false, description: identifierDescription(for: node)) if let secondName = param.secondName { @@ -82,8 +82,8 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { secondName, allowUnderscores: false, description: identifierDescription(for: node)) } } - } else if let parameterClause = input.as(EnumCaseParameterClauseSyntax.self) { - for param in parameterClause.parameterList { + } else if let enumCaseParameterClause = parameterClause.as(EnumCaseParameterClauseSyntax.self) { + for param in enumCaseParameterClause.parameterList { if let firstName = param.firstName { diagnoseLowerCamelCaseViolations( firstName, allowUnderscores: false, description: identifierDescription(for: node)) @@ -93,7 +93,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { secondName, allowUnderscores: false, description: identifierDescription(for: node)) } } - } else if let parameterClause = input.as(ParameterClauseSyntax.self) { + } else if let parameterClause = parameterClause.as(ParameterClauseSyntax.self) { for param in parameterClause.parameterList { diagnoseLowerCamelCaseViolations( param.firstName, allowUnderscores: false, description: identifierDescription(for: node)) @@ -121,7 +121,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { diagnoseLowerCamelCaseViolations( node.identifier, allowUnderscores: allowUnderscores, description: identifierDescription(for: node)) - for param in node.signature.input.parameterList { + for param in node.signature.parameterClause.parameterList { // These identifiers aren't described using `identifierDescription(for:)` because no single // node can disambiguate the argument label from the parameter name. diagnoseLowerCamelCaseViolations( @@ -158,8 +158,8 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { // Identify test methods using the same heuristics as XCTest: name starts with "test", has // no arguments, and returns a void type. if functionDecl.identifier.text.starts(with: "test") - && functionDecl.signature.input.parameterList.isEmpty - && (functionDecl.signature.output.map(\.isVoid) ?? true) + && functionDecl.signature.parameterClause.parameterList.isEmpty + && (functionDecl.signature.returnClause.map(\.isVoid) ?? true) { set.insert(functionDecl) } @@ -189,9 +189,9 @@ fileprivate func identifierDescription(for node: NodeT case .enumCaseElement: return "enum case" case .functionDecl: return "function" case .optionalBindingCondition(let binding): - return binding.bindingKeyword.tokenKind == .keyword(.var) ? "variable" : "constant" + return binding.bindingSpecifier.tokenKind == .keyword(.var) ? "variable" : "constant" case .variableDecl(let variableDecl): - return variableDecl.bindingKeyword.tokenKind == .keyword(.var) ? "variable" : "constant" + return variableDecl.bindingSpecifier.tokenKind == .keyword(.var) ? "variable" : "constant" default: return "identifier" } diff --git a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift index 89741008f..e3c51bbd0 100644 --- a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift +++ b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift @@ -39,7 +39,7 @@ public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { var overloads = [String: [FunctionDeclSyntax]]() var staticOverloads = [String: [FunctionDeclSyntax]]() for fn in functions { - let params = fn.signature.input.parameterList + let params = fn.signature.parameterClause.parameterList guard let firstParam = params.firstAndOnly else { continue } guard firstParam.type.is(FunctionTypeSyntax.self) else { continue } if let mods = fn.modifiers, mods.has(modifier: "static") || mods.has(modifier: "class") { diff --git a/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift b/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift index 75e5e2a67..2d3f3f99d 100644 --- a/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift +++ b/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift @@ -15,7 +15,7 @@ import SwiftSyntax extension FunctionDeclSyntax { /// Constructs a name for a function that includes parameter labels, i.e. `foo(_:bar:)`. var fullDeclName: String { - let params = signature.input.parameterList.map { param in + let params = signature.parameterClause.parameterList.map { param in "\(param.firstName.text):" } return "\(identifier.text)(\(params.joined()))" diff --git a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift b/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift index 3c6f40fe8..33685f543 100644 --- a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift +++ b/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift @@ -24,13 +24,13 @@ public final class NoVoidReturnOnFunctionSignature: SyntaxFormatRule { /// it for closure signatures, because that may introduce an ambiguity when closure signatures /// are inferred. public override func visit(_ node: FunctionSignatureSyntax) -> FunctionSignatureSyntax { - if let ret = node.output?.returnType.as(SimpleTypeIdentifierSyntax.self), ret.name.text == "Void" { - diagnose(.removeRedundantReturn("Void"), on: ret) - return node.with(\.output, nil) + if let returnType = node.returnClause?.returnType.as(SimpleTypeIdentifierSyntax.self), returnType.name.text == "Void" { + diagnose(.removeRedundantReturn("Void"), on: returnType) + return node.with(\.returnClause, nil) } - if let tup = node.output?.returnType.as(TupleTypeSyntax.self), tup.elements.isEmpty { - diagnose(.removeRedundantReturn("()"), on: tup) - return node.with(\.output, nil) + if let tupleReturnType = node.returnClause?.returnType.as(TupleTypeSyntax.self), tupleReturnType.elements.isEmpty { + diagnose(.removeRedundantReturn("()"), on: tupleReturnType) + return node.with(\.returnClause, nil) } return node } diff --git a/Sources/SwiftFormatRules/OrderedImports.swift b/Sources/SwiftFormatRules/OrderedImports.swift index 9b3e9730a..8fd189486 100644 --- a/Sources/SwiftFormatRules/OrderedImports.swift +++ b/Sources/SwiftFormatRules/OrderedImports.swift @@ -523,7 +523,7 @@ fileprivate class Line { { return .testableImport } - if importDecl.importKind != nil { + if importDecl.importKindSpecifier != nil { return .declImport } return .regularImport diff --git a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift index 8135db047..34828ec06 100644 --- a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift @@ -23,7 +23,7 @@ import SwiftSyntax /// Format: `-> ()` is replaced with `-> Void` public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { public override func visit(_ node: FunctionTypeSyntax) -> TypeSyntax { - guard let returnType = node.output.returnType.as(TupleTypeSyntax.self), + guard let returnType = node.returnClause.returnType.as(TupleTypeSyntax.self), returnType.elements.count == 0 else { return super.visit(node) @@ -45,13 +45,13 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { let voidKeyword = makeVoidIdentifierType(toReplace: returnType) var rewrittenNode = node rewrittenNode.parameters = parameters - rewrittenNode.output.returnType = TypeSyntax(voidKeyword) + rewrittenNode.returnClause.returnType = TypeSyntax(voidKeyword) return TypeSyntax(rewrittenNode) } public override func visit(_ node: ClosureSignatureSyntax) -> ClosureSignatureSyntax { - guard let output = node.output, - let returnType = output.returnType.as(TupleTypeSyntax.self), + guard let returnClause = node.returnClause, + let returnType = returnClause.returnType.as(TupleTypeSyntax.self), returnType.elements.count == 0 else { return super.visit(node) @@ -67,20 +67,20 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { return super.visit(node) } - let input: ClosureSignatureSyntax.Input? - switch node.input { + let closureParameterClause: ClosureSignatureSyntax.ParameterClause? + switch node.parameterClause { case .parameterClause(let parameterClause)?: // If the closure input is a complete parameter clause (variables and types), make sure that // nested function types are also rewritten (for example, `label: (Int -> ()) -> ()` should // become `label: (Int -> Void) -> Void`). - input = .input(visit(parameterClause)) + closureParameterClause = .parameterClause(visit(parameterClause)) default: // Otherwise, it's a simple signature (just variable names, no types), so there is nothing to // rewrite. - input = node.input + closureParameterClause = node.parameterClause } let voidKeyword = makeVoidIdentifierType(toReplace: returnType) - return node.with(\.input, input).with(\.output, output.with(\.returnType, TypeSyntax(voidKeyword))) + return node.with(\.parameterClause, closureParameterClause).with(\.returnClause, returnClause.with(\.returnType, TypeSyntax(voidKeyword))) } /// Returns a value indicating whether the leading trivia of the given token contained any diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 4ce6dea3e..ef52d214c 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -418,8 +418,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { parameters: functionType.parameters, rightParen: functionType.rightParen, effectSpecifiers: functionType.effectSpecifiers, - arrow: functionType.output.arrow, - returnType: functionType.output.returnType + arrow: functionType.returnClause.arrow, + returnType: functionType.returnClause.returnType ) return ExprSyntax(result) @@ -514,7 +514,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { for accessorDecl in accessorBlock.accessors { // Look for accessors that indicate that this is a computed property. If none are found, then // it is a stored property (e.g., having only observers like `willSet/didSet`). - switch accessorDecl.accessorKind.tokenKind { + switch accessorDecl.accessorSpecifier.tokenKind { case .keyword(.get), .keyword(.set), .keyword(.unsafeAddress), @@ -540,7 +540,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { isStoredProperty(patternBinding), patternBinding.initializer == nil, let variableDecl = nearestAncestor(of: patternBinding, type: VariableDeclSyntax.self), - variableDecl.bindingKeyword.tokenKind == .keyword(.var) + variableDecl.bindingSpecifier.tokenKind == .keyword(.var) { return true } diff --git a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift index 24f569f12..46106132e 100644 --- a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift @@ -26,7 +26,7 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { let acc = accessorBlock.accessors.first, let body = acc.body, accessorBlock.accessors.count == 1, - acc.accessorKind.tokenKind == .keyword(.get), + acc.accessorSpecifier.tokenKind == .keyword(.get), acc.attributes == nil, acc.modifier == nil, acc.effectSpecifiers == nil diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift index 9a5a654c2..89f690bc4 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift @@ -51,7 +51,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { for initializer in initializers { guard matchesPropertyList( - parameters: initializer.signature.input.parameterList, + parameters: initializer.signature.parameterClause.parameterList, properties: storedProperties) else { continue } guard @@ -115,7 +115,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { // Ensure that parameters that correspond to properties declared using 'var' have a default // argument that is identical to the property's default value. Otherwise, a default argument // doesn't match the memberwise initializer. - let isVarDecl = property.bindingKeyword.tokenKind == .keyword(.var) + let isVarDecl = property.bindingSpecifier.tokenKind == .keyword(.var) if isVarDecl, let initializer = property.firstInitializer { guard let defaultArg = parameter.defaultArgument else { return false } guard initializer.value.description == defaultArg.value.description else { return false } diff --git a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift index d4788265a..710eeb578 100644 --- a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift @@ -35,7 +35,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { public override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { return checkFunctionLikeDocumentation( DeclSyntax(node), name: node.identifier.text, signature: node.signature, - returnClause: node.signature.output) + returnClause: node.signature.returnClause) } private func checkFunctionLikeDocumentation( @@ -71,7 +71,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { name: name, returnsDescription: docComment.returns, node: node) - let funcParameters = funcParametersIdentifiers(in: signature.input.parameterList) + let funcParameters = funcParametersIdentifiers(in: signature.parameterClause.parameterList) // If the documentation of the parameters is wrong 'docCommentInfo' won't // parse the parameters correctly. First the documentation has to be fix diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index 6b7db1228..d0a778159 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -125,7 +125,7 @@ final class RuleCollector { for member in members { guard let function = member.decl.as(FunctionDeclSyntax.self) else { continue } guard function.identifier.text == "visit" else { continue } - let params = function.signature.input.parameterList + let params = function.signature.parameterClause.parameterList guard let firstType = params.firstAndOnly?.type.as(SimpleTypeIdentifierSyntax.self) else { continue } From b1cbfa4a5baa28b5e7c0cb622fd10094a99c94da Mon Sep 17 00:00:00 2001 From: kitasuke Date: Sun, 16 Jul 2023 16:52:09 +0900 Subject: [PATCH 063/332] Add exclusion comment for lint rules --- Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift | 3 +++ Sources/SwiftFormatRules/NeverForceUnwrap.swift | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift index 0c8380c37..12ea92ef8 100644 --- a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift @@ -16,6 +16,9 @@ import SwiftSyntax /// All values should be written in lower camel-case (`lowerCamelCase`). /// Underscores (except at the beginning of an identifier) are disallowed. /// +/// This rule does not apply to test code, defined as code which: +/// * Contains the line `import XCTest` +/// /// Lint: If an identifier contains underscores or begins with a capital letter, a lint error is /// raised. public final class AlwaysUseLowerCamelCase: SyntaxLintRule { diff --git a/Sources/SwiftFormatRules/NeverForceUnwrap.swift b/Sources/SwiftFormatRules/NeverForceUnwrap.swift index 4a50007f5..5c1b7da6f 100644 --- a/Sources/SwiftFormatRules/NeverForceUnwrap.swift +++ b/Sources/SwiftFormatRules/NeverForceUnwrap.swift @@ -15,6 +15,9 @@ import SwiftSyntax /// Force-unwraps are strongly discouraged and must be documented. /// +/// This rule does not apply to test code, defined as code which: +/// * Contains the line `import XCTest` +/// /// Lint: If a force unwrap is used, a lint warning is raised. public final class NeverForceUnwrap: SyntaxLintRule { From 10bc3d8ff58aec0b085c6e5906a783bda5b0e0a5 Mon Sep 17 00:00:00 2001 From: kitasuke Date: Mon, 17 Jul 2023 20:57:05 +0900 Subject: [PATCH 064/332] Use methods on Sequence instead of SyntaxCollection --- .../ModifierListSyntax+Convenience.swift | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift index 7a4b82564..1242ab779 100644 --- a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift +++ b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift @@ -37,13 +37,8 @@ extension ModifierListSyntax { /// Returns modifier list without the given modifier. func remove(name: String) -> ModifierListSyntax { - guard has(modifier: name) else { return self } - for (index, mod) in self.enumerated() { - if mod.name.text == name { - return removing(childAt: index) - } - } - return self + let newModifiers = filter { $0.name.text != name } + return ModifierListSyntax(newModifiers) } /// Returns a formatted declaration modifier token with the given name. @@ -71,9 +66,13 @@ extension ModifierListSyntax { trailingTrivia: .spaces(1)) : modifier if index == 0 { - guard formatTrivia else { return inserting(modifier, at: index) } + guard formatTrivia else { + newModifiers.insert(modifier, at: index) + return ModifierListSyntax(newModifiers) + } guard let firstMod = first, let firstTok = firstMod.firstToken(viewMode: .sourceAccurate) else { - return inserting(modifier, at: index) + newModifiers.insert(modifier, at: index) + return ModifierListSyntax(newModifiers) } let formattedMod = replaceTrivia( on: modifier, @@ -87,7 +86,8 @@ extension ModifierListSyntax { newModifiers.insert(formattedMod, at: 0) return ModifierListSyntax(newModifiers) } else { - return inserting(modifier, at: index) + newModifiers.insert(modifier, at: index) + return ModifierListSyntax(newModifiers) } } From bee97be5aef3c1f290b53f847e9082e47dacd32a Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 17 Jul 2023 09:09:40 -0700 Subject: [PATCH 065/332] Update swift-format for renamed children in SwiftSyntax --- .../TokenStreamCreator.swift | 112 +++++++++--------- ...lPublicDeclarationsHaveDocumentation.swift | 8 +- .../AlwaysUseLowerCamelCase.swift | 32 ++--- .../AmbiguousTrailingClosureOverload.swift | 10 +- .../DontRepeatTypeInStaticProperties.swift | 8 +- .../SwiftFormatRules/FullyIndirectEnum.swift | 2 +- .../FunctionDeclSyntax+Convenience.swift | 4 +- .../SwiftFormatRules/NeverForceUnwrap.swift | 2 +- .../NoEmptyTrailingClosureParentheses.swift | 4 +- .../NoLabelsInCasePatterns.swift | 6 +- .../NoLeadingUnderscores.swift | 18 +-- .../NoVoidReturnOnFunctionSignature.swift | 4 +- Sources/SwiftFormatRules/OneCasePerLine.swift | 2 +- .../OnlyOneTrailingClosureArgument.swift | 2 +- .../ReturnVoidInsteadOfEmptyTuple.swift | 8 +- .../UseLetInEveryBoundCaseVariable.swift | 2 +- .../UseShorthandTypeNames.swift | 36 +++--- .../UseSingleLinePropertyGetter.swift | 4 +- .../UseSynthesizedInitializer.swift | 2 +- .../UseWhereClausesInForLoops.swift | 2 +- .../ValidateDocumentationComments.swift | 6 +- Sources/generate-pipeline/RuleCollector.swift | 12 +- 22 files changed, 143 insertions(+), 143 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index abb3aee56..951d0a98d 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -158,7 +158,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { attributes: node.attributes, modifiers: node.modifiers, typeKeyword: node.classKeyword, - identifier: node.identifier, + identifier: node.name, genericParameterOrPrimaryAssociatedTypeClause: node.genericParameterClause.map(Syntax.init), inheritanceClause: node.inheritanceClause, genericWhereClause: node.genericWhereClause, @@ -172,7 +172,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { attributes: node.attributes, modifiers: node.modifiers, typeKeyword: node.actorKeyword, - identifier: node.identifier, + identifier: node.name, genericParameterOrPrimaryAssociatedTypeClause: node.genericParameterClause.map(Syntax.init), inheritanceClause: node.inheritanceClause, genericWhereClause: node.genericWhereClause, @@ -186,7 +186,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { attributes: node.attributes, modifiers: node.modifiers, typeKeyword: node.structKeyword, - identifier: node.identifier, + identifier: node.name, genericParameterOrPrimaryAssociatedTypeClause: node.genericParameterClause.map(Syntax.init), inheritanceClause: node.inheritanceClause, genericWhereClause: node.genericWhereClause, @@ -200,7 +200,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { attributes: node.attributes, modifiers: node.modifiers, typeKeyword: node.enumKeyword, - identifier: node.identifier, + identifier: node.name, genericParameterOrPrimaryAssociatedTypeClause: node.genericParameterClause.map(Syntax.init), inheritanceClause: node.inheritanceClause, genericWhereClause: node.genericWhereClause, @@ -214,7 +214,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { attributes: node.attributes, modifiers: node.modifiers, typeKeyword: node.protocolKeyword, - identifier: node.identifier, + identifier: node.name, genericParameterOrPrimaryAssociatedTypeClause: node.primaryAssociatedTypeClause.map(Syntax.init), inheritanceClause: node.inheritanceClause, @@ -248,7 +248,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { arrangeAttributeList(node.attributes) - let hasArguments = !node.signature.parameterClause.parameterList.isEmpty + let hasArguments = !node.signature.parameterClause.parameters.isEmpty // Prioritize keeping ") -> " together. We can only do this if the macro has // arguments. @@ -327,7 +327,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // MARK: - Function and function-like declaration nodes (initializers, deinitializers, subscripts) override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { - let hasArguments = !node.signature.parameterClause.parameterList.isEmpty + let hasArguments = !node.signature.parameterClause.parameters.isEmpty // Prioritize keeping ") throws -> " together. We can only do this if the function // has arguments. @@ -354,8 +354,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // from the following parenthesis or generic argument list. Note that even if the function is // defining a prefix or postfix operator, the token kind always comes through as // `binaryOperator`. - if case .binaryOperator = node.identifier.tokenKind { - after(node.identifier.lastToken(viewMode: .sourceAccurate), tokens: .space) + if case .binaryOperator = node.name.tokenKind { + after(node.name.lastToken(viewMode: .sourceAccurate), tokens: .space) } arrangeFunctionLikeDecl( @@ -369,7 +369,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { - let hasArguments = !node.signature.parameterClause.parameterList.isEmpty + let hasArguments = !node.signature.parameterClause.parameters.isEmpty // Prioritize keeping ") throws" together. We can only do this if the function // has arguments. @@ -411,7 +411,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind { - let hasArguments = !node.parameterClause.parameterList.isEmpty + let hasArguments = !node.parameterClause.parameters.isEmpty before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) @@ -442,7 +442,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.returnClause.firstToken(viewMode: .sourceAccurate), tokens: .break) - if let accessorOrCodeBlock = node.accessor { + if let accessorOrCodeBlock = node.accessors { switch accessorOrCodeBlock { case .accessors(let accessorBlock): arrangeBracesAndContents(of: accessorBlock) @@ -1030,13 +1030,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } - let arguments = node.argumentList + let arguments = node.arguments // If there is a trailing closure, force the right parenthesis down to the next line so it // stays with the open curly brace. let breakBeforeRightParen = (node.trailingClosure != nil && !isCompactSingleFunctionCallArgument(arguments)) - || mustBreakBeforeClosingDelimiter(of: node, argumentListPath: \.argumentList) + || mustBreakBeforeClosingDelimiter(of: node, argumentListPath: \.arguments) before( node.trailingClosure?.leftBrace, @@ -1200,7 +1200,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Whether we should prioritize keeping ") throws -> " together. We can only do // this if the closure has arguments. let keepOutputTogether = - !closureParameterClause.parameterList.isEmpty && config.prioritizeKeepingFunctionOutputTogether + !closureParameterClause.parameters.isEmpty && config.prioritizeKeepingFunctionOutputTogether // Keep the output together by grouping from the right paren to the end of the output. if keepOutputTogether { @@ -1260,13 +1260,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } - let arguments = node.argumentList + let arguments = node.arguments // If there is a trailing closure, force the right bracket down to the next line so it stays // with the open curly brace. let breakBeforeRightBracket = node.trailingClosure != nil - || mustBreakBeforeClosingDelimiter(of: node, argumentListPath: \.argumentList) + || mustBreakBeforeClosingDelimiter(of: node, argumentListPath: \.arguments) before( node.trailingClosure?.leftBrace, @@ -1296,7 +1296,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: MacroExpansionDeclSyntax) -> SyntaxVisitorContinueKind { arrangeFunctionCallArgumentList( - node.argumentList, + node.arguments, leftDelimiter: node.leftParen, rightDelimiter: node.rightParen, forcesBreakBeforeRightDelimiter: false) @@ -1304,13 +1304,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind { - let arguments = node.argumentList + let arguments = node.arguments // If there is a trailing closure, force the right parenthesis down to the next line so it // stays with the open curly brace. let breakBeforeRightParen = (node.trailingClosure != nil && !isCompactSingleFunctionCallArgument(arguments)) - || mustBreakBeforeClosingDelimiter(of: node, argumentListPath: \.argumentList) + || mustBreakBeforeClosingDelimiter(of: node, argumentListPath: \.arguments) before( node.trailingClosure?.leftBrace, @@ -1327,7 +1327,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: ClosureParameterClauseSyntax) -> SyntaxVisitorContinueKind { // Prioritize keeping ") throws -> " together. We can only do this if the function // has arguments. - if !node.parameterList.isEmpty && config.prioritizeKeepingFunctionOutputTogether { + if !node.parameters.isEmpty && config.prioritizeKeepingFunctionOutputTogether { // Due to visitation order, this .open corresponds to a .close added in FunctionDeclSyntax // or SubscriptDeclSyntax. before(node.rightParen, tokens: .open) @@ -1339,7 +1339,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: EnumCaseParameterClauseSyntax) -> SyntaxVisitorContinueKind { // Prioritize keeping ") throws -> " together. We can only do this if the function // has arguments. - if !node.parameterList.isEmpty && config.prioritizeKeepingFunctionOutputTogether { + if !node.parameters.isEmpty && config.prioritizeKeepingFunctionOutputTogether { // Due to visitation order, this .open corresponds to a .close added in FunctionDeclSyntax // or SubscriptDeclSyntax. before(node.rightParen, tokens: .open) @@ -1351,7 +1351,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: ParameterClauseSyntax) -> SyntaxVisitorContinueKind { // Prioritize keeping ") throws -> " together. We can only do this if the function // has arguments. - if !node.parameterList.isEmpty && config.prioritizeKeepingFunctionOutputTogether { + if !node.parameters.isEmpty && config.prioritizeKeepingFunctionOutputTogether { // Due to visitation order, this .open corresponds to a .close added in FunctionDeclSyntax // or SubscriptDeclSyntax. before(node.rightParen, tokens: .open) @@ -1414,16 +1414,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // `ReturnClauseSyntax`. To maintain the previous formatting behavior, // add a special case. before(node.arrow, tokens: .break) - before(node.returnType.firstToken(viewMode: .sourceAccurate), tokens: .break) + before(node.type.firstToken(viewMode: .sourceAccurate), tokens: .break) } else { after(node.arrow, tokens: .space) } // Member type identifier is used when the return type is a member of another type. Add a group // here so that the base, dot, and member type are kept together when they fit. - if node.returnType.is(MemberTypeIdentifierSyntax.self) { - before(node.returnType.firstToken(viewMode: .sourceAccurate), tokens: .open) - after(node.returnType.lastToken(viewMode: .sourceAccurate), tokens: .close) + if node.type.is(MemberTypeIdentifierSyntax.self) { + before(node.type.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(node.type.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -1434,9 +1434,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: IfConfigClauseSyntax) -> SyntaxVisitorContinueKind { switch node.poundKeyword.tokenKind { - case .poundIfKeyword, .poundElseifKeyword: + case .poundIf, .poundElseif: after(node.poundKeyword, tokens: .space) - case .poundElseKeyword: + case .poundElse: break default: preconditionFailure() @@ -1575,7 +1575,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: PrecedenceGroupDeclSyntax) -> SyntaxVisitorContinueKind { after(node.precedencegroupKeyword, tokens: .break) - after(node.identifier, tokens: .break(.reset)) + after(node.name, tokens: .break(.reset)) after(node.leftBrace, tokens: .break(.open, newlines: .soft)) before(node.rightBrace, tokens: .break(.close)) return .visitChildren @@ -1757,7 +1757,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AttributeSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) - switch node.argument { + switch node.arguments { case .argumentList(let argumentList)?: if let leftParen = node.leftParen, let rightParen = node.rightParen { arrangeFunctionCallArgumentList( @@ -2054,14 +2054,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { before(node.asKeyword, tokens: .break(.continue), .open) - before(node.typeName.firstToken(viewMode: .sourceAccurate), tokens: .space) + before(node.type.firstToken(viewMode: .sourceAccurate), tokens: .space) after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } override func visit(_ node: IsExprSyntax) -> SyntaxVisitorContinueKind { before(node.isKeyword, tokens: .break(.continue), .open) - before(node.typeName.firstToken(viewMode: .sourceAccurate), tokens: .space) + before(node.type.firstToken(viewMode: .sourceAccurate), tokens: .space) after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } @@ -2160,7 +2160,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } - if let accessorOrCodeBlock = node.accessor { + if let accessorOrCodeBlock = node.accessors { switch accessorOrCodeBlock { case .accessors(let accessorBlock): arrangeBracesAndContents(of: accessorBlock) @@ -2398,8 +2398,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.whereKeyword, tokens: .break(.open)) after(node.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0)) - before(node.requirementList.firstToken(viewMode: .sourceAccurate), tokens: .open(genericRequirementListConsistency())) - after(node.requirementList.lastToken(viewMode: .sourceAccurate), tokens: .close) + before(node.requirements.firstToken(viewMode: .sourceAccurate), tokens: .open(genericRequirementListConsistency())) + after(node.requirements.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } @@ -2423,8 +2423,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: SameTypeRequirementSyntax) -> SyntaxVisitorContinueKind { - before(node.equalityToken, tokens: .break) - after(node.equalityToken, tokens: .space) + before(node.equal, tokens: .break) + after(node.equal, tokens: .space) return .visitChildren } @@ -2469,8 +2469,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // 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 // breaks if the first item in the list would overflow the column limit. - before(node.inheritedTypeCollection.firstToken(viewMode: .sourceAccurate), tokens: .open, .break(.open, size: 1)) - after(node.inheritedTypeCollection.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0), .close) + before(node.inheritedTypes.firstToken(viewMode: .sourceAccurate), tokens: .open, .break(.open, size: 1)) + after(node.inheritedTypes.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0), .close) return .visitChildren } @@ -2532,20 +2532,20 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // This node encapsulates the entire list of arguments in a `@differentiable(...)` attribute. var needsBreakBeforeWhereClause = false - if let parametersComma = node.parametersComma { - after(parametersComma, tokens: .break(.same)) + if let diffParamsComma = node.parametersComma { + after(diffParamsComma, tokens: .break(.same)) } else if node.parameters != nil { // If there were diff params but no comma following them, then we have "wrt: foo where ..." // and we need a break before the where clause. needsBreakBeforeWhereClause = true } - if let genericWhereClause = node.genericWhereClause { + if let whereClause = node.genericWhereClause { if needsBreakBeforeWhereClause { - before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same)) + before(whereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same)) } - before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .open) - after(genericWhereClause.lastToken(viewMode: .sourceAccurate), tokens: .close) + before(whereClause.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(whereClause.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren } @@ -2571,9 +2571,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // The comma after originalDeclName is optional and is only present if there are diffParams. after(node.comma ?? node.originalDeclName.lastToken(viewMode: .sourceAccurate), tokens: .close) - if let parameters = node.parameters { - before(parameters.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) - after(parameters.lastToken(viewMode: .sourceAccurate), tokens: .close) + if let diffParams = node.parameters { + before(diffParams.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) + after(diffParams.lastToken(viewMode: .sourceAccurate), tokens: .close) } return .visitChildren @@ -2817,7 +2817,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func arrangeClosureParameterClause( _ parameters: ClosureParameterClauseSyntax, forcesBreakBeforeRightParen: Bool ) { - guard !parameters.parameterList.isEmpty else { return } + guard !parameters.parameters.isEmpty else { return } after(parameters.leftParen, tokens: .break(.open, size: 0), .open(argumentListConsistency())) before( @@ -2835,7 +2835,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func arrangeEnumCaseParameterClause( _ parameters: EnumCaseParameterClauseSyntax, forcesBreakBeforeRightParen: Bool ) { - guard !parameters.parameterList.isEmpty else { return } + guard !parameters.parameters.isEmpty else { return } after(parameters.leftParen, tokens: .break(.open, size: 0), .open(argumentListConsistency())) before( @@ -2853,7 +2853,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func arrangeParameterClause( _ parameters: ParameterClauseSyntax, forcesBreakBeforeRightParen: Bool ) { - guard !parameters.parameterList.isEmpty else { return } + guard !parameters.parameters.isEmpty else { return } after(parameters.leftParen, tokens: .break(.open, size: 0), .open(argumentListConsistency())) before( @@ -3431,7 +3431,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { case .infixOperatorExpr(let infixOperatorExpr): return parenthesizedLeftmostExpr(of: infixOperatorExpr.leftOperand) case .ternaryExpr(let ternaryExpr): - return parenthesizedLeftmostExpr(of: ternaryExpr.conditionExpression) + return parenthesizedLeftmostExpr(of: ternaryExpr.condition) default: return nil } @@ -3465,9 +3465,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { case .postfixUnaryExpr(let postfixUnaryExpr): return leftmostExpr(of: postfixUnaryExpr.expression, ifMatching: predicate) case .prefixOperatorExpr(let prefixOperatorExpr): - return leftmostExpr(of: prefixOperatorExpr.postfixExpression, ifMatching: predicate) + return leftmostExpr(of: prefixOperatorExpr.base, ifMatching: predicate) case .ternaryExpr(let ternaryExpr): - return leftmostExpr(of: ternaryExpr.conditionExpression, ifMatching: predicate) + return leftmostExpr(of: ternaryExpr.condition, ifMatching: predicate) case .functionCallExpr(let functionCallExpr): return leftmostExpr(of: functionCallExpr.calledExpression, ifMatching: predicate) case .subscriptExpr(let subscriptExpr): @@ -3565,7 +3565,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // We don't try to absorb any parens in this case, because the condition of a ternary cannot // be grouped with any exprs outside of the condition. return ( - unindentingNode: Syntax(ternaryExpr.conditionExpression), + unindentingNode: Syntax(ternaryExpr.condition), shouldReset: false, breakKind: .continuation, shouldGroup: true diff --git a/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift b/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift index 5bd6d6bee..fdd60ec5f 100644 --- a/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift +++ b/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift @@ -45,7 +45,7 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { } public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseMissingDocComment(DeclSyntax(node), name: node.identifier.text, modifiers: node.modifiers) + diagnoseMissingDocComment(DeclSyntax(node), name: node.name.text, modifiers: node.modifiers) return .skipChildren } @@ -56,17 +56,17 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { } public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseMissingDocComment(DeclSyntax(node), name: node.identifier.text, modifiers: node.modifiers) + diagnoseMissingDocComment(DeclSyntax(node), name: node.name.text, modifiers: node.modifiers) return .skipChildren } public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseMissingDocComment(DeclSyntax(node), name: node.identifier.text, modifiers: node.modifiers) + diagnoseMissingDocComment(DeclSyntax(node), name: node.name.text, modifiers: node.modifiers) return .skipChildren } public override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseMissingDocComment(DeclSyntax(node), name: node.identifier.text, modifiers: node.modifiers) + diagnoseMissingDocComment(DeclSyntax(node), name: node.name.text, modifiers: node.modifiers) return .skipChildren } diff --git a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift index 54fcc8004..196bb5682 100644 --- a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift @@ -70,14 +70,14 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { } public override func visit(_ node: ClosureSignatureSyntax) -> SyntaxVisitorContinueKind { - if let parameterClause = node.parameterClause { - if let closureParamList = parameterClause.as(ClosureParamListSyntax.self) { + if let input = node.parameterClause { + if let closureParamList = input.as(ClosureParamListSyntax.self) { for param in closureParamList { diagnoseLowerCamelCaseViolations( param.name, allowUnderscores: false, description: identifierDescription(for: node)) } - } else if let closureParameterClause = parameterClause.as(ClosureParameterClauseSyntax.self) { - for param in closureParameterClause.parameterList { + } else if let parameterClause = input.as(ClosureParameterClauseSyntax.self) { + for param in parameterClause.parameters { diagnoseLowerCamelCaseViolations( param.firstName, allowUnderscores: false, description: identifierDescription(for: node)) if let secondName = param.secondName { @@ -85,8 +85,8 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { secondName, allowUnderscores: false, description: identifierDescription(for: node)) } } - } else if let enumCaseParameterClause = parameterClause.as(EnumCaseParameterClauseSyntax.self) { - for param in enumCaseParameterClause.parameterList { + } else if let parameterClause = input.as(EnumCaseParameterClauseSyntax.self) { + for param in parameterClause.parameters { if let firstName = param.firstName { diagnoseLowerCamelCaseViolations( firstName, allowUnderscores: false, description: identifierDescription(for: node)) @@ -96,8 +96,8 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { secondName, allowUnderscores: false, description: identifierDescription(for: node)) } } - } else if let parameterClause = parameterClause.as(ParameterClauseSyntax.self) { - for param in parameterClause.parameterList { + } else if let parameterClause = input.as(ParameterClauseSyntax.self) { + for param in parameterClause.parameters { diagnoseLowerCamelCaseViolations( param.firstName, allowUnderscores: false, description: identifierDescription(for: node)) if let secondName = param.secondName { @@ -122,9 +122,9 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { // underscores to separate phrases in very detailed test names. let allowUnderscores = testCaseFuncs.contains(node) diagnoseLowerCamelCaseViolations( - node.identifier, allowUnderscores: allowUnderscores, + node.name, allowUnderscores: allowUnderscores, description: identifierDescription(for: node)) - for param in node.signature.parameterClause.parameterList { + for param in node.signature.parameterClause.parameters { // These identifiers aren't described using `identifierDescription(for:)` because no single // node can disambiguate the argument label from the parameter name. diagnoseLowerCamelCaseViolations( @@ -139,7 +139,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { public override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind { diagnoseLowerCamelCaseViolations( - node.identifier, allowUnderscores: false, description: identifierDescription(for: node)) + node.name, allowUnderscores: false, description: identifierDescription(for: node)) return .skipChildren } @@ -160,9 +160,9 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { } else if let functionDecl = member.decl.as(FunctionDeclSyntax.self) { // Identify test methods using the same heuristics as XCTest: name starts with "test", has // no arguments, and returns a void type. - if functionDecl.identifier.text.starts(with: "test") - && functionDecl.signature.parameterClause.parameterList.isEmpty - && (functionDecl.signature.returnClause.map(\.isVoid) ?? true) + if functionDecl.name.text.starts(with: "test") + && functionDecl.signature.parameterClause.parameters.isEmpty + && (functionDecl.signature.returnClause.map(\.isVoid) ?? true) { set.insert(functionDecl) } @@ -203,10 +203,10 @@ fileprivate func identifierDescription(for node: NodeT extension ReturnClauseSyntax { /// Whether this return clause specifies an explicit `Void` return type. fileprivate var isVoid: Bool { - if let returnTypeIdentifier = returnType.as(SimpleTypeIdentifierSyntax.self) { + if let returnTypeIdentifier = type.as(SimpleTypeIdentifierSyntax.self) { return returnTypeIdentifier.name.text == "Void" } - if let returnTypeTuple = returnType.as(TupleTypeSyntax.self) { + if let returnTypeTuple = type.as(TupleTypeSyntax.self) { return returnTypeTuple.elements.isEmpty } return false diff --git a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift index e3c51bbd0..985919eeb 100644 --- a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift +++ b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift @@ -24,12 +24,12 @@ public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { let decl = decls[0] diagnose( .ambiguousTrailingClosureOverload(decl.fullDeclName), - on: decl.identifier, + on: decl.name, notes: decls.dropFirst().map { decl in Finding.Note( message: .otherAmbiguousOverloadHere(decl.fullDeclName), location: Finding.Location( - decl.identifier.startLocation(converter: self.context.sourceLocationConverter)) + decl.name.startLocation(converter: self.context.sourceLocationConverter)) ) }) } @@ -39,13 +39,13 @@ public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { var overloads = [String: [FunctionDeclSyntax]]() var staticOverloads = [String: [FunctionDeclSyntax]]() for fn in functions { - let params = fn.signature.parameterClause.parameterList + let params = fn.signature.parameterClause.parameters guard let firstParam = params.firstAndOnly else { continue } guard firstParam.type.is(FunctionTypeSyntax.self) else { continue } if let mods = fn.modifiers, mods.has(modifier: "static") || mods.has(modifier: "class") { - staticOverloads[fn.identifier.text, default: []].append(fn) + staticOverloads[fn.name.text, default: []].append(fn) } else { - overloads[fn.identifier.text, default: []].append(fn) + overloads[fn.name.text, default: []].append(fn) } } diff --git a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift index 9bb3c4946..ecb19de9b 100644 --- a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift @@ -23,22 +23,22 @@ import SwiftSyntax public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.memberBlock.members, endingWith: node.identifier.text) + diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text) return .skipChildren } public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.memberBlock.members, endingWith: node.identifier.text) + diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text) return .skipChildren } public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.memberBlock.members, endingWith: node.identifier.text) + diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text) return .skipChildren } public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.memberBlock.members, endingWith: node.identifier.text) + diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text) return .skipChildren } diff --git a/Sources/SwiftFormatRules/FullyIndirectEnum.swift b/Sources/SwiftFormatRules/FullyIndirectEnum.swift index 2853f55ec..0a6cbfd76 100644 --- a/Sources/SwiftFormatRules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormatRules/FullyIndirectEnum.swift @@ -31,7 +31,7 @@ public final class FullyIndirectEnum: SyntaxFormatRule { return DeclSyntax(node) } - diagnose(.moveIndirectKeywordToEnumDecl(name: node.identifier.text), on: node.identifier) + diagnose(.moveIndirectKeywordToEnumDecl(name: node.name.text), on: node.name) // Removes 'indirect' keyword from cases, reformats let newMembers = enumMembers.map { diff --git a/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift b/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift index 2d3f3f99d..e0a98d0ec 100644 --- a/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift +++ b/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift @@ -15,9 +15,9 @@ import SwiftSyntax extension FunctionDeclSyntax { /// Constructs a name for a function that includes parameter labels, i.e. `foo(_:bar:)`. var fullDeclName: String { - let params = signature.parameterClause.parameterList.map { param in + let params = signature.parameterClause.parameters.map { param in "\(param.firstName.text):" } - return "\(identifier.text)(\(params.joined()))" + return "\(name.text)(\(params.joined()))" } } diff --git a/Sources/SwiftFormatRules/NeverForceUnwrap.swift b/Sources/SwiftFormatRules/NeverForceUnwrap.swift index 5c1b7da6f..bff517029 100644 --- a/Sources/SwiftFormatRules/NeverForceUnwrap.swift +++ b/Sources/SwiftFormatRules/NeverForceUnwrap.swift @@ -44,7 +44,7 @@ public final class NeverForceUnwrap: SyntaxLintRule { guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren } guard let questionOrExclamation = node.questionOrExclamationMark else { return .skipChildren } guard questionOrExclamation.tokenKind == .exclamationMark else { return .skipChildren } - diagnose(.doNotForceCast(name: node.typeName.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description), on: node) + diagnose(.doNotForceCast(name: node.type.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description), on: node) return .skipChildren } } diff --git a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift index d89365620..6b7602f8a 100644 --- a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift +++ b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift @@ -22,10 +22,10 @@ import SwiftSyntax public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { public override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax { - guard node.argumentList.count == 0 else { return super.visit(node) } + guard node.arguments.count == 0 else { return super.visit(node) } guard let trailingClosure = node.trailingClosure, - node.argumentList.isEmpty && node.leftParen != nil else + node.arguments.isEmpty && node.leftParen != nil else { return super.visit(node) } diff --git a/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift b/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift index 07d02c293..4371f123f 100644 --- a/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift +++ b/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift @@ -37,7 +37,7 @@ public final class NoLabelsInCasePatterns: SyntaxFormatRule { // Search function call argument list for violations var newArgs: [TupleExprElementSyntax] = [] - for argument in funcCall.argumentList { + for argument in funcCall.arguments { guard let label = argument.label else { newArgs.append(argument) continue @@ -50,7 +50,7 @@ public final class NoLabelsInCasePatterns: SyntaxFormatRule { } // Remove label if it's the same as the value identifier - let name = valueBinding.valuePattern.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description + let name = valueBinding.pattern.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description guard name == label.text else { newArgs.append(argument) continue @@ -60,7 +60,7 @@ public final class NoLabelsInCasePatterns: SyntaxFormatRule { } let newArgList = TupleExprElementListSyntax(newArgs) - let newFuncCall = funcCall.with(\.argumentList, newArgList) + let newFuncCall = funcCall.with(\.arguments, newArgList) let newExpPat = expPat.with(\.expression, ExprSyntax(newFuncCall)) let newItem = item.with(\.pattern, PatternSyntax(newExpPat)) newCaseItems.append(newItem) diff --git a/Sources/SwiftFormatRules/NoLeadingUnderscores.swift b/Sources/SwiftFormatRules/NoLeadingUnderscores.swift index e3f44900a..2b467dab7 100644 --- a/Sources/SwiftFormatRules/NoLeadingUnderscores.swift +++ b/Sources/SwiftFormatRules/NoLeadingUnderscores.swift @@ -32,27 +32,27 @@ public final class NoLeadingUnderscores: SyntaxLintRule { public override class var isOptIn: Bool { return true } public override func visit(_ node: AssociatedtypeDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseIfNameStartsWithUnderscore(node.identifier) + diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren } public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseIfNameStartsWithUnderscore(node.identifier) + diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren } public override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind { - diagnoseIfNameStartsWithUnderscore(node.identifier) + diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren } public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseIfNameStartsWithUnderscore(node.identifier) + diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren } public override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseIfNameStartsWithUnderscore(node.identifier) + diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren } @@ -93,22 +93,22 @@ public final class NoLeadingUnderscores: SyntaxLintRule { } public override func visit(_ node: PrecedenceGroupDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseIfNameStartsWithUnderscore(node.identifier) + diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren } public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseIfNameStartsWithUnderscore(node.identifier) + diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren } public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseIfNameStartsWithUnderscore(node.identifier) + diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren } public override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseIfNameStartsWithUnderscore(node.identifier) + diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren } diff --git a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift b/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift index 33685f543..6d91de3fa 100644 --- a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift +++ b/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift @@ -24,11 +24,11 @@ public final class NoVoidReturnOnFunctionSignature: SyntaxFormatRule { /// it for closure signatures, because that may introduce an ambiguity when closure signatures /// are inferred. public override func visit(_ node: FunctionSignatureSyntax) -> FunctionSignatureSyntax { - if let returnType = node.returnClause?.returnType.as(SimpleTypeIdentifierSyntax.self), returnType.name.text == "Void" { + if let returnType = node.returnClause?.type.as(SimpleTypeIdentifierSyntax.self), returnType.name.text == "Void" { diagnose(.removeRedundantReturn("Void"), on: returnType) return node.with(\.returnClause, nil) } - if let tupleReturnType = node.returnClause?.returnType.as(TupleTypeSyntax.self), tupleReturnType.elements.isEmpty { + if let tupleReturnType = node.returnClause?.type.as(TupleTypeSyntax.self), tupleReturnType.elements.isEmpty { diagnose(.removeRedundantReturn("()"), on: tupleReturnType) return node.with(\.returnClause, nil) } diff --git a/Sources/SwiftFormatRules/OneCasePerLine.swift b/Sources/SwiftFormatRules/OneCasePerLine.swift index d97210ef0..9cd1af903 100644 --- a/Sources/SwiftFormatRules/OneCasePerLine.swift +++ b/Sources/SwiftFormatRules/OneCasePerLine.swift @@ -103,7 +103,7 @@ public final class OneCasePerLine: SyntaxFormatRule { if element.associatedValue != nil || element.rawValue != nil { // Once we reach one of these, we need to write out the ones we've collected so far, then // emit a separate case declaration with the associated/raw value element. - diagnose(.moveAssociatedOrRawValueCase(name: element.identifier.text), on: element) + diagnose(.moveAssociatedOrRawValueCase(name: element.name.text), on: element) if let caseDeclForCollectedElements = collector.makeCaseDeclAndReset() { newMembers.append(member.with(\.decl, DeclSyntax(caseDeclForCollectedElements))) diff --git a/Sources/SwiftFormatRules/OnlyOneTrailingClosureArgument.swift b/Sources/SwiftFormatRules/OnlyOneTrailingClosureArgument.swift index 7b7c959e8..cf8b934cf 100644 --- a/Sources/SwiftFormatRules/OnlyOneTrailingClosureArgument.swift +++ b/Sources/SwiftFormatRules/OnlyOneTrailingClosureArgument.swift @@ -20,7 +20,7 @@ import SwiftSyntax public final class OnlyOneTrailingClosureArgument: SyntaxLintRule { public override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { - guard (node.argumentList.contains { $0.expression.is(ClosureExprSyntax.self) }) else { + guard (node.arguments.contains { $0.expression.is(ClosureExprSyntax.self) }) else { return .skipChildren } guard node.trailingClosure != nil else { return .skipChildren } diff --git a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift index 34828ec06..a862d9c82 100644 --- a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift @@ -23,7 +23,7 @@ import SwiftSyntax /// Format: `-> ()` is replaced with `-> Void` public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { public override func visit(_ node: FunctionTypeSyntax) -> TypeSyntax { - guard let returnType = node.returnClause.returnType.as(TupleTypeSyntax.self), + guard let returnType = node.returnClause.type.as(TupleTypeSyntax.self), returnType.elements.count == 0 else { return super.visit(node) @@ -45,13 +45,13 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { let voidKeyword = makeVoidIdentifierType(toReplace: returnType) var rewrittenNode = node rewrittenNode.parameters = parameters - rewrittenNode.returnClause.returnType = TypeSyntax(voidKeyword) + rewrittenNode.returnClause.type = TypeSyntax(voidKeyword) return TypeSyntax(rewrittenNode) } public override func visit(_ node: ClosureSignatureSyntax) -> ClosureSignatureSyntax { guard let returnClause = node.returnClause, - let returnType = returnClause.returnType.as(TupleTypeSyntax.self), + let returnType = returnClause.type.as(TupleTypeSyntax.self), returnType.elements.count == 0 else { return super.visit(node) @@ -80,7 +80,7 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { closureParameterClause = node.parameterClause } let voidKeyword = makeVoidIdentifierType(toReplace: returnType) - return node.with(\.parameterClause, closureParameterClause).with(\.returnClause, returnClause.with(\.returnType, TypeSyntax(voidKeyword))) + return node.with(\.parameterClause, closureParameterClause).with(\.returnClause, returnClause.with(\.type, TypeSyntax(voidKeyword))) } /// Returns a value indicating whether the leading trivia of the given token contained any diff --git a/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift b/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift index 698643c30..d37fbb919 100644 --- a/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift +++ b/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift @@ -24,7 +24,7 @@ public final class UseLetInEveryBoundCaseVariable: SyntaxLintRule { public override func visit(_ node: ValueBindingPatternSyntax) -> SyntaxVisitorContinueKind { // Diagnose a pattern binding if it is a function call and the callee is a member access // expression (e.g., `case let .x(y)` or `case let T.x(y)`). - if canDistributeLetVarThroughPattern(node.valuePattern) { + if canDistributeLetVarThroughPattern(node.pattern) { diagnose(.useLetInBoundCaseVariables, on: node) } return .visitChildren diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index ef52d214c..88ce8fb85 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -52,7 +52,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { break } newNode = shorthandArrayType( - element: typeArgument.argumentType, + element: typeArgument.argument, leadingTrivia: leadingTrivia, trailingTrivia: trailingTrivia) @@ -62,8 +62,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { break } newNode = shorthandDictionaryType( - key: typeArguments.0.argumentType, - value: typeArguments.1.argumentType, + key: typeArguments.0.argument, + value: typeArguments.1.argument, leadingTrivia: leadingTrivia, trailingTrivia: trailingTrivia) @@ -77,7 +77,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { break } newNode = shorthandOptionalType( - wrapping: typeArgument.argumentType, + wrapping: typeArgument.argument, leadingTrivia: leadingTrivia, trailingTrivia: trailingTrivia) @@ -137,7 +137,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { break } let arrayTypeExpr = makeArrayTypeExpression( - elementType: typeArgument.argumentType, + elementType: typeArgument.argument, leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) newNode = ExprSyntax(arrayTypeExpr) @@ -148,8 +148,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { break } let dictTypeExpr = makeDictionaryTypeExpression( - keyType: typeArguments.0.argumentType, - valueType: typeArguments.1.argumentType, + keyType: typeArguments.0.argument, + valueType: typeArguments.1.argument, leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), colon: TokenSyntax.colonToken(trailingTrivia: .spaces(1)), rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) @@ -161,7 +161,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { break } let optionalTypeExpr = makeOptionalTypeExpression( - wrapping: typeArgument.argumentType, + wrapping: typeArgument.argument, leadingTrivia: leadingTrivia, questionMark: TokenSyntax.postfixQuestionMarkToken(trailingTrivia: trailingTrivia)) newNode = ExprSyntax(optionalTypeExpr) @@ -204,7 +204,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { ) -> TypeSyntax { let result = ArrayTypeSyntax( leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), - elementType: element, + element: element, rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) return TypeSyntax(result) } @@ -219,9 +219,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { ) -> TypeSyntax { let result = DictionaryTypeSyntax( leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), - keyType: key, + key: key, colon: TokenSyntax.colonToken(trailingTrivia: .spaces(1)), - valueType: value, + value: value, rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) return TypeSyntax(result) } @@ -302,9 +302,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { } let dictElementList = DictionaryElementListSyntax([ DictionaryElementSyntax( - keyExpression: keyTypeExpr, + key: keyTypeExpr, colon: colon, - valueExpression: valueTypeExpr, + value: valueTypeExpr, trailingComma: nil), ]) return DictionaryExprSyntax( @@ -391,15 +391,15 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { case .arrayType(let arrayType): let result = makeArrayTypeExpression( - elementType: arrayType.elementType, + elementType: arrayType.element, leftSquare: arrayType.leftSquare, rightSquare: arrayType.rightSquare) return ExprSyntax(result) case .dictionaryType(let dictionaryType): let result = makeDictionaryTypeExpression( - keyType: dictionaryType.keyType, - valueType: dictionaryType.valueType, + keyType: dictionaryType.key, + valueType: dictionaryType.value, leftSquare: dictionaryType.leftSquare, colon: dictionaryType.colon, rightSquare: dictionaryType.rightSquare) @@ -419,7 +419,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { rightParen: functionType.rightParen, effectSpecifiers: functionType.effectSpecifiers, arrow: functionType.returnClause.arrow, - returnType: functionType.returnClause.returnType + returnType: functionType.returnClause.type ) return ExprSyntax(result) @@ -499,7 +499,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { /// Returns true if the given pattern binding represents a stored property/variable (as opposed to /// a computed property/variable). private func isStoredProperty(_ node: PatternBindingSyntax) -> Bool { - guard let accessor = node.accessor else { + guard let accessor = node.accessors else { // If it has no accessors at all, it is definitely a stored property. return true } diff --git a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift index 46106132e..de08ddd5a 100644 --- a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift @@ -22,7 +22,7 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax { guard - let accessorBlock = node.accessor?.as(AccessorBlockSyntax.self), + let accessorBlock = node.accessors?.as(AccessorBlockSyntax.self), let acc = accessorBlock.accessors.first, let body = acc.body, accessorBlock.accessors.count == 1, @@ -37,7 +37,7 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { let newBlock = CodeBlockSyntax( leftBrace: accessorBlock.leftBrace, statements: body.statements, rightBrace: accessorBlock.rightBrace) - return node.with(\.accessor, .getter(newBlock)) + return node.with(\.accessors, .getter(newBlock)) } } diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift index 89f690bc4..10498bd75 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift @@ -51,7 +51,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { for initializer in initializers { guard matchesPropertyList( - parameters: initializer.signature.parameterClause.parameterList, + parameters: initializer.signature.parameterClause.parameters, properties: storedProperties) else { continue } guard diff --git a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift index 25365e629..9dbfbb284 100644 --- a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift @@ -100,7 +100,7 @@ fileprivate func updateWithWhereCondition( statements: CodeBlockItemListSyntax ) -> ForInStmtSyntax { // Construct a new `where` clause with the condition. - let lastToken = node.sequenceExpr.lastToken(viewMode: .sourceAccurate) + let lastToken = node.sequence.lastToken(viewMode: .sourceAccurate) var whereLeadingTrivia = Trivia() if lastToken?.trailingTrivia.containsSpaces == false { whereLeadingTrivia = .spaces(1) diff --git a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift index 710eeb578..e2f5422b4 100644 --- a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift @@ -34,7 +34,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { public override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { return checkFunctionLikeDocumentation( - DeclSyntax(node), name: node.identifier.text, signature: node.signature, + DeclSyntax(node), name: node.name.text, signature: node.signature, returnClause: node.signature.returnClause) } @@ -71,7 +71,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { name: name, returnsDescription: docComment.returns, node: node) - let funcParameters = funcParametersIdentifiers(in: signature.parameterClause.parameterList) + let funcParameters = funcParametersIdentifiers(in: signature.parameterClause.parameters) // If the documentation of the parameters is wrong 'docCommentInfo' won't // parse the parameters correctly. First the documentation has to be fix @@ -106,7 +106,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { if returnClause == nil && returnsDescription != nil { diagnose(.removeReturnComment(funcName: name), on: node) } else if let returnClause = returnClause, returnsDescription == nil { - if let returnTypeIdentifier = returnClause.returnType.as(SimpleTypeIdentifierSyntax.self), + if let returnTypeIdentifier = returnClause.type.as(SimpleTypeIdentifierSyntax.self), returnTypeIdentifier.name.text == "Never" { return diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index d0a778159..5473fb8fc 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -87,11 +87,11 @@ final class RuleCollector { let maybeInheritanceClause: TypeInheritanceClauseSyntax? if let classDecl = statement.item.as(ClassDeclSyntax.self) { - typeName = classDecl.identifier.text + typeName = classDecl.name.text members = classDecl.memberBlock.members maybeInheritanceClause = classDecl.inheritanceClause } else if let structDecl = statement.item.as(StructDeclSyntax.self) { - typeName = structDecl.identifier.text + typeName = structDecl.name.text members = structDecl.memberBlock.members maybeInheritanceClause = structDecl.inheritanceClause } else { @@ -104,8 +104,8 @@ final class RuleCollector { } // Scan through the inheritance clause to find one of the protocols/types we're interested in. - for inheritance in inheritanceClause.inheritedTypeCollection { - guard let identifier = inheritance.typeName.as(SimpleTypeIdentifierSyntax.self) else { + for inheritance in inheritanceClause.inheritedTypes { + guard let identifier = inheritance.type.as(SimpleTypeIdentifierSyntax.self) else { continue } @@ -124,8 +124,8 @@ final class RuleCollector { var visitedNodes = [String]() for member in members { guard let function = member.decl.as(FunctionDeclSyntax.self) else { continue } - guard function.identifier.text == "visit" else { continue } - let params = function.signature.parameterClause.parameterList + guard function.name.text == "visit" else { continue } + let params = function.signature.parameterClause.parameters guard let firstType = params.firstAndOnly?.type.as(SimpleTypeIdentifierSyntax.self) else { continue } From 3e451ef5f73b7bc1fb145603ee1cbcae89137e28 Mon Sep 17 00:00:00 2001 From: kitasuke Date: Sat, 22 Jul 2023 12:36:20 +0900 Subject: [PATCH 066/332] Fix warning to use CollectionType method instead --- Sources/SwiftFormatRules/DoNotUseSemicolons.swift | 5 +++-- Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift index 59cea384f..b462f1922 100644 --- a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift @@ -74,10 +74,11 @@ public final class DoNotUseSemicolons: SyntaxFormatRule { // 'while' statement. if Syntax(item).as(CodeBlockItemSyntax.self)? .children(viewMode: .sourceAccurate).first?.is(DoStmtSyntax.self) == true, - idx < node.count - 1 + idx < node.count - 1, + let childrenIdx = node.index(of: item) { let children = node.children(viewMode: .sourceAccurate) - let nextItem = children[children.index(after: item.index)] + let nextItem = children[children.index(after: childrenIdx)] if Syntax(nextItem).as(CodeBlockItemSyntax.self)? .children(viewMode: .sourceAccurate).first?.is(WhileStmtSyntax.self) == true { diff --git a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift index 9dbfbb284..09a1be91d 100644 --- a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift @@ -85,7 +85,7 @@ public final class UseWhereClausesInForLoops: SyntaxFormatRule { return updateWithWhereCondition( node: forInStmt, condition: condition, - statements: forInStmt.body.statements.removingFirst() + statements: CodeBlockItemListSyntax(forInStmt.body.statements.dropFirst()) ) default: From dae4ef813bddd66598f9ac1c097fb14e73fdfd20 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 26 Jul 2023 16:27:44 -0700 Subject: [PATCH 067/332] Adjustments for node renames in swift-syntax --- .../SwiftFormatPrettyPrint/TokenStreamCreator.swift | 12 ++++++------ .../DontRepeatTypeInStaticProperties.swift | 4 ++-- .../FileScopedDeclarationPrivacy.swift | 2 +- Sources/SwiftFormatRules/UseShorthandTypeNames.swift | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 951d0a98d..69db5bb1a 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -3360,8 +3360,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func maybeGroupAroundSubexpression( _ expr: ExprSyntax, combiningOperator operatorExpr: ExprSyntax? = nil ) { - switch Syntax(expr).as(SyntaxEnum.self) { - case .memberAccessExpr, .subscriptExpr: + switch Syntax(expr).kind { + case .memberAccessExpr, .subscriptCallExpr: before(expr.firstToken(viewMode: .sourceAccurate), tokens: .open) after(expr.lastToken(viewMode: .sourceAccurate), tokens: .close) default: @@ -3458,19 +3458,19 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return leftmostExpr(of: asExpr.expression, ifMatching: predicate) case .isExpr(let isExpr): return leftmostExpr(of: isExpr.expression, ifMatching: predicate) - case .forcedValueExpr(let forcedValueExpr): + case .forceUnwrapExpr(let forcedValueExpr): return leftmostExpr(of: forcedValueExpr.expression, ifMatching: predicate) case .optionalChainingExpr(let optionalChainingExpr): return leftmostExpr(of: optionalChainingExpr.expression, ifMatching: predicate) - case .postfixUnaryExpr(let postfixUnaryExpr): + case .postfixOperatorExpr(let postfixUnaryExpr): return leftmostExpr(of: postfixUnaryExpr.expression, ifMatching: predicate) case .prefixOperatorExpr(let prefixOperatorExpr): - return leftmostExpr(of: prefixOperatorExpr.base, ifMatching: predicate) + return leftmostExpr(of: prefixOperatorExpr.expression, ifMatching: predicate) case .ternaryExpr(let ternaryExpr): return leftmostExpr(of: ternaryExpr.condition, ifMatching: predicate) case .functionCallExpr(let functionCallExpr): return leftmostExpr(of: functionCallExpr.calledExpression, ifMatching: predicate) - case .subscriptExpr(let subscriptExpr): + case .subscriptCallExpr(let subscriptExpr): return leftmostExpr(of: subscriptExpr.calledExpression, ifMatching: predicate) case .memberAccessExpr(let memberAccessExpr): return memberAccessExpr.base.flatMap { leftmostExpr(of: $0, ifMatching: predicate) } diff --git a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift index ecb19de9b..77973d44a 100644 --- a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift @@ -46,9 +46,9 @@ public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { let members = node.memberBlock.members switch Syntax(node.extendedType).as(SyntaxEnum.self) { - case .simpleTypeIdentifier(let simpleType): + case .identifierType(let simpleType): diagnoseStaticMembers(members, endingWith: simpleType.name.text) - case .memberTypeIdentifier(let memberType): + case .memberType(let memberType): // We don't need to drill recursively into this structure because types with more than two // components are constructed left-heavy; that is, `A.B.C.D` is structured as `((A.B).C).D`, // and the final component of the top type is what we want. diff --git a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift index 69814fe24..bf69c68a5 100644 --- a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift +++ b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift @@ -91,7 +91,7 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { modifiers: protocolDecl.modifiers, factory: { protocolDecl.with(\.modifiers, $0) })) - case .typealiasDecl(let typealiasDecl): + case .typeAliasDecl(let typealiasDecl): return DeclSyntax(rewrittenDecl( typealiasDecl, modifiers: typealiasDecl.modifiers, diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 88ce8fb85..53b0e8560 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -241,7 +241,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // otherwise the "?" applies to the return type instead of the function type. Attach the // leading trivia to the left-paren that we're adding in this case. let tupleTypeElement = TupleTypeElementSyntax( - inoutKeyword: nil, name: nil, secondName: nil, colon: nil, type: TypeSyntax(functionType), + inoutKeyword: nil, firstName: nil, secondName: nil, colon: nil, type: TypeSyntax(functionType), ellipsis: nil, initializer: nil, trailingComma: nil) let tupleTypeElementList = TupleTypeElementListSyntax([tupleTypeElement]) let tupleType = TupleTypeSyntax( @@ -360,7 +360,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { /// written `Array.Index` to compile correctly. private func expressionRepresentation(of type: TypeSyntax) -> ExprSyntax? { switch Syntax(type).as(SyntaxEnum.self) { - case .simpleTypeIdentifier(let simpleTypeIdentifier): + case .identifierType(let simpleTypeIdentifier): let identifierExpr = IdentifierExprSyntax( identifier: simpleTypeIdentifier.name, declNameArguments: nil) @@ -378,7 +378,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { return ExprSyntax(identifierExpr) } - case .memberTypeIdentifier(let memberTypeIdentifier): + case .memberType(let memberTypeIdentifier): guard let baseType = expressionRepresentation(of: memberTypeIdentifier.baseType) else { return nil } From 2b9f52cb51b4e653472a42aa790a000ba7dd1523 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 27 Jul 2023 13:22:57 -0700 Subject: [PATCH 068/332] Remove `initializer` from `TupleTypeElementSyntax` initializer call This property has been removed in SwiftSyntax. --- Sources/SwiftFormatRules/UseShorthandTypeNames.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 53b0e8560..79c88d5f4 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -242,7 +242,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // leading trivia to the left-paren that we're adding in this case. let tupleTypeElement = TupleTypeElementSyntax( inoutKeyword: nil, firstName: nil, secondName: nil, colon: nil, type: TypeSyntax(functionType), - ellipsis: nil, initializer: nil, trailingComma: nil) + ellipsis: nil, trailingComma: nil) let tupleTypeElementList = TupleTypeElementListSyntax([tupleTypeElement]) let tupleType = TupleTypeSyntax( leftParen: TokenSyntax.leftParenToken(leadingTrivia: leadingTrivia), From 0cd30c514e346337ebeac7a224e4a5c88b23987e Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 27 Jul 2023 13:26:38 -0700 Subject: [PATCH 069/332] Rename children of differentiability nodes Those have been renamed in SwiftSyntax. --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 69db5bb1a..fbcd3ecec 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -2532,9 +2532,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // This node encapsulates the entire list of arguments in a `@differentiable(...)` attribute. var needsBreakBeforeWhereClause = false - if let diffParamsComma = node.parametersComma { + if let diffParamsComma = node.argumentsComma { after(diffParamsComma, tokens: .break(.same)) - } else if node.parameters != nil { + } else if node.arguments != nil { // If there were diff params but no comma following them, then we have "wrt: foo where ..." // and we need a break before the where clause. needsBreakBeforeWhereClause = true @@ -2571,7 +2571,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // The comma after originalDeclName is optional and is only present if there are diffParams. after(node.comma ?? node.originalDeclName.lastToken(viewMode: .sourceAccurate), tokens: .close) - if let diffParams = node.parameters { + if let diffParams = node.arguments { before(diffParams.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) after(diffParams.lastToken(viewMode: .sourceAccurate), tokens: .close) } From aff4e09f333d1cdd05f1fec627944bd8859189b6 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 27 Jul 2023 14:15:13 -0700 Subject: [PATCH 070/332] Use `where` instead of filter for `for` loops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With https://github.com/apple/swift-syntax/pull/1958, `node.filter` will return a new `SyntaxCollection` that has the filtered elements removed (instead of the array it’s currently returning). Since that node has elements removed, it will have a new parent and thus all the nodes inside of it have new IDs. Use `where` after `for` to get the elements with the same IDs and just don’t iterate the elements that don’t satisfy the condition. This is also more performant because it doesn’t create an intermediate array. --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index fbcd3ecec..8eff9b46a 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -744,7 +744,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // An if-configuration clause around a switch-case encloses the case's node, so an // if-configuration clause requires a break here in order to be allowed on a new line. - for ifConfigDecl in node.cases.filter({ $0.is(IfConfigDeclSyntax.self) }) { + for ifConfigDecl in node.cases where ifConfigDecl.is(IfConfigDeclSyntax.self) { if config.indentSwitchCaseLabels { before(ifConfigDecl.firstToken(viewMode: .sourceAccurate), tokens: .break(.open)) after(ifConfigDecl.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0)) From 4f444f517bf292971ce30c8b2daa85e276a8e169 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 31 Jul 2023 18:08:55 -0700 Subject: [PATCH 071/332] Adjustments for refactoring of representation of Accessors in SwiftSyntax --- .../TokenStreamCreator.swift | 59 ++++++++++++------- .../UseShorthandTypeNames.swift | 7 +-- .../UseSingleLinePropertyGetter.swift | 14 ++--- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index fbcd3ecec..11c036f64 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -16,6 +16,17 @@ import SwiftFormatCore import SwiftOperators import SwiftSyntax +fileprivate extension AccessorBlockSyntax { + /// Assuming that the accessor only contains an implicit getter (i.e. no + /// `get` or `set`), return the code block items in that getter. + var getterCodeBlockItems: CodeBlockItemListSyntax { + guard case .getter(let codeBlockItemList) = self.accessors else { + preconditionFailure("AccessorBlock has an accessor list and not just a getter") + } + return codeBlockItemList + } +} + /// Visits the nodes of a syntax tree and constructs a linear stream of formatting tokens that /// tell the pretty printer how the source text should be laid out. fileprivate final class TokenStreamCreator: SyntaxVisitor { @@ -442,12 +453,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.returnClause.firstToken(viewMode: .sourceAccurate), tokens: .break) - if let accessorOrCodeBlock = node.accessors { - switch accessorOrCodeBlock { - case .accessors(let accessorBlock): - arrangeBracesAndContents(of: accessorBlock) - case .getter(let codeBlock): - arrangeBracesAndContents(of: codeBlock, contentsKeyPath: \.statements) + if let accessorBlock = node.accessorBlock { + switch accessorBlock.accessors { + case .accessors(let accessors): + arrangeBracesAndContents( + leftBrace: accessorBlock.leftBrace, + accessors: accessors, + rightBrace: accessorBlock.rightBrace + ) + case .getter: + arrangeBracesAndContents(of: accessorBlock, contentsKeyPath: \.getterCodeBlockItems) } } @@ -2160,12 +2175,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } - if let accessorOrCodeBlock = node.accessors { - switch accessorOrCodeBlock { - case .accessors(let accessorBlock): - arrangeBracesAndContents(of: accessorBlock) - case .getter(let codeBlock): - arrangeBracesAndContents(of: codeBlock, contentsKeyPath: \.statements) + if let accessorBlock = node.accessorBlock { + switch accessorBlock.accessors { + case .accessors(let accessors): + arrangeBracesAndContents( + leftBrace: accessorBlock.leftBrace, + accessors: accessors, + rightBrace: accessorBlock.rightBrace + ) + case .getter: + arrangeBracesAndContents(of: accessorBlock, contentsKeyPath: \.getterCodeBlockItems) } } else if let trailingComma = node.trailingComma { // If this is one of multiple comma-delimited bindings, move any pending close breaks to @@ -2960,24 +2979,24 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// Applies consistent formatting to the braces and contents of the given node. /// /// - Parameter node: An `AccessorBlockSyntax` node. - private func arrangeBracesAndContents(of node: AccessorBlockSyntax) { + private func arrangeBracesAndContents(leftBrace: TokenSyntax, accessors: AccessorDeclListSyntax, rightBrace: TokenSyntax) { // If the collection is empty, then any comments that might be present in the block must be // leading trivia of the right brace. - let commentPrecedesRightBrace = node.rightBrace.leadingTrivia.numberOfComments > 0 + let commentPrecedesRightBrace = rightBrace.leadingTrivia.numberOfComments > 0 // We can't use `count` here because it also includes missing children. Instead, we get an // iterator and check if it returns `nil` immediately. - var accessorsIterator = node.accessors.makeIterator() + var accessorsIterator = accessors.makeIterator() let areAccessorsEmpty = accessorsIterator.next() == nil let bracesAreCompletelyEmpty = areAccessorsEmpty && !commentPrecedesRightBrace - before(node.leftBrace, tokens: .break(.reset, size: 1)) + before(leftBrace, tokens: .break(.reset, size: 1)) if !bracesAreCompletelyEmpty { - after(node.leftBrace, tokens: .break(.open, size: 1), .open) - before(node.rightBrace, tokens: .break(.close, size: 1), .close) + after(leftBrace, tokens: .break(.open, size: 1), .open) + before(rightBrace, tokens: .break(.close, size: 1), .close) } else { - after(node.leftBrace, tokens: .break(.open, size: 0)) - before(node.rightBrace, tokens: .break(.close, size: 0)) + after(leftBrace, tokens: .break(.open, size: 0)) + before(rightBrace, tokens: .break(.close, size: 0)) } } diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 79c88d5f4..0de87e764 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -499,19 +499,18 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { /// Returns true if the given pattern binding represents a stored property/variable (as opposed to /// a computed property/variable). private func isStoredProperty(_ node: PatternBindingSyntax) -> Bool { - guard let accessor = node.accessors else { + guard let accessor = node.accessorBlock else { // If it has no accessors at all, it is definitely a stored property. return true } - guard let accessorBlock = accessor.as(AccessorBlockSyntax.self) else { + guard case .accessors(let accessors) = accessor.accessors else { // If the accessor isn't an `AccessorBlockSyntax`, then it is a `CodeBlockSyntax`; i.e., the // accessor an implicit `get`. So, it is definitely not a stored property. - assert(accessor.is(CodeBlockSyntax.self)) return false } - for accessorDecl in accessorBlock.accessors { + for accessorDecl in accessors { // Look for accessors that indicate that this is a computed property. If none are found, then // it is a stored property (e.g., having only observers like `willSet/didSet`). switch accessorDecl.accessorSpecifier.tokenKind { diff --git a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift index de08ddd5a..e2cb7431f 100644 --- a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift @@ -22,10 +22,11 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax { guard - let accessorBlock = node.accessors?.as(AccessorBlockSyntax.self), - let acc = accessorBlock.accessors.first, + let accessorBlock = node.accessorBlock, + case .accessors(let accessors) = accessorBlock.accessors, + let acc = accessors.first, let body = acc.body, - accessorBlock.accessors.count == 1, + accessors.count == 1, acc.accessorSpecifier.tokenKind == .keyword(.get), acc.attributes == nil, acc.modifier == nil, @@ -34,10 +35,9 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { diagnose(.removeExtraneousGetBlock, on: acc) - let newBlock = CodeBlockSyntax( - leftBrace: accessorBlock.leftBrace, statements: body.statements, - rightBrace: accessorBlock.rightBrace) - return node.with(\.accessors, .getter(newBlock)) + var result = node + result.accessorBlock?.accessors = .getter(body.statements) + return result } } From 7733d1793e23e6e0a221295d68dd17981d10a5c1 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 31 Jul 2023 21:48:24 -0700 Subject: [PATCH 072/332] Adjustments for usage of `DeclReferenceExprSyntax` as child of `MemberAccessExprSyntax` --- Sources/SwiftFormatRules/UseShorthandTypeNames.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 0de87e764..bd47dfdaa 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -385,8 +385,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let result = MemberAccessExprSyntax( base: baseType, period: memberTypeIdentifier.period, - name: memberTypeIdentifier.name, - declNameArguments: nil) + name: memberTypeIdentifier.name + ) return ExprSyntax(result) case .arrayType(let arrayType): From cb0a0fce9fdd7066594d296b1e8af47ed7ebe46e Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 21 Jul 2023 13:54:31 -0700 Subject: [PATCH 073/332] [Core] Add a new finding severity - `refactoring` Refactoring identifies an actionable feedback to the developer and has higher impact than a general warning. --- Sources/SwiftFormatCore/Finding.swift | 1 + Sources/SwiftFormatCore/Rule.swift | 4 +++- Sources/SwiftFormatCore/RuleBasedFindingCategory.swift | 5 ++++- Sources/swift-format/Utilities/DiagnosticsEngine.swift | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormatCore/Finding.swift b/Sources/SwiftFormatCore/Finding.swift index 7e002a5d8..f8b747a1f 100644 --- a/Sources/SwiftFormatCore/Finding.swift +++ b/Sources/SwiftFormatCore/Finding.swift @@ -16,6 +16,7 @@ public struct Finding { public enum Severity { case warning case error + case refactoring } /// The file path and location in that file where a finding was encountered. diff --git a/Sources/SwiftFormatCore/Rule.swift b/Sources/SwiftFormatCore/Rule.swift index 264bafd82..aa596616a 100644 --- a/Sources/SwiftFormatCore/Rule.swift +++ b/Sources/SwiftFormatCore/Rule.swift @@ -46,6 +46,7 @@ extension Rule { public func diagnose( _ message: Finding.Message, on node: SyntaxType?, + severity: Finding.Severity? = nil, leadingTriviaIndex: Trivia.Index? = nil, notes: [Finding.Note] = [] ) { @@ -57,9 +58,10 @@ extension Rule { syntaxLocation = node?.startLocation(converter: context.sourceLocationConverter) } + let category = RuleBasedFindingCategory(ruleType: type(of: self), severity: severity) context.findingEmitter.emit( message, - category: RuleBasedFindingCategory(ruleType: type(of: self)), + category: category, location: syntaxLocation.flatMap(Finding.Location.init), notes: notes) } diff --git a/Sources/SwiftFormatCore/RuleBasedFindingCategory.swift b/Sources/SwiftFormatCore/RuleBasedFindingCategory.swift index 03a2f4541..a43dade37 100644 --- a/Sources/SwiftFormatCore/RuleBasedFindingCategory.swift +++ b/Sources/SwiftFormatCore/RuleBasedFindingCategory.swift @@ -22,8 +22,11 @@ struct RuleBasedFindingCategory: FindingCategorizing { var description: String { ruleType.ruleName } + var severity: Finding.Severity? + /// Creates a finding category that wraps the given rule type. - init(ruleType: Rule.Type) { + init(ruleType: Rule.Type, severity: Finding.Severity? = nil) { self.ruleType = ruleType + self.severity = severity } } diff --git a/Sources/swift-format/Utilities/DiagnosticsEngine.swift b/Sources/swift-format/Utilities/DiagnosticsEngine.swift index 52e0b8909..0da219ac6 100644 --- a/Sources/swift-format/Utilities/DiagnosticsEngine.swift +++ b/Sources/swift-format/Utilities/DiagnosticsEngine.swift @@ -116,6 +116,7 @@ final class DiagnosticsEngine { switch finding.severity { case .error: severity = .error case .warning: severity = .warning + case .refactoring: severity = .warning } return Diagnostic( severity: severity, From 21c122fd9ac40ef00861e6688ac23cc1f3197862 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 21 Jul 2023 16:31:20 -0700 Subject: [PATCH 074/332] [Core] Add a new finding severity - `convention` Identifies programming conventions such as for naming types and functions. --- Sources/SwiftFormatCore/Finding.swift | 1 + Sources/swift-format/Utilities/DiagnosticsEngine.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Sources/SwiftFormatCore/Finding.swift b/Sources/SwiftFormatCore/Finding.swift index f8b747a1f..c4fe886ab 100644 --- a/Sources/SwiftFormatCore/Finding.swift +++ b/Sources/SwiftFormatCore/Finding.swift @@ -17,6 +17,7 @@ public struct Finding { case warning case error case refactoring + case convention } /// The file path and location in that file where a finding was encountered. diff --git a/Sources/swift-format/Utilities/DiagnosticsEngine.swift b/Sources/swift-format/Utilities/DiagnosticsEngine.swift index 0da219ac6..700a2c229 100644 --- a/Sources/swift-format/Utilities/DiagnosticsEngine.swift +++ b/Sources/swift-format/Utilities/DiagnosticsEngine.swift @@ -117,6 +117,7 @@ final class DiagnosticsEngine { case .error: severity = .error case .warning: severity = .warning case .refactoring: severity = .warning + case .convention: severity = .warning } return Diagnostic( severity: severity, From acbe484e4961a73d3685861bd6ed3e77aa1dbce6 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 2 Aug 2023 10:49:24 -0700 Subject: [PATCH 075/332] Fix deprecation warnings from renamed nodes / types in SwiftSyntax --- Sources/SwiftFormat/Pipelines+Generated.swift | 18 +-- Sources/SwiftFormatCore/RuleMask.swift | 2 +- .../TokenStreamCreator.swift | 146 +++++++++--------- .../AddModifierRewriter.swift | 6 +- ...lPublicDeclarationsHaveDocumentation.swift | 4 +- .../AlwaysUseLowerCamelCase.swift | 10 +- .../AmbiguousTrailingClosureOverload.swift | 2 +- ...cumentationCommentWithOneLineSummary.swift | 4 +- .../SwiftFormatRules/DoNotUseSemicolons.swift | 4 +- .../DontRepeatTypeInStaticProperties.swift | 2 +- .../FileScopedDeclarationPrivacy.swift | 6 +- .../SwiftFormatRules/FullyIndirectEnum.swift | 6 +- .../GroupNumericLiterals.swift | 8 +- .../ModifierListSyntax+Convenience.swift | 20 +-- .../SwiftFormatRules/NeverForceUnwrap.swift | 2 +- ...NeverUseImplicitlyUnwrappedOptionals.swift | 2 +- .../NoAccessLevelOnExtensionDeclaration.swift | 10 +- .../NoAssignmentInExpressions.swift | 4 +- .../NoCasesWithOnlyFallthrough.swift | 6 +- .../NoLabelsInCasePatterns.swift | 10 +- .../NoLeadingUnderscores.swift | 4 +- .../NoParensAroundConditions.swift | 6 +- .../NoVoidReturnOnFunctionSignature.swift | 2 +- Sources/SwiftFormatRules/OneCasePerLine.swift | 6 +- .../ReturnVoidInsteadOfEmptyTuple.swift | 4 +- .../SemicolonSyntaxProtocol.swift | 2 +- .../UseLetInEveryBoundCaseVariable.swift | 2 +- .../UseShorthandTypeNames.swift | 36 ++--- .../UseSynthesizedInitializer.swift | 14 +- ...eTripleSlashForDocumentationComments.swift | 2 +- .../UseWhereClausesInForLoops.swift | 12 +- .../ValidateDocumentationComments.swift | 2 +- 32 files changed, 182 insertions(+), 182 deletions(-) diff --git a/Sources/SwiftFormat/Pipelines+Generated.swift b/Sources/SwiftFormat/Pipelines+Generated.swift index 8fb86cbd0..2c39463eb 100644 --- a/Sources/SwiftFormat/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Pipelines+Generated.swift @@ -39,7 +39,7 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } - override func visit(_ node: AssociatedtypeDeclSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren @@ -119,12 +119,12 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } - override func visit(_ node: ForInStmtSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: ForStmtSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(UseWhereClausesInForLoops.visit, for: node) return .visitChildren } - override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: ForceUnwrapExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } @@ -194,12 +194,12 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } - override func visit(_ node: MemberDeclBlockSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) return .visitChildren } - override func visit(_ node: MemberDeclListSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: MemberBlockItemListSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(DoNotUseSemicolons.visit, for: node) return .visitChildren } @@ -228,12 +228,12 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } - override func visit(_ node: RepeatWhileStmtSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: RepeatStmtSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } - override func visit(_ node: SimpleTypeIdentifierSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: IdentifierTypeSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(UseShorthandTypeNames.visit, for: node) return .visitChildren } @@ -249,7 +249,7 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } - override func visit(_ node: SpecializeExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: GenericSpecializationExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(UseShorthandTypeNames.visit, for: node) return .visitChildren } @@ -296,7 +296,7 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } - override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) diff --git a/Sources/SwiftFormatCore/RuleMask.swift b/Sources/SwiftFormatCore/RuleMask.swift index 37dfec1c2..b18239b02 100644 --- a/Sources/SwiftFormatCore/RuleMask.swift +++ b/Sources/SwiftFormatCore/RuleMask.swift @@ -165,7 +165,7 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor { return appendRuleStatusDirectives(from: firstToken, of: Syntax(node)) } - override func visit(_ node: MemberDeclListItemSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: MemberBlockItemSyntax) -> SyntaxVisitorContinueKind { guard let firstToken = node.firstToken(viewMode: .sourceAccurate) else { return .visitChildren } diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 07234bfc1..0fe1209f2 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -302,13 +302,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func arrangeTypeDeclBlock( _ node: Syntax, attributes: AttributeListSyntax?, - modifiers: ModifierListSyntax?, + modifiers: DeclModifierListSyntax?, typeKeyword: TokenSyntax, identifier: TokenSyntax, genericParameterOrPrimaryAssociatedTypeClause: Syntax?, - inheritanceClause: TypeInheritanceClauseSyntax?, + inheritanceClause: InheritanceClauseSyntax?, genericWhereClause: GenericWhereClauseSyntax?, - memberBlock: MemberDeclBlockSyntax + memberBlock: MemberBlockSyntax ) { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) @@ -523,7 +523,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // MARK: - Property and subscript accessor block nodes - override func visit(_ node: AccessorListSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: AccessorDeclListSyntax) -> SyntaxVisitorContinueKind { for child in node.dropLast() { // If the child doesn't have a body (it's just the `get`/`set` keyword), then we're in a // protocol and we want to let them be placed on the same line if possible. Otherwise, we @@ -540,7 +540,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: AccessorParameterSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: AccessorParametersSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -616,7 +616,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: ForInStmtSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: ForStmtSyntax) -> SyntaxVisitorContinueKind { // If we have a `(try) await` clause, allow breaking after the `for` so that the `(try) await` // can fall onto the next line if needed, and if both `try await` are present, keep them // together. Otherwise, keep `for` glued to the token after it so that we break somewhere later @@ -671,7 +671,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: RepeatWhileStmtSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: RepeatStmtSyntax) -> SyntaxVisitorContinueKind { arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) if config.lineBreakBeforeControlFlowKeywords { @@ -786,7 +786,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } before(node.firstToken(viewMode: .sourceAccurate), tokens: openBreak) - after(node.unknownAttr?.lastToken(viewMode: .sourceAccurate), tokens: .space) + after(node.attribute?.lastToken(viewMode: .sourceAccurate), tokens: .space) after(node.label.lastToken(viewMode: .sourceAccurate), tokens: .break(.reset, size: 0), .break(.open), .open) // If switch/case labels were configured to be indented, insert an extra `close` break after @@ -889,14 +889,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: TupleExprElementListSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: LabeledExprListSyntax) -> SyntaxVisitorContinueKind { // Intentionally do nothing here. Since `TupleExprElement`s are used both in tuple expressions // and function argument lists, which need to be formatted, differently, those nodes manually // loop over the nodes and arrange them in those contexts. return .visitChildren } - override func visit(_ node: TupleExprElementSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: LabeledExprSyntax) -> SyntaxVisitorContinueKind { // Intentionally do nothing here. Since `TupleExprElement`s are used both in tuple expressions // and function argument lists, which need to be formatted, differently, those nodes manually // loop over the nodes and arrange them in those contexts. @@ -907,7 +907,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// argument). /// /// - Parameter node: The tuple expression element to be arranged. - private func arrangeAsTupleExprElement(_ node: TupleExprElementSyntax) { + private func arrangeAsTupleExprElement(_ node: LabeledExprSyntax) { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) after(node.colon, tokens: .break) after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) @@ -1033,14 +1033,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } if let calledMemberAccessExpr = node.calledExpression.as(MemberAccessExprSyntax.self) { - if let base = calledMemberAccessExpr.base, base.is(IdentifierExprSyntax.self) { + if let base = calledMemberAccessExpr.base, base.is(DeclReferenceExprSyntax.self) { // When this function call is wrapped by a try-expr or await-expr, the group applied when // visiting that wrapping expression is sufficient. Adding another group here in that case // can result in unnecessarily breaking after the try/await keyword. if !(base.firstToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .all)?.parent?.is(TryExprSyntax.self) ?? false || base.firstToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .all)?.parent?.is(AwaitExprSyntax.self) ?? false) { before(base.firstToken(viewMode: .sourceAccurate), tokens: .open) - after(calledMemberAccessExpr.name.lastToken(viewMode: .sourceAccurate), tokens: .close) + after(calledMemberAccessExpr.declName.baseName.lastToken(viewMode: .sourceAccurate), tokens: .close) } } } @@ -1089,7 +1089,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// right delimiter if a line break occurred after the left delimiter, or false if the right /// delimiter is allowed to hang on the same line as the final argument. private func arrangeFunctionCallArgumentList( - _ arguments: TupleExprElementListSyntax, + _ arguments: LabeledExprListSyntax, leftDelimiter: TokenSyntax?, rightDelimiter: TokenSyntax?, forcesBreakBeforeRightDelimiter: Bool @@ -1124,7 +1124,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// - node: The tuple expression element. /// - shouldGroup: If true, group around the argument to prefer keeping it together if possible. private func arrangeAsFunctionCallArgument( - _ node: TupleExprElementSyntax, + _ node: LabeledExprSyntax, shouldGroup: Bool ) { if shouldGroup { @@ -1191,7 +1191,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: ClosureParamSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: ClosureShorthandParameterSyntax) -> SyntaxVisitorContinueKind { after(node.trailingComma, tokens: .break(.same)) return .visitChildren } @@ -1245,13 +1245,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: ClosureCaptureSignatureSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: ClosureCaptureClauseSyntax) -> SyntaxVisitorContinueKind { after(node.leftSquare, tokens: .break(.open, size: 0), .open) before(node.rightSquare, tokens: .break(.close, size: 0), .close) return .visitChildren } - override func visit(_ node: ClosureCaptureItemSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: ClosureCaptureSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) after(node.specifier?.lastToken(viewMode: .sourceAccurate), tokens: .break) before(node.equal, tokens: .break) @@ -1265,13 +1265,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: SubscriptExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: SubscriptCallExprSyntax) -> SyntaxVisitorContinueKind { preVisitInsertingContextualBreaks(node) if let calledMemberAccessExpr = node.calledExpression.as(MemberAccessExprSyntax.self) { - if let base = calledMemberAccessExpr.base, base.is(IdentifierExprSyntax.self) { + if let base = calledMemberAccessExpr.base, base.is(DeclReferenceExprSyntax.self) { before(base.firstToken(viewMode: .sourceAccurate), tokens: .open) - after(calledMemberAccessExpr.name.lastToken(viewMode: .sourceAccurate), tokens: .close) + after(calledMemberAccessExpr.declName.baseName.lastToken(viewMode: .sourceAccurate), tokens: .close) } } @@ -1296,7 +1296,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visitPost(_ node: SubscriptExprSyntax) { + override func visitPost(_ node: SubscriptCallExprSyntax) { clearContextualBreakState(node) } @@ -1363,7 +1363,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: ParameterClauseSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: FunctionParameterClauseSyntax) -> SyntaxVisitorContinueKind { // Prioritize keeping ") throws -> " together. We can only do this if the function // has arguments. if !node.parameters.isEmpty && config.prioritizeKeepingFunctionOutputTogether { @@ -1436,7 +1436,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Member type identifier is used when the return type is a member of another type. Add a group // here so that the base, dot, and member type are kept together when they fit. - if node.type.is(MemberTypeIdentifierSyntax.self) { + if node.type.is(MemberTypeSyntax.self) { before(node.type.firstToken(viewMode: .sourceAccurate), tokens: .open) after(node.type.lastToken(viewMode: .sourceAccurate), tokens: .close) } @@ -1491,11 +1491,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: MemberDeclBlockSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } - override func visit(_ node: MemberDeclListSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: MemberBlockItemListSyntax) -> SyntaxVisitorContinueKind { // Skip ignored items, because the tokens after `item.lastToken` would be ignored and leave // unclosed open tokens. for item in node where !shouldFormatterIgnore(node: Syntax(item)) { @@ -1508,7 +1508,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: MemberDeclListItemSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: MemberBlockItemSyntax) -> SyntaxVisitorContinueKind { if shouldFormatterIgnore(node: Syntax(node)) { appendFormatterIgnored(node: Syntax(node)) return .skipChildren @@ -1548,7 +1548,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: DesignatedTypeElementSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: DesignatedTypeSyntax) -> SyntaxVisitorContinueKind { after(node.leadingComma, tokens: .break(.same)) return .visitChildren } @@ -1556,7 +1556,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind { after(node.trailingComma, tokens: .break) - if let associatedValue = node.associatedValue { + if let associatedValue = node.parameterClause { arrangeEnumCaseParameterClause(associatedValue, forcesBreakBeforeRightParen: false) } @@ -1583,7 +1583,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: ObjCSelectorSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: ObjCSelectorPieceListSyntax) -> SyntaxVisitorContinueKind { insertTokens(.break(.same, size: 0), betweenElementsOf: node) return .visitChildren } @@ -1608,7 +1608,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: PrecedenceGroupNameElementSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: PrecedenceGroupNameSyntax) -> SyntaxVisitorContinueKind { after(node.trailingComma, tokens: .break(.same)) return .visitChildren } @@ -1755,12 +1755,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // When there's a simple base (i.e. identifier), group the entire `try/await .` // sequence. This check has to happen here so that the `MemberAccessExprSyntax.name` is // available. - if base.is(IdentifierExprSyntax.self) { - return memberAccessExpr.name.lastToken(viewMode: .sourceAccurate) + if base.is(DeclReferenceExprSyntax.self) { + return memberAccessExpr.declName.baseName.lastToken(viewMode: .sourceAccurate) } return findTryAwaitExprConnectingToken(inExpr: base) } - if expr.is(IdentifierExprSyntax.self) { + if expr.is(DeclReferenceExprSyntax.self) { return expr.lastToken(viewMode: .sourceAccurate) } return nil @@ -1795,7 +1795,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: AvailabilitySpecListSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: AvailabilityArgumentListSyntax) -> SyntaxVisitorContinueKind { insertTokens(.break(.same, size: 1), betweenElementsOf: node) return .visitChildren } @@ -1807,21 +1807,21 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: AvailabilityVersionRestrictionListSyntax) + override func visit(_ node: PlatformVersionItemListSyntax) -> SyntaxVisitorContinueKind { insertTokens(.break(.same, size: 1), betweenElementsOf: node) return .visitChildren } - override func visit(_ node: AvailabilityVersionRestrictionSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: PlatformVersionSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) after(node.platform, tokens: .break(.continue, size: 1)) after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } - override func visit(_ node: BackDeployedAttributeSpecListSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: BackDeployedAttributeArgumentsSyntax) -> SyntaxVisitorContinueKind { before( node.platforms.firstToken(viewMode: .sourceAccurate), tokens: .break(.open, size: 1), .open(argumentListConsistency())) @@ -1900,10 +1900,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // When the ternary is wrapped in parens, absorb the closing paren into the ternary's group so // that it is glued to the last token of the ternary. let closeScopeToken: TokenSyntax? - if let parenExpr = outermostEnclosingNode(from: Syntax(node.secondChoice)) { + if let parenExpr = outermostEnclosingNode(from: Syntax(node.elseExpression)) { closeScopeToken = parenExpr.lastToken(viewMode: .sourceAccurate) } else { - closeScopeToken = node.secondChoice.lastToken(viewMode: .sourceAccurate) + closeScopeToken = node.elseExpression.lastToken(viewMode: .sourceAccurate) } after(closeScopeToken, tokens: .break(.close(mustBreak: false), size: 0), .close, .close) return .visitChildren @@ -1969,7 +1969,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: InfixOperatorExprSyntax) -> SyntaxVisitorContinueKind { - let binOp = node.operatorOperand + let binOp = node.operator if binOp.is(ArrowExprSyntax.self) { // `ArrowExprSyntax` nodes occur when a function type is written in an expression context; // for example, `let x = [(Int) throws -> Void]()`. We want to treat those consistently like @@ -2063,7 +2063,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: PostfixUnaryExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: PostfixOperatorExprSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -2101,7 +2101,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: SuperRefExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: SuperExprSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -2211,7 +2211,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { arrangeAttributeList(node.attributes) after(node.typealiasKeyword, tokens: .break) @@ -2237,7 +2237,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: DeclReferenceExprSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -2245,7 +2245,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: SpecializeExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: GenericSpecializationExprSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -2255,7 +2255,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: ConstrainedSugarTypeSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: SomeOrAnyTypeSyntax) -> SyntaxVisitorContinueKind { after(node.someOrAnySpecifier, tokens: .space) return .visitChildren } @@ -2264,11 +2264,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: FallthroughStmtSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: FallThroughStmtSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } - override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: ForceUnwrapExprSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -2345,12 +2345,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: StringLiteralExprSyntax) -> SyntaxVisitorContinueKind { - if node.openQuote.tokenKind == .multilineStringQuote { + if node.openingQuote.tokenKind == .multilineStringQuote { // Looks up the correct break kind based on prior context. let breakKind = pendingMultilineStringBreakKinds[node, default: .same] - after(node.openQuote, tokens: .break(breakKind, size: 0, newlines: .hard(count: 1))) + after(node.openingQuote, tokens: .break(breakKind, size: 0, newlines: .hard(count: 1))) if !node.segments.isEmpty { - before(node.closeQuote, tokens: .break(breakKind, newlines: .hard(count: 1))) + before(node.closingQuote, tokens: .break(breakKind, newlines: .hard(count: 1))) } } return .visitChildren @@ -2359,7 +2359,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: StringSegmentSyntax) -> SyntaxVisitorContinueKind { // Looks up the correct break kind based on prior context. func breakKind() -> BreakKind { - if let stringLiteralSegments = node.parent?.as(StringLiteralSegmentsSyntax.self), + if let stringLiteralSegments = node.parent?.as(StringLiteralSegmentListSyntax.self), let stringLiteralExpr = stringLiteralSegments.parent?.as(StringLiteralExprSyntax.self) { return pendingMultilineStringBreakKinds[stringLiteralExpr, default: .same] @@ -2392,7 +2392,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .skipChildren } - override func visit(_ node: AssociatedtypeDeclSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind { arrangeAttributeList(node.attributes) after(node.associatedtypeKeyword, tokens: .break) @@ -2459,7 +2459,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: MemberTypeIdentifierSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: MemberTypeSyntax) -> SyntaxVisitorContinueKind { before(node.period, tokens: .break(.continue, size: 0)) return .visitChildren } @@ -2468,7 +2468,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: SimpleTypeIdentifierSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: IdentifierTypeSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -2484,7 +2484,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: TypeInheritanceClauseSyntax) -> SyntaxVisitorContinueKind { + 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 // breaks if the first item in the list would overflow the column limit. @@ -2493,7 +2493,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: UnresolvedPatternExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: PatternExprSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } @@ -2569,18 +2569,18 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: DifferentiabilityParamsSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: DifferentiabilityArgumentsSyntax) -> SyntaxVisitorContinueKind { after(node.leftParen, tokens: .break(.open, size: 0), .open) before(node.rightParen, tokens: .break(.close, size: 0), .close) return .visitChildren } - override func visit(_ node: DifferentiabilityParamSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: DifferentiabilityArgumentSyntax) -> SyntaxVisitorContinueKind { after(node.trailingComma, tokens: .break(.same)) return .visitChildren } - override func visit(_ node: DerivativeRegistrationAttributeArgumentsSyntax) + override func visit(_ node: DerivativeAttributeArgumentsSyntax) -> SyntaxVisitorContinueKind { // This node encapsulates the entire list of arguments in a `@derivative(...)` or @@ -2598,7 +2598,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: DifferentiabilityParamsClauseSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: DifferentiabilityWithRespectToArgumentSyntax) -> SyntaxVisitorContinueKind { // This node encapsulates the `wrt:` label and value/variable in a `@differentiable`, // `@derivative`, or `@transpose` attribute. after(node.colon, tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) @@ -2870,7 +2870,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// - forcesBreakBeforeRightParen: Whether a break should be required before the right paren /// when the right paren is on a different line than the corresponding left paren. private func arrangeParameterClause( - _ parameters: ParameterClauseSyntax, forcesBreakBeforeRightParen: Bool + _ parameters: FunctionParameterClauseSyntax, forcesBreakBeforeRightParen: Bool ) { guard !parameters.parameters.isEmpty else { return } @@ -3310,7 +3310,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// Returns true if open/close breaks should be inserted around the entire function call argument /// list. - private func shouldGroupAroundArgumentList(_ arguments: TupleExprElementListSyntax) -> Bool { + private func shouldGroupAroundArgumentList(_ arguments: LabeledExprListSyntax) -> Bool { let argumentCount = arguments.count // If there are no arguments, there's no reason to break. @@ -3328,7 +3328,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// - expr: An expression that includes opening and closing delimiters and arguments. /// - argumentListPath: A key path for accessing the expression's function call argument list. private func mustBreakBeforeClosingDelimiter( - of expr: T, argumentListPath: KeyPath + of expr: T, argumentListPath: KeyPath ) -> Bool { guard let parent = expr.parent, @@ -3347,7 +3347,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// /// This is true for any argument list that contains a single argument (labeled or unlabeled) that /// is an array, dictionary, or closure literal. - func isCompactSingleFunctionCallArgument(_ argumentList: TupleExprElementListSyntax) -> Bool { + func isCompactSingleFunctionCallArgument(_ argumentList: LabeledExprListSyntax) -> Bool { guard argumentList.count == 1 else { return false } let expression = argumentList.first!.expression @@ -3509,7 +3509,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// not a multiline string literal. private func leftmostMultilineStringLiteral(of expr: ExprSyntax) -> StringLiteralExprSyntax? { return leftmostExpr(of: expr) { - $0.as(StringLiteralExprSyntax.self)?.openQuote.tokenKind == .multilineStringQuote + $0.as(StringLiteralExprSyntax.self)?.openingQuote.tokenKind == .multilineStringQuote }?.as(StringLiteralExprSyntax.self) } @@ -3609,7 +3609,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let innerExpr = parenthesizedExpr.elements.first?.expression, let stringLiteralExpr = innerExpr.as(StringLiteralExprSyntax.self), - stringLiteralExpr.openQuote.tokenKind == .multilineStringQuote + stringLiteralExpr.openingQuote.tokenKind == .multilineStringQuote { pendingMultilineStringBreakKinds[stringLiteralExpr] = .continue return nil @@ -3825,7 +3825,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // children nodes. before(expr.firstToken(viewMode: .sourceAccurate), tokens: .contextualBreakingStart) after(expr.lastToken(viewMode: .sourceAccurate), tokens: .contextualBreakingEnd) - let hasCompoundExpression = !expr.is(IdentifierExprSyntax.self) + let hasCompoundExpression = !expr.is(DeclReferenceExprSyntax.self) return (hasCompoundExpression, false) } } @@ -3837,7 +3837,7 @@ private func isNestedInPostfixIfConfig(node: Syntax) -> Bool { // This guard handles the situation where a type with its own modifiers // is nested inside of an if config. That type should not count as being // in a postfix if config because its entire body is inside the if config. - if this?.is(TupleExprElementSyntax.self) == true { + if this?.is(LabeledExprSyntax.self) == true { return false } @@ -3884,7 +3884,7 @@ class CommentMovingRewriter: SyntaxRewriter { return super.visit(node) } - override func visit(_ node: MemberDeclListItemSyntax) -> MemberDeclListItemSyntax { + override func visit(_ node: MemberBlockItemSyntax) -> MemberBlockItemSyntax { if shouldFormatterIgnore(node: Syntax(node)) { return node } @@ -3899,7 +3899,7 @@ class CommentMovingRewriter: SyntaxRewriter { } override func visit(_ node: InfixOperatorExprSyntax) -> ExprSyntax { - if let binaryOperatorExpr = node.operatorOperand.as(BinaryOperatorExprSyntax.self), + if let binaryOperatorExpr = node.operator.as(BinaryOperatorExprSyntax.self), let followingToken = binaryOperatorExpr.operator.nextToken(viewMode: .all), followingToken.leadingTrivia.hasLineComment { @@ -4061,7 +4061,7 @@ protocol CallingExprSyntaxProtocol: ExprSyntaxProtocol { } extension FunctionCallExprSyntax: CallingExprSyntaxProtocol { } -extension SubscriptExprSyntax: CallingExprSyntaxProtocol { } +extension SubscriptCallExprSyntax: CallingExprSyntaxProtocol { } extension Syntax { func asProtocol(_: CallingExprSyntaxProtocol.Protocol) -> CallingExprSyntaxProtocol? { diff --git a/Sources/SwiftFormatRules/AddModifierRewriter.swift b/Sources/SwiftFormatRules/AddModifierRewriter.swift index 5ea4aa30c..fe27c046c 100644 --- a/Sources/SwiftFormatRules/AddModifierRewriter.swift +++ b/Sources/SwiftFormatRules/AddModifierRewriter.swift @@ -49,7 +49,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { return DeclSyntax(node.with(\.modifiers, newModifiers)) } - override func visit(_ node: AssociatedtypeDeclSyntax) -> DeclSyntax { + override func visit(_ node: AssociatedTypeDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard let modifiers = node.modifiers else { @@ -114,7 +114,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { return DeclSyntax(node.with(\.modifiers, newModifiers)) } - override func visit(_ node: TypealiasDeclSyntax) -> DeclSyntax { + override func visit(_ node: TypeAliasDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard let modifiers = node.modifiers else { @@ -163,7 +163,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { /// - Parameter modifiersProvider: A closure that returns all modifiers for the given node. private func nodeByRelocatingTrivia( in node: NodeType, - for modifiersProvider: (NodeType) -> ModifierListSyntax? + for modifiersProvider: (NodeType) -> DeclModifierListSyntax? ) -> NodeType { guard let modifier = modifiersProvider(node)?.firstAndOnly, let movingLeadingTrivia = modifier.nextToken(viewMode: .sourceAccurate)?.leadingTrivia diff --git a/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift b/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift index fdd60ec5f..386b4624c 100644 --- a/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift +++ b/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift @@ -65,7 +65,7 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { return .skipChildren } - public override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseMissingDocComment(DeclSyntax(node), name: node.name.text, modifiers: node.modifiers) return .skipChildren } @@ -73,7 +73,7 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { private func diagnoseMissingDocComment( _ decl: DeclSyntax, name: String, - modifiers: ModifierListSyntax? + modifiers: DeclModifierListSyntax? ) { guard documentationCommentText(extractedFrom: decl.leadingTrivia) == nil, diff --git a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift index 196bb5682..0b06157e7 100644 --- a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift @@ -71,7 +71,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { public override func visit(_ node: ClosureSignatureSyntax) -> SyntaxVisitorContinueKind { if let input = node.parameterClause { - if let closureParamList = input.as(ClosureParamListSyntax.self) { + if let closureParamList = input.as(ClosureShorthandParameterListSyntax.self) { for param in closureParamList { diagnoseLowerCamelCaseViolations( param.name, allowUnderscores: false, description: identifierDescription(for: node)) @@ -96,7 +96,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { secondName, allowUnderscores: false, description: identifierDescription(for: node)) } } - } else if let parameterClause = input.as(ParameterClauseSyntax.self) { + } else if let parameterClause = input.as(FunctionParameterClauseSyntax.self) { for param in parameterClause.parameters { diagnoseLowerCamelCaseViolations( param.firstName, allowUnderscores: false, description: identifierDescription(for: node)) @@ -146,14 +146,14 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { /// Collects methods that look like XCTest test case methods from the given member list, inserting /// them into the given set. private func collectTestMethods( - from members: MemberDeclListSyntax, + from members: MemberBlockItemListSyntax, into set: inout Set ) { for member in members { if let ifConfigDecl = member.decl.as(IfConfigDeclSyntax.self) { // Recurse into any conditional member lists and collect their test methods as well. for clause in ifConfigDecl.clauses { - if let clauseMembers = clause.elements?.as(MemberDeclListSyntax.self) { + if let clauseMembers = clause.elements?.as(MemberBlockItemListSyntax.self) { collectTestMethods(from: clauseMembers, into: &set) } } @@ -203,7 +203,7 @@ fileprivate func identifierDescription(for node: NodeT extension ReturnClauseSyntax { /// Whether this return clause specifies an explicit `Void` return type. fileprivate var isVoid: Bool { - if let returnTypeIdentifier = type.as(SimpleTypeIdentifierSyntax.self) { + if let returnTypeIdentifier = type.as(IdentifierTypeSyntax.self) { return returnTypeIdentifier.name.text == "Void" } if let returnTypeTuple = type.as(TupleTypeSyntax.self) { diff --git a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift index 985919eeb..66444ca33 100644 --- a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift +++ b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift @@ -65,7 +65,7 @@ public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { return .visitChildren } - public override func visit(_ decls: MemberDeclBlockSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ decls: MemberBlockSyntax) -> SyntaxVisitorContinueKind { let functions = decls.members.compactMap { $0.decl.as(FunctionDeclSyntax.self) } discoverAndDiagnoseOverloads(functions) return .visitChildren diff --git a/Sources/SwiftFormatRules/BeginDocumentationCommentWithOneLineSummary.swift b/Sources/SwiftFormatRules/BeginDocumentationCommentWithOneLineSummary.swift index 0034f0a5d..d9e7cd60a 100644 --- a/Sources/SwiftFormatRules/BeginDocumentationCommentWithOneLineSummary.swift +++ b/Sources/SwiftFormatRules/BeginDocumentationCommentWithOneLineSummary.swift @@ -75,12 +75,12 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { return .skipChildren } - public override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseDocComments(in: DeclSyntax(node)) return .skipChildren } - public override func visit(_ node: AssociatedtypeDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseDocComments(in: DeclSyntax(node)) return .skipChildren } diff --git a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift index b462f1922..6877a4a0f 100644 --- a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift @@ -103,8 +103,8 @@ public final class DoNotUseSemicolons: SyntaxFormatRule { return nodeByRemovingSemicolons(from: node, nodeCreator: CodeBlockItemListSyntax.init) } - public override func visit(_ node: MemberDeclListSyntax) -> MemberDeclListSyntax { - return nodeByRemovingSemicolons(from: node, nodeCreator: MemberDeclListSyntax.init) + public override func visit(_ node: MemberBlockItemListSyntax) -> MemberBlockItemListSyntax { + return nodeByRemovingSemicolons(from: node, nodeCreator: MemberBlockItemListSyntax.init) } } diff --git a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift index 77973d44a..1c9b9e1f1 100644 --- a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift @@ -65,7 +65,7 @@ public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { /// Iterates over the static/class properties in the given member list and diagnoses any where the /// name has the containing type name (excluding possible namespace prefixes, like `NS` or `UI`) /// as a suffix. - private func diagnoseStaticMembers(_ members: MemberDeclListSyntax, endingWith typeName: String) { + private func diagnoseStaticMembers(_ members: MemberBlockItemListSyntax, endingWith typeName: String) { for member in members { guard let varDecl = member.decl.as(VariableDeclSyntax.self), diff --git a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift index bf69c68a5..9050c6db9 100644 --- a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift +++ b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift @@ -135,8 +135,8 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { /// - Returns: A new node if the modifiers were rewritten, or the original node if not. private func rewrittenDecl( _ decl: DeclType, - modifiers: ModifierListSyntax?, - factory: (ModifierListSyntax?) -> DeclType + modifiers: DeclModifierListSyntax?, + factory: (DeclModifierListSyntax?) -> DeclType ) -> DeclType { let invalidAccess: TokenKind let validAccess: TokenKind @@ -165,7 +165,7 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { } return modifier } - return factory(ModifierListSyntax(newModifiers)) + return factory(DeclModifierListSyntax(newModifiers)) } } diff --git a/Sources/SwiftFormatRules/FullyIndirectEnum.swift b/Sources/SwiftFormatRules/FullyIndirectEnum.swift index 0a6cbfd76..60ce164e2 100644 --- a/Sources/SwiftFormatRules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormatRules/FullyIndirectEnum.swift @@ -35,7 +35,7 @@ public final class FullyIndirectEnum: SyntaxFormatRule { // Removes 'indirect' keyword from cases, reformats let newMembers = enumMembers.map { - (member: MemberDeclListItemSyntax) -> MemberDeclListItemSyntax in + (member: MemberBlockItemSyntax) -> MemberBlockItemSyntax in guard let caseMember = member.decl.as(EnumCaseDeclSyntax.self), let modifiers = caseMember.modifiers, modifiers.has(modifier: "indirect"), @@ -70,14 +70,14 @@ public final class FullyIndirectEnum: SyntaxFormatRule { name: TokenSyntax.identifier( "indirect", leadingTrivia: leadingTrivia, trailingTrivia: .spaces(1)), detail: nil) - let newMemberBlock = node.memberBlock.with(\.members, MemberDeclListSyntax(newMembers)) + let newMemberBlock = node.memberBlock.with(\.members, MemberBlockItemListSyntax(newMembers)) return DeclSyntax(newEnumDecl.addModifier(newModifier).with(\.memberBlock, newMemberBlock)) } /// Returns a value indicating whether all enum cases in the given list are indirect. /// /// Note that if the enum has no cases, this returns false. - private func allCasesAreIndirect(in members: MemberDeclListSyntax) -> Bool { + private func allCasesAreIndirect(in members: MemberBlockItemListSyntax) -> Bool { var hadCases = false for member in members { if let caseMember = member.decl.as(EnumCaseDeclSyntax.self) { diff --git a/Sources/SwiftFormatRules/GroupNumericLiterals.swift b/Sources/SwiftFormatRules/GroupNumericLiterals.swift index aa4ebb519..32ee3eab5 100644 --- a/Sources/SwiftFormatRules/GroupNumericLiterals.swift +++ b/Sources/SwiftFormatRules/GroupNumericLiterals.swift @@ -27,7 +27,7 @@ import SwiftSyntax /// TODO: Handle floating point literals. public final class GroupNumericLiterals: SyntaxFormatRule { public override func visit(_ node: IntegerLiteralExprSyntax) -> ExprSyntax { - var originalDigits = node.digits.text + var originalDigits = node.literal.text guard !originalDigits.contains("_") else { return ExprSyntax(node) } let isNegative = originalDigits.first == "-" @@ -59,11 +59,11 @@ public final class GroupNumericLiterals: SyntaxFormatRule { } newDigits = isNegative ? "-" + newDigits : newDigits - let result = node.with(\.digits, + let result = node.with(\.literal, TokenSyntax.integerLiteral( newDigits, - leadingTrivia: node.digits.leadingTrivia, - trailingTrivia: node.digits.trailingTrivia)) + leadingTrivia: node.literal.leadingTrivia, + trailingTrivia: node.literal.trailingTrivia)) return ExprSyntax(result) } diff --git a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift index 1242ab779..a4991c42c 100644 --- a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift +++ b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift @@ -12,7 +12,7 @@ import SwiftSyntax -extension ModifierListSyntax { +extension DeclModifierListSyntax { func has(modifier: String) -> Bool { return contains { $0.name.text == modifier } @@ -36,9 +36,9 @@ extension ModifierListSyntax { } /// Returns modifier list without the given modifier. - func remove(name: String) -> ModifierListSyntax { + func remove(name: String) -> DeclModifierListSyntax { let newModifiers = filter { $0.name.text != name } - return ModifierListSyntax(newModifiers) + return DeclModifierListSyntax(newModifiers) } /// Returns a formatted declaration modifier token with the given name. @@ -53,7 +53,7 @@ extension ModifierListSyntax { func insert( modifier: DeclModifierSyntax, at index: Int, formatTrivia: Bool = true - ) -> ModifierListSyntax { + ) -> DeclModifierListSyntax { guard index >= 0, index <= count else { return self } var newModifiers: [DeclModifierSyntax] = [] @@ -68,11 +68,11 @@ extension ModifierListSyntax { if index == 0 { guard formatTrivia else { newModifiers.insert(modifier, at: index) - return ModifierListSyntax(newModifiers) + return DeclModifierListSyntax(newModifiers) } guard let firstMod = first, let firstTok = firstMod.firstToken(viewMode: .sourceAccurate) else { newModifiers.insert(modifier, at: index) - return ModifierListSyntax(newModifiers) + return DeclModifierListSyntax(newModifiers) } let formattedMod = replaceTrivia( on: modifier, @@ -84,22 +84,22 @@ extension ModifierListSyntax { leadingTrivia: [], trailingTrivia: .spaces(1)) newModifiers.insert(formattedMod, at: 0) - return ModifierListSyntax(newModifiers) + return DeclModifierListSyntax(newModifiers) } else { newModifiers.insert(modifier, at: index) - return ModifierListSyntax(newModifiers) + return DeclModifierListSyntax(newModifiers) } } /// Returns modifier list with the given modifier at the end. /// Trivia manipulation optional by 'formatTrivia' - func append(modifier: DeclModifierSyntax, formatTrivia: Bool = true) -> ModifierListSyntax { + func append(modifier: DeclModifierSyntax, formatTrivia: Bool = true) -> DeclModifierListSyntax { return insert(modifier: modifier, at: count, formatTrivia: formatTrivia) } /// Returns modifier list with the given modifier at the beginning. /// Trivia manipulation optional by 'formatTrivia' - func prepend(modifier: DeclModifierSyntax, formatTrivia: Bool = true) -> ModifierListSyntax { + func prepend(modifier: DeclModifierSyntax, formatTrivia: Bool = true) -> DeclModifierListSyntax { return insert(modifier: modifier, at: 0, formatTrivia: formatTrivia) } } diff --git a/Sources/SwiftFormatRules/NeverForceUnwrap.swift b/Sources/SwiftFormatRules/NeverForceUnwrap.swift index bff517029..19e0243e1 100644 --- a/Sources/SwiftFormatRules/NeverForceUnwrap.swift +++ b/Sources/SwiftFormatRules/NeverForceUnwrap.swift @@ -32,7 +32,7 @@ public final class NeverForceUnwrap: SyntaxLintRule { return .visitChildren } - public override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: ForceUnwrapExprSyntax) -> SyntaxVisitorContinueKind { guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren } diagnose(.doNotForceUnwrap(name: node.expression.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description), on: node) return .skipChildren diff --git a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift index 1f1d52cfe..13e504404 100644 --- a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -42,7 +42,7 @@ public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule { // Ignores IBOutlet variables if let attributes = node.attributes { for attribute in attributes { - if (attribute.as(AttributeSyntax.self))?.attributeName.as(SimpleTypeIdentifierSyntax.self)?.name.text == "IBOutlet" { + if (attribute.as(AttributeSyntax.self))?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "IBOutlet" { return .skipChildren } } diff --git a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift index 8c1163a09..71ed1f50e 100644 --- a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift @@ -44,7 +44,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { accessKeywordToAdd = accessKeyword } - let newMembers = MemberDeclBlockSyntax( + let newMembers = MemberBlockSyntax( leftBrace: node.memberBlock.leftBrace, members: addMemberAccessKeywords(memDeclBlock: node.memberBlock, keyword: accessKeywordToAdd), rightBrace: node.memberBlock.rightBrace) @@ -78,10 +78,10 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { // Adds given keyword to all members in declaration block private func addMemberAccessKeywords( - memDeclBlock: MemberDeclBlockSyntax, + memDeclBlock: MemberBlockSyntax, keyword: DeclModifierSyntax - ) -> MemberDeclListSyntax { - var newMembers: [MemberDeclListItemSyntax] = [] + ) -> MemberBlockItemListSyntax { + var newMembers: [MemberBlockItemSyntax] = [] let formattedKeyword = replaceTrivia( on: keyword, token: keyword.name, @@ -96,7 +96,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { else { continue } newMembers.append(memberItem.with(\.decl, newDecl)) } - return MemberDeclListSyntax(newMembers) + return MemberBlockItemListSyntax(newMembers) } } diff --git a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift index fa27da83c..a478c04f2 100644 --- a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift @@ -102,10 +102,10 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { /// expression (either simple assignment with `=` or compound assignment with an operator like /// `+=`). private func isAssignmentExpression(_ expr: InfixOperatorExprSyntax) -> Bool { - if expr.operatorOperand.is(AssignmentExprSyntax.self) { + if expr.operator.is(AssignmentExprSyntax.self) { return true } - guard let binaryOp = expr.operatorOperand.as(BinaryOperatorExprSyntax.self) else { + guard let binaryOp = expr.operator.as(BinaryOperatorExprSyntax.self) else { return false } return context.operatorTable.infixOperator(named: binaryOp.operator.text)?.precedenceGroup diff --git a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift b/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift index f8966ec2c..6ad5e9bed 100644 --- a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift +++ b/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift @@ -126,7 +126,7 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { // When there are any additional or non-fallthrough statements, it isn't only a fallthrough. guard let onlyStatement = switchCase.statements.firstAndOnly, - onlyStatement.item.is(FallthroughStmtSyntax.self) + onlyStatement.item.is(FallThroughStmtSyntax.self) else { return false } @@ -168,7 +168,7 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { return cases.first! } - var newCaseItems: [CaseItemSyntax] = [] + var newCaseItems: [SwitchCaseItemSyntax] = [] let labels = cases.lazy.compactMap({ $0.label.as(SwitchCaseLabelSyntax.self) }) for label in labels.dropLast() { // We can blindly append all but the last case item because they must already have a trailing @@ -188,7 +188,7 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { newCaseItems.append(contentsOf: labels.last!.caseItems) let newCase = cases.last!.with(\.label, .case( - labels.last!.with(\.caseItems, CaseItemListSyntax(newCaseItems)))) + labels.last!.with(\.caseItems, SwitchCaseItemListSyntax(newCaseItems)))) // Only the first violation case can have displaced trivia, because any non-whitespace // trivia in the other violation cases would've prevented collapsing. diff --git a/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift b/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift index 4371f123f..2d09524e3 100644 --- a/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift +++ b/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift @@ -24,7 +24,7 @@ import SwiftSyntax /// Format: Redundant labels in case patterns are removed. public final class NoLabelsInCasePatterns: SyntaxFormatRule { public override func visit(_ node: SwitchCaseLabelSyntax) -> SwitchCaseLabelSyntax { - var newCaseItems: [CaseItemSyntax] = [] + var newCaseItems: [SwitchCaseItemSyntax] = [] for item in node.caseItems { guard let expPat = item.pattern.as(ExpressionPatternSyntax.self) else { newCaseItems.append(item) @@ -36,13 +36,13 @@ public final class NoLabelsInCasePatterns: SyntaxFormatRule { } // Search function call argument list for violations - var newArgs: [TupleExprElementSyntax] = [] + var newArgs: [LabeledExprSyntax] = [] for argument in funcCall.arguments { guard let label = argument.label else { newArgs.append(argument) continue } - guard let unresolvedPat = argument.expression.as(UnresolvedPatternExprSyntax.self), + guard let unresolvedPat = argument.expression.as(PatternExprSyntax.self), let valueBinding = unresolvedPat.pattern.as(ValueBindingPatternSyntax.self) else { newArgs.append(argument) @@ -59,13 +59,13 @@ public final class NoLabelsInCasePatterns: SyntaxFormatRule { newArgs.append(argument.with(\.label, nil).with(\.colon, nil)) } - let newArgList = TupleExprElementListSyntax(newArgs) + let newArgList = LabeledExprListSyntax(newArgs) let newFuncCall = funcCall.with(\.arguments, newArgList) let newExpPat = expPat.with(\.expression, ExprSyntax(newFuncCall)) let newItem = item.with(\.pattern, PatternSyntax(newExpPat)) newCaseItems.append(newItem) } - let newCaseItemList = CaseItemListSyntax(newCaseItems) + let newCaseItemList = SwitchCaseItemListSyntax(newCaseItems) return node.with(\.caseItems, newCaseItemList) } } diff --git a/Sources/SwiftFormatRules/NoLeadingUnderscores.swift b/Sources/SwiftFormatRules/NoLeadingUnderscores.swift index 2b467dab7..e8a8da0b0 100644 --- a/Sources/SwiftFormatRules/NoLeadingUnderscores.swift +++ b/Sources/SwiftFormatRules/NoLeadingUnderscores.swift @@ -31,7 +31,7 @@ public final class NoLeadingUnderscores: SyntaxLintRule { /// doesn't intend for arbitrary usage. public override class var isOptIn: Bool { return true } - public override func visit(_ node: AssociatedtypeDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren } @@ -107,7 +107,7 @@ public final class NoLeadingUnderscores: SyntaxLintRule { return .visitChildren } - public override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseIfNameStartsWithUnderscore(node.name) return .visitChildren } diff --git a/Sources/SwiftFormatRules/NoParensAroundConditions.swift b/Sources/SwiftFormatRules/NoParensAroundConditions.swift index 3a69c37b4..facc00671 100644 --- a/Sources/SwiftFormatRules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormatRules/NoParensAroundConditions.swift @@ -80,16 +80,16 @@ public final class NoParensAroundConditions: SyntaxFormatRule { /// FIXME(hbh): Parsing for SwitchExprSyntax is not implemented. public override func visit(_ node: SwitchExprSyntax) -> ExprSyntax { - guard let tup = node.expression.as(TupleExprSyntax.self), + guard let tup = node.subject.as(TupleExprSyntax.self), tup.elements.firstAndOnly != nil else { return super.visit(node) } return ExprSyntax( - node.with(\.expression, extractExpr(tup)).with(\.cases, visit(node.cases))) + node.with(\.subject, extractExpr(tup)).with(\.cases, visit(node.cases))) } - public override func visit(_ node: RepeatWhileStmtSyntax) -> StmtSyntax { + public override func visit(_ node: RepeatStmtSyntax) -> StmtSyntax { guard let tup = node.condition.as(TupleExprSyntax.self), tup.elements.firstAndOnly != nil else { diff --git a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift b/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift index 6d91de3fa..27636c712 100644 --- a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift +++ b/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift @@ -24,7 +24,7 @@ public final class NoVoidReturnOnFunctionSignature: SyntaxFormatRule { /// it for closure signatures, because that may introduce an ambiguity when closure signatures /// are inferred. public override func visit(_ node: FunctionSignatureSyntax) -> FunctionSignatureSyntax { - if let returnType = node.returnClause?.type.as(SimpleTypeIdentifierSyntax.self), returnType.name.text == "Void" { + if let returnType = node.returnClause?.type.as(IdentifierTypeSyntax.self), returnType.name.text == "Void" { diagnose(.removeRedundantReturn("Void"), on: returnType) return node.with(\.returnClause, nil) } diff --git a/Sources/SwiftFormatRules/OneCasePerLine.swift b/Sources/SwiftFormatRules/OneCasePerLine.swift index 9cd1af903..f1a4226fb 100644 --- a/Sources/SwiftFormatRules/OneCasePerLine.swift +++ b/Sources/SwiftFormatRules/OneCasePerLine.swift @@ -85,7 +85,7 @@ public final class OneCasePerLine: SyntaxFormatRule { } public override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { - var newMembers: [MemberDeclListItemSyntax] = [] + var newMembers: [MemberBlockItemSyntax] = [] for member in node.memberBlock.members { // If it's not a case declaration, or it's a case declaration with only one element, leave it @@ -100,7 +100,7 @@ public final class OneCasePerLine: SyntaxFormatRule { // Collect the elements of the case declaration until we see one that has either an associated // value or a raw value. for element in caseDecl.elements { - if element.associatedValue != nil || element.rawValue != nil { + if element.parameterClause != nil || element.rawValue != nil { // Once we reach one of these, we need to write out the ones we've collected so far, then // emit a separate case declaration with the associated/raw value element. diagnose(.moveAssociatedOrRawValueCase(name: element.name.text), on: element) @@ -123,7 +123,7 @@ public final class OneCasePerLine: SyntaxFormatRule { } } - let newMemberBlock = node.memberBlock.with(\.members, MemberDeclListSyntax(newMembers)) + let newMemberBlock = node.memberBlock.with(\.members, MemberBlockItemListSyntax(newMembers)) return DeclSyntax(node.with(\.memberBlock, newMemberBlock)) } } diff --git a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift index a862d9c82..34d9f1566 100644 --- a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift @@ -100,9 +100,9 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { /// Returns a type syntax node with the identifier `Void` whose leading and trailing trivia have /// been copied from the tuple type syntax node it is replacing. - private func makeVoidIdentifierType(toReplace node: TupleTypeSyntax) -> SimpleTypeIdentifierSyntax + private func makeVoidIdentifierType(toReplace node: TupleTypeSyntax) -> IdentifierTypeSyntax { - return SimpleTypeIdentifierSyntax( + return IdentifierTypeSyntax( name: TokenSyntax.identifier( "Void", leadingTrivia: node.firstToken(viewMode: .sourceAccurate)?.leadingTrivia ?? [], diff --git a/Sources/SwiftFormatRules/SemicolonSyntaxProtocol.swift b/Sources/SwiftFormatRules/SemicolonSyntaxProtocol.swift index b0fd39950..61e2fa112 100644 --- a/Sources/SwiftFormatRules/SemicolonSyntaxProtocol.swift +++ b/Sources/SwiftFormatRules/SemicolonSyntaxProtocol.swift @@ -17,7 +17,7 @@ protocol SemicolonSyntaxProtocol: SyntaxProtocol { var semicolon: TokenSyntax? { get set } } -extension MemberDeclListItemSyntax: SemicolonSyntaxProtocol {} +extension MemberBlockItemSyntax: SemicolonSyntaxProtocol {} extension CodeBlockItemSyntax: SemicolonSyntaxProtocol {} extension Syntax { diff --git a/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift b/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift index d37fbb919..577df511c 100644 --- a/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift +++ b/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift @@ -40,7 +40,7 @@ public final class UseLetInEveryBoundCaseVariable: SyntaxLintRule { while true { if let optionalExpr = expression.as(OptionalChainingExprSyntax.self) { expression = optionalExpr.expression - } else if let forcedExpr = expression.as(ForcedValueExprSyntax.self) { + } else if let forcedExpr = expression.as(ForceUnwrapExprSyntax.self) { expression = forcedExpr.expression } else { break diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index bd47dfdaa..f535f70cb 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -22,7 +22,7 @@ import SwiftSyntax /// converted to `[Element]`. public final class UseShorthandTypeNames: SyntaxFormatRule { - public override func visit(_ node: SimpleTypeIdentifierSyntax) -> TypeSyntax { + public override func visit(_ node: IdentifierTypeSyntax) -> TypeSyntax { // Ignore types that don't have generic arguments. guard let genericArgumentClause = node.genericArgumentClause else { return super.visit(node) @@ -34,7 +34,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // `Foo>.Bar` can still be transformed to `Foo<[Int]>.Bar` because the member // reference is not directly attached to the type that will be transformed, but we need to visit // the children so that we don't skip this). - guard let parent = node.parent, !parent.is(MemberTypeIdentifierSyntax.self) else { + guard let parent = node.parent, !parent.is(MemberTypeSyntax.self) else { return super.visit(node) } @@ -98,7 +98,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { return TypeSyntax(result) } - public override func visit(_ node: SpecializeExprSyntax) -> ExprSyntax { + public override func visit(_ node: GenericSpecializationExprSyntax) -> ExprSyntax { // `SpecializeExpr`s are found in the syntax tree when a generic type is encountered in an // expression context, such as `Array()`. In these situations, the corresponding array and // dictionary shorthand nodes will be expression nodes, not type nodes, so we may need to @@ -106,7 +106,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // appropriate equivalent. // Ignore nodes where the expression being specialized isn't a simple identifier. - guard let expression = node.expression.as(IdentifierExprSyntax.self) else { + guard let expression = node.expression.as(DeclReferenceExprSyntax.self) else { return super.visit(node) } @@ -130,7 +130,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let (leadingTrivia, trailingTrivia) = boundaryTrivia(around: Syntax(node)) let newNode: ExprSyntax? - switch expression.identifier.text { + switch expression.baseName.text { case "Array": guard let typeArgument = genericArgumentList.firstAndOnly else { newNode = nil @@ -171,7 +171,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { } if let newNode = newNode { - diagnose(.useTypeShorthand(type: expression.identifier.text), on: expression) + diagnose(.useTypeShorthand(type: expression.baseName.text), on: expression) return newNode } @@ -328,9 +328,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // otherwise the "?" applies to the return type instead of the function type. Attach the // leading trivia to the left-paren that we're adding in this case. let tupleExprElement = - TupleExprElementSyntax( + LabeledExprSyntax( label: nil, colon: nil, expression: wrappedTypeExpr, trailingComma: nil) - let tupleExprElementList = TupleExprElementListSyntax([tupleExprElement]) + let tupleExprElementList = LabeledExprListSyntax([tupleExprElement]) let tupleExpr = TupleExprSyntax( leftParen: TokenSyntax.leftParenToken(leadingTrivia: leadingTrivia ?? []), elements: tupleExprElementList, @@ -361,16 +361,16 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { private func expressionRepresentation(of type: TypeSyntax) -> ExprSyntax? { switch Syntax(type).as(SyntaxEnum.self) { case .identifierType(let simpleTypeIdentifier): - let identifierExpr = IdentifierExprSyntax( - identifier: simpleTypeIdentifier.name, - declNameArguments: nil) + let identifierExpr = DeclReferenceExprSyntax( + baseName: simpleTypeIdentifier.name, + argumentNames: nil) // If the type has a generic argument clause, we need to construct a `SpecializeExpr` to wrap // the identifier and the generic arguments. Otherwise, we can return just the // `IdentifierExpr` itself. if let genericArgumentClause = simpleTypeIdentifier.genericArgumentClause { let newGenericArgumentClause = visit(genericArgumentClause) - let result = SpecializeExprSyntax( + let result = GenericSpecializationExprSyntax( expression: ExprSyntax(identifierExpr), genericArgumentClause: newGenericArgumentClause) return ExprSyntax(result) @@ -437,21 +437,21 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { } private func expressionRepresentation(of tupleTypeElements: TupleTypeElementListSyntax) - -> TupleExprElementListSyntax? + -> LabeledExprListSyntax? { guard !tupleTypeElements.isEmpty else { return nil } - var exprElements = [TupleExprElementSyntax]() + var exprElements = [LabeledExprSyntax]() for typeElement in tupleTypeElements { guard let elementExpr = expressionRepresentation(of: typeElement.type) else { return nil } exprElements.append( - TupleExprElementSyntax( - label: typeElement.name, + LabeledExprSyntax( + label: typeElement.firstName, colon: typeElement.colon, expression: elementExpr, trailingComma: typeElement.trailingComma)) } - return TupleExprElementListSyntax(exprElements) + return LabeledExprListSyntax(exprElements) } private func makeFunctionTypeExpression( @@ -533,7 +533,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { /// Returns true if the given type identifier node represents the type of a mutable variable or /// stored property that does not have an initializer clause. - private func isTypeOfUninitializedStoredVar(_ node: SimpleTypeIdentifierSyntax) -> Bool { + private func isTypeOfUninitializedStoredVar(_ node: IdentifierTypeSyntax) -> Bool { if let typeAnnotation = node.parent?.as(TypeAnnotationSyntax.self), let patternBinding = nearestAncestor(of: typeAnnotation, type: PatternBindingSyntax.self), isStoredProperty(patternBinding), diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift index 10498bd75..5d3509ba6 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift @@ -83,7 +83,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { /// - properties: The properties from the enclosing type. /// - Returns: Whether the initializer has the same access level as the synthesized initializer. private func matchesAccessLevel( - modifiers: ModifierListSyntax?, properties: [VariableDeclSyntax] + modifiers: DeclModifierListSyntax?, properties: [VariableDeclSyntax] ) -> Bool { let synthesizedAccessLevel = synthesizedInitAccessLevel(using: properties) let accessLevel = modifiers?.accessLevelModifier @@ -117,9 +117,9 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { // doesn't match the memberwise initializer. let isVarDecl = property.bindingSpecifier.tokenKind == .keyword(.var) if isVarDecl, let initializer = property.firstInitializer { - guard let defaultArg = parameter.defaultArgument else { return false } + guard let defaultArg = parameter.defaultValue else { return false } guard initializer.value.description == defaultArg.value.description else { return false } - } else if parameter.defaultArgument != nil { + } else if parameter.defaultValue != nil { return false } @@ -143,7 +143,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { for statement in initBody.statements { guard let expr = statement.item.as(InfixOperatorExprSyntax.self), - expr.operatorOperand.is(AssignmentExprSyntax.self) + expr.operator.is(AssignmentExprSyntax.self) else { return false } @@ -159,13 +159,13 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { return false } - leftName = memberAccessExpr.name.text + leftName = memberAccessExpr.declName.baseName.text } else { return false } - if let identifierExpr = expr.rightOperand.as(IdentifierExprSyntax.self) { - rightName = identifierExpr.identifier.text + if let identifierExpr = expr.rightOperand.as(DeclReferenceExprSyntax.self) { + rightName = identifierExpr.baseName.text } else { return false } diff --git a/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift b/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift index e245edc9c..fc8566d8a 100644 --- a/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift +++ b/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift @@ -60,7 +60,7 @@ public final class UseTripleSlashForDocumentationComments: SyntaxFormatRule { return convertDocBlockCommentToDocLineComment(DeclSyntax(node)) } - public override func visit(_ node: TypealiasDeclSyntax) -> DeclSyntax { + public override func visit(_ node: TypeAliasDeclSyntax) -> DeclSyntax { return convertDocBlockCommentToDocLineComment(DeclSyntax(node)) } diff --git a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift index 09a1be91d..3ac45ef97 100644 --- a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift @@ -25,7 +25,7 @@ public final class UseWhereClausesInForLoops: SyntaxFormatRule { /// be enabled by default. public override class var isOptIn: Bool { return true } - public override func visit(_ node: ForInStmtSyntax) -> StmtSyntax { + public override func visit(_ node: ForStmtSyntax) -> StmtSyntax { // Extract IfStmt node if it's the only node in the function's body. guard !node.body.statements.isEmpty else { return StmtSyntax(node) } let firstStatement = node.body.statements.first! @@ -49,8 +49,8 @@ public final class UseWhereClausesInForLoops: SyntaxFormatRule { private func diagnoseAndUpdateForInStatement( firstStmt: StmtSyntax, - forInStmt: ForInStmtSyntax - ) -> ForInStmtSyntax { + forInStmt: ForStmtSyntax + ) -> ForStmtSyntax { switch Syntax(firstStmt).as(SyntaxEnum.self) { case .expressionStmt(let exprStmt): switch Syntax(exprStmt.expression).as(SyntaxEnum.self) { @@ -95,10 +95,10 @@ public final class UseWhereClausesInForLoops: SyntaxFormatRule { } fileprivate func updateWithWhereCondition( - node: ForInStmtSyntax, + node: ForStmtSyntax, condition: ExprSyntax, statements: CodeBlockItemListSyntax -) -> ForInStmtSyntax { +) -> ForStmtSyntax { // Construct a new `where` clause with the condition. let lastToken = node.sequence.lastToken(viewMode: .sourceAccurate) var whereLeadingTrivia = Trivia() @@ -111,7 +111,7 @@ fileprivate func updateWithWhereCondition( ) let whereClause = WhereClauseSyntax( whereKeyword: whereKeyword, - guardResult: condition + condition: condition ) // Replace the where clause and extract the body from the IfStmt. diff --git a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift index e2f5422b4..090d000e8 100644 --- a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift @@ -106,7 +106,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { if returnClause == nil && returnsDescription != nil { diagnose(.removeReturnComment(funcName: name), on: node) } else if let returnClause = returnClause, returnsDescription == nil { - if let returnTypeIdentifier = returnClause.type.as(SimpleTypeIdentifierSyntax.self), + if let returnTypeIdentifier = returnClause.type.as(IdentifierTypeSyntax.self), returnTypeIdentifier.name.text == "Never" { return From 18d598ee6e4ccfb19392f9fd9e9d865095826760 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Fri, 4 Aug 2023 09:32:39 -0400 Subject: [PATCH 076/332] Move the default `Configuration.init()` into a separate file. This is meant to make it easier for users forking or building their own swift-format to change the hardcoded default configuration. This also adds a helper for the default test configuration. We can't let the unit tests rely on the default configuration because that would make the tests fail when run with a swift-format binary that has a different default configuration. This approach lets the default test configuration differ from the "real" default configuration. --- .../Configuration+Default.swift | 41 ++++++ .../Configuration.swift | 138 +++++++++++------- .../Configuration+Testing.swift | 45 ++++++ .../AssignmentExprTests.swift | 4 +- .../AttributeTests.swift | 6 +- .../BinaryOperatorExprTests.swift | 4 +- .../ClassDeclTests.swift | 12 +- .../ClosureExprTests.swift | 8 +- .../DeclNameArgumentTests.swift | 4 +- .../EnumDeclTests.swift | 14 +- .../ExtensionDeclTests.swift | 6 +- .../FunctionCallTests.swift | 6 +- .../FunctionDeclTests.swift | 30 ++-- .../IfConfigTests.swift | 2 +- .../IfStmtTests.swift | 2 +- .../InitializerDeclTests.swift | 16 +- .../MacroDeclTests.swift | 18 +-- .../MemberAccessExprTests.swift | 10 +- .../ObjectLiteralExprTests.swift | 8 +- .../PrettyPrintTestCase.swift | 2 +- .../ProtocolDeclTests.swift | 4 +- .../RepeatStmtTests.swift | 2 +- .../RespectsExistingLineBreaksTests.swift | 2 +- .../StructDeclTests.swift | 12 +- .../SubscriptDeclTests.swift | 12 +- .../SwitchCaseIndentConfigTests.swift | 16 +- .../SwitchStmtTests.swift | 2 +- .../TryCatchTests.swift | 4 +- .../FileScopedDeclarationPrivacyTests.swift | 2 +- .../LintOrFormatRuleTestCase.swift | 4 +- .../WhitespaceTestCase.swift | 2 +- 31 files changed, 276 insertions(+), 162 deletions(-) create mode 100644 Sources/SwiftFormatConfiguration/Configuration+Default.swift create mode 100644 Sources/SwiftFormatTestSupport/Configuration+Testing.swift diff --git a/Sources/SwiftFormatConfiguration/Configuration+Default.swift b/Sources/SwiftFormatConfiguration/Configuration+Default.swift new file mode 100644 index 000000000..61ba05c34 --- /dev/null +++ b/Sources/SwiftFormatConfiguration/Configuration+Default.swift @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +extension Configuration { + /// Creates a new `Configuration` with default values. + /// + /// This initializer is isolated to its own file to make it easier for users who are forking or + /// building swift-format themselves to hardcode a different default configuration. To do this, + /// simply replace this file with your own default initializer that sets the values to whatever + /// you want. + /// + /// When swift-format reads a configuration file from disk, any values that are not specified in + /// the JSON will be populated from this default configuration. + public init() { + self.rules = Self.defaultRuleEnablements + self.maximumBlankLines = 1 + self.lineLength = 100 + self.tabWidth = 8 + self.indentation = .spaces(2) + self.respectsExistingLineBreaks = true + self.lineBreakBeforeControlFlowKeywords = false + self.lineBreakBeforeEachArgument = false + self.lineBreakBeforeEachGenericRequirement = false + self.prioritizeKeepingFunctionOutputTogether = false + self.indentConditionalCompilationBlocks = true + self.lineBreakAroundMultilineExpressionChainComponents = false + self.fileScopedDeclarationPrivacy = FileScopedDeclarationPrivacyConfiguration() + self.indentSwitchCaseLabels = false + self.spacesAroundRangeFormationOperators = false + self.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() + } +} diff --git a/Sources/SwiftFormatConfiguration/Configuration.swift b/Sources/SwiftFormatConfiguration/Configuration.swift index f5caaaabc..13064c6e7 100644 --- a/Sources/SwiftFormatConfiguration/Configuration.swift +++ b/Sources/SwiftFormatConfiguration/Configuration.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -14,7 +14,12 @@ import Foundation /// A version number that can be specified in the configuration file, which allows us to change the /// format in the future if desired and still support older files. -fileprivate let highestSupportedConfigurationVersion = 1 +/// +/// Note that *adding* new configuration values is not a version-breaking change; swift-format will +/// use default values when loading older configurations that don't contain the new settings. This +/// value only needs to be updated if the configuration changes in a way that would be incompatible +/// with the previous format. +internal let highestSupportedConfigurationVersion = 1 /// Holds the complete set of configured values and defaults. public struct Configuration: Codable, Equatable { @@ -39,30 +44,36 @@ public struct Configuration: Codable, Equatable { case noAssignmentInExpressions } + /// A dictionary containing the default enabled/disabled states of rules, keyed by the rules' + /// names. + /// + /// This value is generated by `generate-pipeline` based on the `isOptIn` value of each rule. + public static let defaultRuleEnablements: [String: Bool] = RuleRegistry.rules + /// The version of this configuration. - private let version: Int + private var version: Int = highestSupportedConfigurationVersion /// MARK: Common configuration /// The dictionary containing the rule names that we wish to run on. A rule is not used if it is /// marked as `false`, or if it is missing from the dictionary. - public var rules: [String: Bool] = RuleRegistry.rules + public var rules: [String: Bool] /// The maximum number of consecutive blank lines that may appear in a file. - public var maximumBlankLines = 1 + public var maximumBlankLines: Int /// The maximum length of a line of source code, after which the formatter will break lines. - public var lineLength = 100 + public var lineLength: Int /// The width of the horizontal tab in spaces. /// /// This value is used when converting indentation types (for example, from tabs into spaces). - public var tabWidth = 8 + public var tabWidth: Int /// A value representing a single level of indentation. /// /// All indentation will be conducted in multiples of this configuration. - public var indentation: Indent = .spaces(2) + public var indentation: Indent /// Indicates that the formatter should try to respect users' discretionary line breaks when /// possible. @@ -71,7 +82,7 @@ public struct Configuration: Codable, Equatable { /// line, but for readability the user might break it inside the curly braces. If this setting is /// true, those line breaks will be kept. If this setting is false, the formatter will act more /// "opinionated" and collapse the statement onto a single line. - public var respectsExistingLineBreaks = true + public var respectsExistingLineBreaks: Bool /// MARK: Rule-specific configuration @@ -81,7 +92,7 @@ public struct Configuration: Codable, Equatable { /// If true, a line break will be added before the keyword, forcing it onto its own line. If /// false (the default), the keyword will be placed after the closing brace (separated by a /// space). - public var lineBreakBeforeControlFlowKeywords = false + public var lineBreakBeforeControlFlowKeywords: Bool /// Determines the line-breaking behavior for generic arguments and function arguments when a /// declaration is wrapped onto multiple lines. @@ -89,7 +100,7 @@ public struct Configuration: Codable, Equatable { /// If false (the default), arguments will be laid out horizontally first, with line breaks only /// being fired when the line length would be exceeded. If true, a line break will be added before /// each argument, forcing the entire argument list to be laid out vertically. - public var lineBreakBeforeEachArgument = false + public var lineBreakBeforeEachArgument: Bool /// Determines the line-breaking behavior for generic requirements when the requirements list /// is wrapped onto multiple lines. @@ -97,7 +108,7 @@ public struct Configuration: Codable, Equatable { /// If true, a line break will be added before each requirement, forcing the entire requirements /// list to be laid out vertically. If false (the default), requirements will be laid out /// horizontally first, with line breaks only being fired when the line length would be exceeded. - public var lineBreakBeforeEachGenericRequirement = false + public var lineBreakBeforeEachGenericRequirement: Bool /// Determines if function-like declaration outputs should be prioritized to be together with the /// function signature right (closing) parenthesis. @@ -107,21 +118,21 @@ public struct Configuration: Codable, Equatable { /// a line break will be fired after the function signature first, indenting the declaration output /// one additional level. If true, A line break will be fired further up in the function's /// declaration (e.g. generic parameters, parameters) before breaking on the function's output. - public var prioritizeKeepingFunctionOutputTogether = false + public var prioritizeKeepingFunctionOutputTogether: Bool /// Determines the indentation behavior for `#if`, `#elseif`, and `#else`. - public var indentConditionalCompilationBlocks = true + public var indentConditionalCompilationBlocks: Bool /// Determines whether line breaks should be forced before and after multiline components of /// dot-chained expressions, such as function calls and subscripts chained together through member /// access (i.e. "." expressions). When any component is multiline and this option is true, a line /// break is forced before the "." of the component and after the component's closing delimiter /// (i.e. right paren, right bracket, right brace, etc.). - public var lineBreakAroundMultilineExpressionChainComponents = false + public var lineBreakAroundMultilineExpressionChainComponents: Bool /// Determines the formal access level (i.e., the level specified in source code) for file-scoped /// declarations whose effective access level is private to the containing file. - public var fileScopedDeclarationPrivacy = FileScopedDeclarationPrivacyConfiguration() + public var fileScopedDeclarationPrivacy: FileScopedDeclarationPrivacyConfiguration /// Determines if `case` statements should be indented compared to the containing `switch` block. /// @@ -142,19 +153,14 @@ public struct Configuration: Codable, Equatable { /// ... /// } ///``` - public var indentSwitchCaseLabels = false + public var indentSwitchCaseLabels: Bool /// Determines whether whitespace should be forced before and after the range formation operators /// `...` and `..<`. - public var spacesAroundRangeFormationOperators = false + public var spacesAroundRangeFormationOperators: Bool /// Contains exceptions for the `NoAssignmentInExpressions` rule. - public var noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() - - /// Constructs a Configuration with all default values. - public init() { - self.version = highestSupportedConfigurationVersion - } + public var noAssignmentInExpressions: NoAssignmentInExpressionsConfiguration /// Constructs a Configuration by loading it from a configuration file. public init(contentsOf url: URL) throws { @@ -165,11 +171,6 @@ public struct Configuration: Codable, Equatable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - // Unfortunately, to allow the user to leave out configuration options in the JSON, we would - // have to make them optional properties, but that makes using the type in the rest of the code - // more annoying because we'd have to unwrap everything. So, we override this initializer and - // provide the defaults ourselves if needed. - // If the version number is not present, assume it is 1. self.version = try container.decodeIfPresent(Int.self, forKey: .version) ?? 1 guard version <= highestSupportedConfigurationVersion else { @@ -182,47 +183,70 @@ public struct Configuration: Codable, Equatable { // If we ever introduce a new version, this is where we should switch on the decoded version // number and dispatch to different decoding methods. - self.maximumBlankLines - = try container.decodeIfPresent(Int.self, forKey: .maximumBlankLines) ?? 1 - self.lineLength = try container.decodeIfPresent(Int.self, forKey: .lineLength) ?? 100 - self.tabWidth = try container.decodeIfPresent(Int.self, forKey: .tabWidth) ?? 8 - self.indentation - = try container.decodeIfPresent(Indent.self, forKey: .indentation) ?? .spaces(2) - self.respectsExistingLineBreaks - = try container.decodeIfPresent(Bool.self, forKey: .respectsExistingLineBreaks) ?? true - self.lineBreakBeforeControlFlowKeywords - = try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeControlFlowKeywords) ?? false - self.lineBreakBeforeEachArgument - = try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachArgument) ?? false - self.lineBreakBeforeEachGenericRequirement - = try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachGenericRequirement) ?? false - self.prioritizeKeepingFunctionOutputTogether - = try container.decodeIfPresent(Bool.self, forKey: .prioritizeKeepingFunctionOutputTogether) ?? false - self.indentConditionalCompilationBlocks - = try container.decodeIfPresent(Bool.self, forKey: .indentConditionalCompilationBlocks) ?? true + // Unfortunately, to allow the user to leave out configuration options in the JSON, we would + // have to make them optional properties, but that makes using the type in the rest of the code + // more annoying because we'd have to unwrap everything. So, we override this initializer and + // provide the defaults ourselves if needed. We get those defaults by pulling them from a + // default-initialized instance. + let defaults = Configuration() + + self.maximumBlankLines = + try container.decodeIfPresent(Int.self, forKey: .maximumBlankLines) + ?? defaults.maximumBlankLines + self.lineLength = + try container.decodeIfPresent(Int.self, forKey: .lineLength) + ?? defaults.lineLength + self.tabWidth = + try container.decodeIfPresent(Int.self, forKey: .tabWidth) + ?? defaults.tabWidth + self.indentation = + try container.decodeIfPresent(Indent.self, forKey: .indentation) + ?? defaults.indentation + self.respectsExistingLineBreaks = + try container.decodeIfPresent(Bool.self, forKey: .respectsExistingLineBreaks) + ?? defaults.respectsExistingLineBreaks + self.lineBreakBeforeControlFlowKeywords = + try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeControlFlowKeywords) + ?? defaults.lineBreakBeforeControlFlowKeywords + self.lineBreakBeforeEachArgument = + try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachArgument) + ?? defaults.lineBreakBeforeEachArgument + self.lineBreakBeforeEachGenericRequirement = + try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachGenericRequirement) + ?? defaults.lineBreakBeforeEachGenericRequirement + self.prioritizeKeepingFunctionOutputTogether = + try container.decodeIfPresent(Bool.self, forKey: .prioritizeKeepingFunctionOutputTogether) + ?? defaults.prioritizeKeepingFunctionOutputTogether + self.indentConditionalCompilationBlocks = + try container.decodeIfPresent(Bool.self, forKey: .indentConditionalCompilationBlocks) + ?? defaults.indentConditionalCompilationBlocks self.lineBreakAroundMultilineExpressionChainComponents = try container.decodeIfPresent( - Bool.self, forKey: .lineBreakAroundMultilineExpressionChainComponents) ?? false + Bool.self, forKey: .lineBreakAroundMultilineExpressionChainComponents) + ?? defaults.lineBreakAroundMultilineExpressionChainComponents self.spacesAroundRangeFormationOperators = try container.decodeIfPresent( - Bool.self, forKey: .spacesAroundRangeFormationOperators) ?? false + Bool.self, forKey: .spacesAroundRangeFormationOperators) + ?? defaults.spacesAroundRangeFormationOperators self.fileScopedDeclarationPrivacy = try container.decodeIfPresent( FileScopedDeclarationPrivacyConfiguration.self, forKey: .fileScopedDeclarationPrivacy) - ?? FileScopedDeclarationPrivacyConfiguration() - self.indentSwitchCaseLabels - = try container.decodeIfPresent(Bool.self, forKey: .indentSwitchCaseLabels) ?? false + ?? defaults.fileScopedDeclarationPrivacy + self.indentSwitchCaseLabels = + try container.decodeIfPresent(Bool.self, forKey: .indentSwitchCaseLabels) + ?? defaults.indentSwitchCaseLabels self.noAssignmentInExpressions = try container.decodeIfPresent( NoAssignmentInExpressionsConfiguration.self, forKey: .noAssignmentInExpressions) - ?? NoAssignmentInExpressionsConfiguration() + ?? defaults.noAssignmentInExpressions // If the `rules` key is not present at all, default it to the built-in set // so that the behavior is the same as if the configuration had been // default-initialized. To get an empty rules dictionary, one can explicitly // set the `rules` key to `{}`. - self.rules - = try container.decodeIfPresent([String: Bool].self, forKey: .rules) ?? RuleRegistry.rules + self.rules = + try container.decodeIfPresent([String: Bool].self, forKey: .rules) + ?? defaults.rules } public func encode(to encoder: Encoder) throws { @@ -295,6 +319,8 @@ public struct FileScopedDeclarationPrivacyConfiguration: Codable, Equatable { /// The formal access level to use when encountering a file-scoped declaration with effective /// private access. public var accessLevel: AccessLevel = .private + + public init() {} } /// Configuration for the `NoAssignmentInExpressions` rule. @@ -307,4 +333,6 @@ public struct NoAssignmentInExpressionsConfiguration: Codable, Equatable { // in the test. "XCTAssertNoThrow" ] + + public init() {} } diff --git a/Sources/SwiftFormatTestSupport/Configuration+Testing.swift b/Sources/SwiftFormatTestSupport/Configuration+Testing.swift new file mode 100644 index 000000000..4ba83a450 --- /dev/null +++ b/Sources/SwiftFormatTestSupport/Configuration+Testing.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftFormatConfiguration + +extension Configuration { + /// The default configuration to be used during unit tests. + /// + /// This configuration is separate from `Configuration.init()` so that that configuration can be + /// replaced without breaking tests that implicitly rely on it. Unfortunately, since this is in a + /// different module than where `Configuration` is defined, we can't make this an initializer that + /// would enforce that every field of `Configuration` is initialized here (we're forced to + /// delegate to another initializer first, which defeats the purpose). So, users adding new + /// configuration settings shouls be sure to supply a default here for testing, otherwise they + /// will be implicitly relying on the real default. + public static var forTesting: Configuration { + var config = Configuration() + config.rules = Configuration.defaultRuleEnablements + config.maximumBlankLines = 1 + config.lineLength = 100 + config.tabWidth = 8 + config.indentation = .spaces(2) + config.respectsExistingLineBreaks = true + config.lineBreakBeforeControlFlowKeywords = false + config.lineBreakBeforeEachArgument = false + config.lineBreakBeforeEachGenericRequirement = false + config.prioritizeKeepingFunctionOutputTogether = false + config.indentConditionalCompilationBlocks = true + config.lineBreakAroundMultilineExpressionChainComponents = false + config.fileScopedDeclarationPrivacy = FileScopedDeclarationPrivacyConfiguration() + config.indentSwitchCaseLabels = false + config.spacesAroundRangeFormationOperators = false + config.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() + return config + } +} diff --git a/Tests/SwiftFormatPrettyPrintTests/AssignmentExprTests.swift b/Tests/SwiftFormatPrettyPrintTests/AssignmentExprTests.swift index a7c735f56..a1e9452a3 100644 --- a/Tests/SwiftFormatPrettyPrintTests/AssignmentExprTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/AssignmentExprTests.swift @@ -84,7 +84,7 @@ final class AssignmentExprTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual( input: input, expected: expectedWithArgBinPacking, linelength: 35, configuration: config) @@ -166,7 +166,7 @@ final class AssignmentExprTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual( input: input, expected: expectedWithArgBinPacking, linelength: 35, configuration: config) diff --git a/Tests/SwiftFormatPrettyPrintTests/AttributeTests.swift b/Tests/SwiftFormatPrettyPrintTests/AttributeTests.swift index edc3bb8bc..d67bc73d3 100644 --- a/Tests/SwiftFormatPrettyPrintTests/AttributeTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/AttributeTests.swift @@ -90,7 +90,7 @@ final class AttributeTests: PrettyPrintTestCase { """ - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.lineBreakBeforeEachArgument = true assertPrettyPrintEqual( input: input, expected: expected, linelength: 32, configuration: configuration) @@ -166,7 +166,7 @@ final class AttributeTests: PrettyPrintTestCase { """ - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.lineBreakBeforeEachArgument = true assertPrettyPrintEqual( input: input, expected: expected, linelength: 40, configuration: configuration) @@ -238,7 +238,7 @@ final class AttributeTests: PrettyPrintTestCase { """ - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.lineBreakBeforeEachArgument = true assertPrettyPrintEqual( input: input, expected: expected, linelength: 40, configuration: configuration) diff --git a/Tests/SwiftFormatPrettyPrintTests/BinaryOperatorExprTests.swift b/Tests/SwiftFormatPrettyPrintTests/BinaryOperatorExprTests.swift index 1a97cd76e..54f12e008 100644 --- a/Tests/SwiftFormatPrettyPrintTests/BinaryOperatorExprTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/BinaryOperatorExprTests.swift @@ -50,7 +50,7 @@ final class BinaryOperatorExprTests: PrettyPrintTestCase { """ - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.spacesAroundRangeFormationOperators = false assertPrettyPrintEqual( input: input, expected: expected, linelength: 80, configuration: configuration) @@ -78,7 +78,7 @@ final class BinaryOperatorExprTests: PrettyPrintTestCase { """ - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.spacesAroundRangeFormationOperators = true assertPrettyPrintEqual( input: input, expected: expected, linelength: 80, configuration: configuration) diff --git a/Tests/SwiftFormatPrettyPrintTests/ClassDeclTests.swift b/Tests/SwiftFormatPrettyPrintTests/ClassDeclTests.swift index 4d9dc76c7..86f47bf0f 100644 --- a/Tests/SwiftFormatPrettyPrintTests/ClassDeclTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/ClassDeclTests.swift @@ -79,7 +79,7 @@ final class ClassDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) } @@ -120,7 +120,7 @@ final class ClassDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) } @@ -267,7 +267,7 @@ final class ClassDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 60, configuration: config) } @@ -343,7 +343,7 @@ final class ClassDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 60, configuration: config) } @@ -424,7 +424,7 @@ final class ClassDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -458,7 +458,7 @@ final class ClassDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) diff --git a/Tests/SwiftFormatPrettyPrintTests/ClosureExprTests.swift b/Tests/SwiftFormatPrettyPrintTests/ClosureExprTests.swift index cab0b8553..41332f5d7 100644 --- a/Tests/SwiftFormatPrettyPrintTests/ClosureExprTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/ClosureExprTests.swift @@ -65,7 +65,7 @@ final class ClosureExprTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 42, configuration: config) } @@ -117,7 +117,7 @@ final class ClosureExprTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 42, configuration: config) } @@ -151,7 +151,7 @@ final class ClosureExprTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: config) } @@ -479,7 +479,7 @@ final class ClosureExprTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.prioritizeKeepingFunctionOutputTogether = true assertPrettyPrintEqual( input: input, expected: expectedKeepingOutputTogether, linelength: 50, configuration: config) diff --git a/Tests/SwiftFormatPrettyPrintTests/DeclNameArgumentTests.swift b/Tests/SwiftFormatPrettyPrintTests/DeclNameArgumentTests.swift index 289b65042..f4138d1d6 100644 --- a/Tests/SwiftFormatPrettyPrintTests/DeclNameArgumentTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/DeclNameArgumentTests.swift @@ -41,7 +41,7 @@ final class DeclNameArgumentTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: config) } @@ -114,7 +114,7 @@ final class DeclNameArgumentTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: config) } diff --git a/Tests/SwiftFormatPrettyPrintTests/EnumDeclTests.swift b/Tests/SwiftFormatPrettyPrintTests/EnumDeclTests.swift index bf015c132..42935e6e5 100644 --- a/Tests/SwiftFormatPrettyPrintTests/EnumDeclTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/EnumDeclTests.swift @@ -79,7 +79,7 @@ final class EnumDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) } @@ -120,7 +120,7 @@ final class EnumDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 31, configuration: config) } @@ -198,7 +198,7 @@ final class EnumDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) } @@ -324,7 +324,7 @@ final class EnumDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 60, configuration: config) } @@ -413,7 +413,7 @@ final class EnumDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 60, configuration: config) } @@ -494,7 +494,7 @@ final class EnumDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -528,7 +528,7 @@ final class EnumDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) diff --git a/Tests/SwiftFormatPrettyPrintTests/ExtensionDeclTests.swift b/Tests/SwiftFormatPrettyPrintTests/ExtensionDeclTests.swift index 632ba07c1..c8a030c84 100644 --- a/Tests/SwiftFormatPrettyPrintTests/ExtensionDeclTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/ExtensionDeclTests.swift @@ -161,7 +161,7 @@ final class ExtensionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 70, configuration: config) } @@ -249,7 +249,7 @@ final class ExtensionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 70, configuration: config) } @@ -362,7 +362,7 @@ final class ExtensionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } diff --git a/Tests/SwiftFormatPrettyPrintTests/FunctionCallTests.swift b/Tests/SwiftFormatPrettyPrintTests/FunctionCallTests.swift index c4978ae1d..7ac04ac34 100644 --- a/Tests/SwiftFormatPrettyPrintTests/FunctionCallTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/FunctionCallTests.swift @@ -54,7 +54,7 @@ final class FunctionCallTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 45, configuration: config) } @@ -92,7 +92,7 @@ final class FunctionCallTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 45, configuration: config) } @@ -127,7 +127,7 @@ final class FunctionCallTests: PrettyPrintTestCase { ) """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 45, configuration: config) } diff --git a/Tests/SwiftFormatPrettyPrintTests/FunctionDeclTests.swift b/Tests/SwiftFormatPrettyPrintTests/FunctionDeclTests.swift index 34cdb236a..2c1646c4d 100644 --- a/Tests/SwiftFormatPrettyPrintTests/FunctionDeclTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/FunctionDeclTests.swift @@ -41,7 +41,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -84,7 +84,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -147,7 +147,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -192,7 +192,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -239,7 +239,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: config) } @@ -285,7 +285,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: config) } @@ -352,7 +352,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -421,7 +421,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) @@ -618,7 +618,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) } @@ -744,7 +744,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { } """ - var config = Configuration() + var config = Configuration.forTesting config.prioritizeKeepingFunctionOutputTogether = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 23, configuration: config) @@ -830,7 +830,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { } """ - var config = Configuration() + var config = Configuration.forTesting config.prioritizeKeepingFunctionOutputTogether = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 23, configuration: config) } @@ -919,7 +919,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { } """ - var config = Configuration() + var config = Configuration.forTesting config.prioritizeKeepingFunctionOutputTogether = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 23, configuration: config) @@ -980,7 +980,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 35, configuration: config) } @@ -1177,7 +1177,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 49, configuration: config) } @@ -1222,7 +1222,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 49, configuration: config) } diff --git a/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift b/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift index 5e8e1199f..6558430e1 100644 --- a/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift @@ -112,7 +112,7 @@ final class IfConfigTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.indentConditionalCompilationBlocks = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 45, configuration: config) } diff --git a/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift b/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift index c9320884b..6c5d74b00 100644 --- a/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift @@ -148,7 +148,7 @@ final class IfStmtTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeControlFlowKeywords = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: config) } diff --git a/Tests/SwiftFormatPrettyPrintTests/InitializerDeclTests.swift b/Tests/SwiftFormatPrettyPrintTests/InitializerDeclTests.swift index 0ca0d75d1..0582cfeec 100644 --- a/Tests/SwiftFormatPrettyPrintTests/InitializerDeclTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/InitializerDeclTests.swift @@ -41,7 +41,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -85,7 +85,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -129,7 +129,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -163,7 +163,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -245,7 +245,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -289,7 +289,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) @@ -369,7 +369,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: config) } @@ -407,7 +407,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: config) diff --git a/Tests/SwiftFormatPrettyPrintTests/MacroDeclTests.swift b/Tests/SwiftFormatPrettyPrintTests/MacroDeclTests.swift index 0529491c4..1cbee43b7 100644 --- a/Tests/SwiftFormatPrettyPrintTests/MacroDeclTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/MacroDeclTests.swift @@ -22,7 +22,7 @@ final class MacroDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 58, configuration: config) } @@ -46,7 +46,7 @@ final class MacroDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 58, configuration: config) } @@ -83,7 +83,7 @@ final class MacroDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 58, configuration: config) } @@ -113,7 +113,7 @@ final class MacroDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 44, configuration: config) } @@ -141,7 +141,7 @@ final class MacroDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 44, configuration: config) } @@ -191,7 +191,7 @@ final class MacroDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 51, configuration: config) } @@ -243,7 +243,7 @@ final class MacroDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) @@ -330,7 +330,7 @@ final class MacroDeclTests: PrettyPrintTestCase { ) -> R """ - var config = Configuration() + var config = Configuration.forTesting config.prioritizeKeepingFunctionOutputTogether = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 23, configuration: config) assertPrettyPrintEqual(input: input, expected: expected, linelength: 24, configuration: config) @@ -371,7 +371,7 @@ final class MacroDeclTests: PrettyPrintTestCase { ) -> R """ - var config = Configuration() + var config = Configuration.forTesting config.prioritizeKeepingFunctionOutputTogether = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 26, configuration: config) } diff --git a/Tests/SwiftFormatPrettyPrintTests/MemberAccessExprTests.swift b/Tests/SwiftFormatPrettyPrintTests/MemberAccessExprTests.swift index f1539bba2..3ba490308 100644 --- a/Tests/SwiftFormatPrettyPrintTests/MemberAccessExprTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/MemberAccessExprTests.swift @@ -118,7 +118,7 @@ final class MemberAccessExprTests: PrettyPrintTestCase { """ - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.lineBreakAroundMultilineExpressionChainComponents = true assertPrettyPrintEqual( input: input, expected: expectedWithForcedBreaks, linelength: 20, @@ -235,7 +235,7 @@ final class MemberAccessExprTests: PrettyPrintTestCase { """ - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.lineBreakAroundMultilineExpressionChainComponents = true assertPrettyPrintEqual( input: input, expected: expectedWithForcedBreaking, linelength: 35, @@ -329,7 +329,7 @@ final class MemberAccessExprTests: PrettyPrintTestCase { """ - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.lineBreakAroundMultilineExpressionChainComponents = true assertPrettyPrintEqual( input: input, expected: expectedWithForcedBreaks, linelength: 50, @@ -422,7 +422,7 @@ final class MemberAccessExprTests: PrettyPrintTestCase { """ - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.lineBreakAroundMultilineExpressionChainComponents = true assertPrettyPrintEqual( input: input, expected: expectedWithForcedBreaks, linelength: 50, @@ -511,7 +511,7 @@ final class MemberAccessExprTests: PrettyPrintTestCase { """ - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.lineBreakAroundMultilineExpressionChainComponents = true assertPrettyPrintEqual( input: input, expected: expectedWithForcedBreaks, linelength: 50, diff --git a/Tests/SwiftFormatPrettyPrintTests/ObjectLiteralExprTests.swift b/Tests/SwiftFormatPrettyPrintTests/ObjectLiteralExprTests.swift index 407fbf518..d8aab9620 100644 --- a/Tests/SwiftFormatPrettyPrintTests/ObjectLiteralExprTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/ObjectLiteralExprTests.swift @@ -27,7 +27,7 @@ final class ObjectLiteralExprTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 25, configuration: config) } @@ -53,7 +53,7 @@ final class ObjectLiteralExprTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 25, configuration: config) } @@ -80,7 +80,7 @@ final class ObjectLiteralExprTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 38, configuration: config) } @@ -106,7 +106,7 @@ final class ObjectLiteralExprTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 38, configuration: config) } diff --git a/Tests/SwiftFormatPrettyPrintTests/PrettyPrintTestCase.swift b/Tests/SwiftFormatPrettyPrintTests/PrettyPrintTestCase.swift index d8f498075..0328b6f82 100644 --- a/Tests/SwiftFormatPrettyPrintTests/PrettyPrintTestCase.swift +++ b/Tests/SwiftFormatPrettyPrintTests/PrettyPrintTestCase.swift @@ -25,7 +25,7 @@ class PrettyPrintTestCase: DiagnosingTestCase { input: String, expected: String, linelength: Int, - configuration: Configuration = Configuration(), + configuration: Configuration = Configuration.forTesting, whitespaceOnly: Bool = false, file: StaticString = #file, line: UInt = #line diff --git a/Tests/SwiftFormatPrettyPrintTests/ProtocolDeclTests.swift b/Tests/SwiftFormatPrettyPrintTests/ProtocolDeclTests.swift index f8b029cde..d18864afe 100644 --- a/Tests/SwiftFormatPrettyPrintTests/ProtocolDeclTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/ProtocolDeclTests.swift @@ -298,7 +298,7 @@ final class ProtocolDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) } @@ -339,7 +339,7 @@ final class ProtocolDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) } diff --git a/Tests/SwiftFormatPrettyPrintTests/RepeatStmtTests.swift b/Tests/SwiftFormatPrettyPrintTests/RepeatStmtTests.swift index 474083baf..d337590ff 100644 --- a/Tests/SwiftFormatPrettyPrintTests/RepeatStmtTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/RepeatStmtTests.swift @@ -107,7 +107,7 @@ final class RepeatStmtTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeControlFlowKeywords = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 25, configuration: config) } diff --git a/Tests/SwiftFormatPrettyPrintTests/RespectsExistingLineBreaksTests.swift b/Tests/SwiftFormatPrettyPrintTests/RespectsExistingLineBreaksTests.swift index 2dd5e9e1d..2bcff38f6 100644 --- a/Tests/SwiftFormatPrettyPrintTests/RespectsExistingLineBreaksTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/RespectsExistingLineBreaksTests.swift @@ -193,7 +193,7 @@ final class RespectsExistingLineBreaksTests: PrettyPrintTestCase { /// Creates a new configuration with the given value for `respectsExistingLineBreaks` and default /// values for everything else. private func configuration(respectingExistingLineBreaks: Bool) -> Configuration { - var config = Configuration() + var config = Configuration.forTesting config.respectsExistingLineBreaks = respectingExistingLineBreaks return config } diff --git a/Tests/SwiftFormatPrettyPrintTests/StructDeclTests.swift b/Tests/SwiftFormatPrettyPrintTests/StructDeclTests.swift index 900842ec3..42534dae5 100644 --- a/Tests/SwiftFormatPrettyPrintTests/StructDeclTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/StructDeclTests.swift @@ -79,7 +79,7 @@ final class StructDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) } @@ -120,7 +120,7 @@ final class StructDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) } @@ -246,7 +246,7 @@ final class StructDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 60, configuration: config) } @@ -334,7 +334,7 @@ final class StructDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 60, configuration: config) } @@ -414,7 +414,7 @@ final class StructDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -448,7 +448,7 @@ final class StructDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) diff --git a/Tests/SwiftFormatPrettyPrintTests/SubscriptDeclTests.swift b/Tests/SwiftFormatPrettyPrintTests/SubscriptDeclTests.swift index 383082e76..dd6938629 100644 --- a/Tests/SwiftFormatPrettyPrintTests/SubscriptDeclTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/SubscriptDeclTests.swift @@ -78,7 +78,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -120,7 +120,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -159,7 +159,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) } @@ -199,7 +199,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) @@ -348,7 +348,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { } """ - var config = Configuration() + var config = Configuration.forTesting config.prioritizeKeepingFunctionOutputTogether = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 26, configuration: config) @@ -472,7 +472,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 34, configuration: config) } diff --git a/Tests/SwiftFormatPrettyPrintTests/SwitchCaseIndentConfigTests.swift b/Tests/SwiftFormatPrettyPrintTests/SwitchCaseIndentConfigTests.swift index 5a1942675..3ba650afe 100644 --- a/Tests/SwiftFormatPrettyPrintTests/SwitchCaseIndentConfigTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/SwitchCaseIndentConfigTests.swift @@ -31,7 +31,7 @@ final class SwitchCaseIndentConfigTests: PrettyPrintTestCase { let expected = input - var config = Configuration() + var config = Configuration.forTesting config.indentSwitchCaseLabels = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 35, configuration: config) @@ -86,7 +86,7 @@ final class SwitchCaseIndentConfigTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.indentSwitchCaseLabels = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 35, configuration: config) @@ -141,7 +141,7 @@ final class SwitchCaseIndentConfigTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.indentSwitchCaseLabels = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 35, configuration: config) @@ -175,7 +175,7 @@ final class SwitchCaseIndentConfigTests: PrettyPrintTestCase { let expected = input - var config = Configuration() + var config = Configuration.forTesting config.indentSwitchCaseLabels = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 35, configuration: config) @@ -230,7 +230,7 @@ final class SwitchCaseIndentConfigTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.indentSwitchCaseLabels = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 35, configuration: config) @@ -285,7 +285,7 @@ final class SwitchCaseIndentConfigTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.indentSwitchCaseLabels = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 35, configuration: config) @@ -340,7 +340,7 @@ final class SwitchCaseIndentConfigTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.indentSwitchCaseLabels = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 35, configuration: config) @@ -371,7 +371,7 @@ final class SwitchCaseIndentConfigTests: PrettyPrintTestCase { let expected = input - var config = Configuration() + var config = Configuration.forTesting config.indentSwitchCaseLabels = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) diff --git a/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift b/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift index c2b744a1c..15bb33798 100644 --- a/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift @@ -552,7 +552,7 @@ final class SwitchStmtTests: PrettyPrintTestCase { """ - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.indentSwitchCaseLabels = true assertPrettyPrintEqual( input: input, expected: expected, linelength: 40, configuration: configuration) diff --git a/Tests/SwiftFormatPrettyPrintTests/TryCatchTests.swift b/Tests/SwiftFormatPrettyPrintTests/TryCatchTests.swift index 152cff650..6c3acaeb8 100644 --- a/Tests/SwiftFormatPrettyPrintTests/TryCatchTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/TryCatchTests.swift @@ -105,7 +105,7 @@ final class TryCatchTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeControlFlowKeywords = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: config) } @@ -156,7 +156,7 @@ final class TryCatchTests: PrettyPrintTestCase { """ - var config = Configuration() + var config = Configuration.forTesting config.lineBreakBeforeControlFlowKeywords = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 42, configuration: config) } diff --git a/Tests/SwiftFormatRulesTests/FileScopedDeclarationPrivacyTests.swift b/Tests/SwiftFormatRulesTests/FileScopedDeclarationPrivacyTests.swift index 8baaebecc..6e30a4dd9 100644 --- a/Tests/SwiftFormatRulesTests/FileScopedDeclarationPrivacyTests.swift +++ b/Tests/SwiftFormatRulesTests/FileScopedDeclarationPrivacyTests.swift @@ -203,7 +203,7 @@ final class FileScopedDeclarationPrivacyTests: LintOrFormatRuleTestCase { completion: ((Int, Int) -> Void) -> Void ) { for testConfig in testConfigurations { - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.fileScopedDeclarationPrivacy.accessLevel = testConfig.desired let substitutedInput = source.replacingOccurrences(of: "$access$", with: testConfig.original) diff --git a/Tests/SwiftFormatRulesTests/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatRulesTests/LintOrFormatRuleTestCase.swift index 66ae212ce..81e3d8167 100644 --- a/Tests/SwiftFormatRulesTests/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatRulesTests/LintOrFormatRuleTestCase.swift @@ -25,7 +25,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { .as(SourceFileSyntax.self)!) // Force the rule to be enabled while we test it. - var configuration = Configuration() + var configuration = Configuration.forTesting configuration.rules[type.ruleName] = true let context = makeContext(sourceFileSyntax: sourceFileSyntax, configuration: configuration) @@ -64,7 +64,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { .as(SourceFileSyntax.self)!) // Force the rule to be enabled while we test it. - var configuration = configuration ?? Configuration() + var configuration = configuration ?? Configuration.forTesting configuration.rules[formatType.ruleName] = true let context = makeContext(sourceFileSyntax: sourceFileSyntax, configuration: configuration) diff --git a/Tests/SwiftFormatWhitespaceLinterTests/WhitespaceTestCase.swift b/Tests/SwiftFormatWhitespaceLinterTests/WhitespaceTestCase.swift index 07b64cfb4..899eade3e 100644 --- a/Tests/SwiftFormatWhitespaceLinterTests/WhitespaceTestCase.swift +++ b/Tests/SwiftFormatWhitespaceLinterTests/WhitespaceTestCase.swift @@ -21,7 +21,7 @@ class WhitespaceTestCase: DiagnosingTestCase { /// - linelength: The maximum allowed line length of the output. final func performWhitespaceLint(input: String, expected: String, linelength: Int? = nil) { let sourceFileSyntax = Parser.parse(source: input) - var configuration = Configuration() + var configuration = Configuration.forTesting if let linelength = linelength { configuration.lineLength = linelength } From 9cc4d3c8b32c5972219ff11b1c4e32d12870511a Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 7 Aug 2023 08:54:12 -0400 Subject: [PATCH 077/332] Don't alter doc line comments unnecessarily. Since we introduced the new logic to parse doc comments, the `UseTripleSlashForDocumentationComments` rule started inadvertently normalizing comments that were already doc line comments. For example, ```swift /// /// Foo /// ``` would have its initial blank line(s) and leading spaces removed, leaving this: ```swift /// Foo /// ``` While we may want to be opinionated at some point about how such comments are formatted, it's not a desired consequence of *this* rule; the name on the tin doesn't say anything about altering comments that already meet the requirement, so now we make sure to leave them alone if they're already doc line comments. --- .../DocumentationComment.swift | 2 +- .../DocumentationCommentText.swift | 239 ++++++++++-------- ...lPublicDeclarationsHaveDocumentation.swift | 2 +- ...eTripleSlashForDocumentationComments.swift | 12 +- .../DocumentationCommentTextTests.swift | 48 +++- ...leSlashForDocumentationCommentsTests.swift | 23 ++ 6 files changed, 212 insertions(+), 114 deletions(-) diff --git a/Sources/SwiftFormatCore/DocumentationComment.swift b/Sources/SwiftFormatCore/DocumentationComment.swift index e6651f9bb..f4f13f3f3 100644 --- a/Sources/SwiftFormatCore/DocumentationComment.swift +++ b/Sources/SwiftFormatCore/DocumentationComment.swift @@ -82,7 +82,7 @@ public struct DocumentationComment { /// /// - Parameter node: The syntax node from which the documentation comment should be extracted. public init?(extractedFrom node: Node) { - guard let commentInfo = documentationCommentText(extractedFrom: node.leadingTrivia) else { + guard let commentInfo = DocumentationCommentText(extractedFrom: node.leadingTrivia) else { return nil } diff --git a/Sources/SwiftFormatCore/DocumentationCommentText.swift b/Sources/SwiftFormatCore/DocumentationCommentText.swift index 0bb67c54f..ca69dc416 100644 --- a/Sources/SwiftFormatCore/DocumentationCommentText.swift +++ b/Sources/SwiftFormatCore/DocumentationCommentText.swift @@ -12,121 +12,160 @@ import SwiftSyntax -/// Extracts and returns the body text of a documentation comment represented as a trivia -/// collection. +/// The text contents of a documentation comment extracted from trivia. /// -/// This function should be used when only the text of the comment is important, not the structural -/// organization. It automatically handles trimming leading indentation from comments as well as -/// "ASCII art" in block comments (i.e., leading asterisks on each line). -/// -/// This implementation is based on -/// https://github.com/apple/swift/blob/main/lib/Markup/LineList.cpp. -/// -/// - Parameter trivia: The trivia collection from which to extract the comment text. -/// - Returns: If a comment was found, a tuple containing the `String` containing the extracted text -/// and the index into the trivia collection where the comment began is returned. Otherwise, `nil` -/// is returned. -public func documentationCommentText(extractedFrom trivia: Trivia) - -> (text: String, startIndex: Trivia.Index)? -{ - /// Represents a line of text and its leading indentation. - struct Line { - var text: Substring - var firstNonspaceDistance: Int - - init(_ text: Substring) { - self.text = text - self.firstNonspaceDistance = indentationDistance(of: text) - } +/// This type should be used when only the text of the comment is important, not the Markdown +/// structural organization. It automatically handles trimming leading indentation from comments as +/// well as "ASCII art" in block comments (i.e., leading asterisks on each line). +public struct DocumentationCommentText { + /// Denotes the kind of punctuation used to introduce the comment. + public enum Introducer { + /// The comment was introduced entirely by line-style comments (`///`). + case line + + /// The comment was introduced entirely by block-style comments (`/** ... */`). + case block + + /// The comment was introduced by a mixture of line-style and block-style comments. + case mixed } - // Look backwards from the end of the trivia collection to find the logical start of the comment. - // We have to copy it into an array since `Trivia` doesn't support bidirectional indexing. - let triviaArray = Array(trivia) - let commentStartIndex: Array.Index - if - let lastNonDocCommentIndex = triviaArray.lastIndex(where: { - switch $0 { - case .docBlockComment, .docLineComment, - .newlines(1), .carriageReturns(1), .carriageReturnLineFeeds(1), - .spaces, .tabs: - return false - default: - return true + /// The comment text extracted from the trivia. + public let text: String + + /// The index in the trivia collection passed to the initializer where the comment started. + public let startIndex: Trivia.Index + + /// The kind of punctuation used to introduce the comment. + public let introducer: Introducer + + /// Extracts and returns the body text of a documentation comment represented as a trivia + /// collection. + /// + /// This implementation is based on + /// https://github.com/apple/swift/blob/main/lib/Markup/LineList.cpp. + /// + /// - Parameter trivia: The trivia collection from which to extract the comment text. + /// - Returns: If a comment was found, a tuple containing the `String` containing the extracted + /// text and the index into the trivia collection where the comment began is returned. + /// Otherwise, `nil` is returned. + public init?(extractedFrom trivia: Trivia) { + /// Represents a line of text and its leading indentation. + struct Line { + var text: Substring + var firstNonspaceDistance: Int + + init(_ text: Substring) { + self.text = text + self.firstNonspaceDistance = indentationDistance(of: text) } - }), - lastNonDocCommentIndex != trivia.endIndex - { - commentStartIndex = triviaArray.index(after: lastNonDocCommentIndex) - } else { - commentStartIndex = triviaArray.startIndex - } + } - // Determine the indentation level of the first line of the comment. This is used to adjust - // block comments, whose text spans multiple lines. - let leadingWhitespace = contiguousWhitespace(in: triviaArray, before: commentStartIndex) - var lines = [Line]() - - // Extract the raw lines of text (which will include their leading comment punctuation, which is - // stripped). - for triviaPiece in trivia[commentStartIndex...] { - switch triviaPiece { - case .docLineComment(let line): - lines.append(Line(line.dropFirst(3))) - - case .docBlockComment(let line): - var cleaned = line.dropFirst(3) - if cleaned.hasSuffix("*/") { - cleaned = cleaned.dropLast(2) - } - - var hasASCIIArt = false - if cleaned.hasPrefix("\n") { - cleaned = cleaned.dropFirst() - hasASCIIArt = asciiArtLength(of: cleaned, leadingSpaces: leadingWhitespace) != 0 + // Look backwards from the end of the trivia collection to find the logical start of the + // comment. We have to copy it into an array since `Trivia` doesn't support bidirectional + // indexing. + let triviaArray = Array(trivia) + let commentStartIndex: Array.Index + if + let lastNonDocCommentIndex = triviaArray.lastIndex(where: { + switch $0 { + case .docBlockComment, .docLineComment, + .newlines(1), .carriageReturns(1), .carriageReturnLineFeeds(1), + .spaces, .tabs: + return false + default: + return true + } + }), + lastNonDocCommentIndex != trivia.endIndex + { + commentStartIndex = triviaArray.index(after: lastNonDocCommentIndex) + } else { + commentStartIndex = triviaArray.startIndex + } + + // Determine the indentation level of the first line of the comment. This is used to adjust + // block comments, whose text spans multiple lines. + let leadingWhitespace = contiguousWhitespace(in: triviaArray, before: commentStartIndex) + var lines = [Line]() + + var introducer: Introducer? + func updateIntroducer(_ newIntroducer: Introducer) { + if let knownIntroducer = introducer, knownIntroducer != newIntroducer { + introducer = .mixed + } else { + introducer = newIntroducer } - - while !cleaned.isEmpty { - var index = cleaned.firstIndex(where: \.isNewline) ?? cleaned.endIndex - if hasASCIIArt { - cleaned = cleaned.dropFirst(asciiArtLength(of: cleaned, leadingSpaces: leadingWhitespace)) - index = cleaned.firstIndex(where: \.isNewline) ?? cleaned.endIndex + } + + // Extract the raw lines of text (which will include their leading comment punctuation, which is + // stripped). + for triviaPiece in trivia[commentStartIndex...] { + switch triviaPiece { + case .docLineComment(let line): + updateIntroducer(.line) + lines.append(Line(line.dropFirst(3))) + + case .docBlockComment(let line): + updateIntroducer(.block) + + var cleaned = line.dropFirst(3) + if cleaned.hasSuffix("*/") { + cleaned = cleaned.dropLast(2) } - // Don't add an unnecessary blank line at the end when `*/` is on its own line. - guard cleaned.firstIndex(where: { !$0.isWhitespace }) != nil else { - break + var hasASCIIArt = false + if cleaned.hasPrefix("\n") { + cleaned = cleaned.dropFirst() + hasASCIIArt = asciiArtLength(of: cleaned, leadingSpaces: leadingWhitespace) != 0 } - let line = cleaned.prefix(upTo: index) - lines.append(Line(line)) - cleaned = cleaned[index...].dropFirst() - } + while !cleaned.isEmpty { + var index = cleaned.firstIndex(where: \.isNewline) ?? cleaned.endIndex + if hasASCIIArt { + cleaned = + cleaned.dropFirst(asciiArtLength(of: cleaned, leadingSpaces: leadingWhitespace)) + index = cleaned.firstIndex(where: \.isNewline) ?? cleaned.endIndex + } + + // Don't add an unnecessary blank line at the end when `*/` is on its own line. + guard cleaned.firstIndex(where: { !$0.isWhitespace }) != nil else { + break + } + + let line = cleaned.prefix(upTo: index) + lines.append(Line(line)) + cleaned = cleaned[index...].dropFirst() + } - default: - break + default: + break + } } - } - // Concatenate the lines into a single string, trimming any leading indentation that might be - // present. - guard - !lines.isEmpty, - let firstLineIndex = lines.firstIndex(where: { !$0.text.isEmpty }) - else { return nil } - - let initialIndentation = indentationDistance(of: lines[firstLineIndex].text) - var result = "" - for line in lines[firstLineIndex...] { - let countToDrop = min(initialIndentation, line.firstNonspaceDistance) - result.append(contentsOf: "\(line.text.dropFirst(countToDrop))\n") - } + // Concatenate the lines into a single string, trimming any leading indentation that might be + // present. + guard + let introducer = introducer, + !lines.isEmpty, + let firstLineIndex = lines.firstIndex(where: { !$0.text.isEmpty }) + else { return nil } + + let initialIndentation = indentationDistance(of: lines[firstLineIndex].text) + var result = "" + for line in lines[firstLineIndex...] { + let countToDrop = min(initialIndentation, line.firstNonspaceDistance) + result.append(contentsOf: "\(line.text.dropFirst(countToDrop))\n") + } - guard !result.isEmpty else { return nil } + guard !result.isEmpty else { return nil } - let commentStartDistance = - triviaArray.distance(from: triviaArray.startIndex, to: commentStartIndex) - return (text: result, startIndex: trivia.index(trivia.startIndex, offsetBy: commentStartDistance)) + let commentStartDistance = + triviaArray.distance(from: triviaArray.startIndex, to: commentStartIndex) + self.text = result + self.startIndex = trivia.index(trivia.startIndex, offsetBy: commentStartDistance) + self.introducer = introducer + } } /// Returns the distance from the start of the string to the first non-whitespace character. diff --git a/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift b/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift index 386b4624c..52e8ec79f 100644 --- a/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift +++ b/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift @@ -76,7 +76,7 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { modifiers: DeclModifierListSyntax? ) { guard - documentationCommentText(extractedFrom: decl.leadingTrivia) == nil, + DocumentationCommentText(extractedFrom: decl.leadingTrivia) == nil, let mods = modifiers, mods.has(modifier: "public") && !mods.has(modifier: "override") else { return diff --git a/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift b/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift index fc8566d8a..48335ffc8 100644 --- a/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift +++ b/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift @@ -68,11 +68,15 @@ public final class UseTripleSlashForDocumentationComments: SyntaxFormatRule { return convertDocBlockCommentToDocLineComment(DeclSyntax(node)) } - /// In the case the given declaration has a docBlockComment as it's documentation - /// comment. Returns the declaration with the docBlockComment converted to - /// a docLineComment. + /// If the declaration has a doc block comment, return the declaration with the comment rewritten + /// as a line comment. + /// + /// If the declaration had no comment or had only line comments, it is returned unchanged. private func convertDocBlockCommentToDocLineComment(_ decl: DeclSyntax) -> DeclSyntax { - guard let commentInfo = documentationCommentText(extractedFrom: decl.leadingTrivia) else { + guard + let commentInfo = DocumentationCommentText(extractedFrom: decl.leadingTrivia), + commentInfo.introducer != .line + else { return decl } diff --git a/Tests/SwiftFormatCoreTests/DocumentationCommentTextTests.swift b/Tests/SwiftFormatCoreTests/DocumentationCommentTextTests.swift index 136b447aa..5fb0f24eb 100644 --- a/Tests/SwiftFormatCoreTests/DocumentationCommentTextTests.swift +++ b/Tests/SwiftFormatCoreTests/DocumentationCommentTextTests.swift @@ -9,8 +9,10 @@ final class DocumentationCommentTextTests: XCTestCase { /// A simple doc comment. func f() {} """ + let commentText = try XCTUnwrap(DocumentationCommentText(extractedFrom: decl.leadingTrivia)) + XCTAssertEqual(commentText.introducer, .line) XCTAssertEqual( - documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + commentText.text, """ A simple doc comment. @@ -23,8 +25,10 @@ final class DocumentationCommentTextTests: XCTestCase { /** A simple doc comment. */ func f() {} """ + let commentText = try XCTUnwrap(DocumentationCommentText(extractedFrom: decl.leadingTrivia)) + XCTAssertEqual(commentText.introducer, .block) XCTAssertEqual( - documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + commentText.text, """ A simple doc comment.\u{0020} @@ -39,8 +43,10 @@ final class DocumentationCommentTextTests: XCTestCase { */ func f() {} """ + let commentText = try XCTUnwrap(DocumentationCommentText(extractedFrom: decl.leadingTrivia)) + XCTAssertEqual(commentText.introducer, .block) XCTAssertEqual( - documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + commentText.text, """ A simple doc comment. @@ -55,8 +61,10 @@ final class DocumentationCommentTextTests: XCTestCase { */ func f() {} """ + let commentText = try XCTUnwrap(DocumentationCommentText(extractedFrom: decl.leadingTrivia)) + XCTAssertEqual(commentText.introducer, .block) XCTAssertEqual( - documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + commentText.text, """ A simple doc comment. @@ -75,8 +83,10 @@ final class DocumentationCommentTextTests: XCTestCase { /// - Returns: A value. func f(x: Int) -> Int {} """ + let commentText = try XCTUnwrap(DocumentationCommentText(extractedFrom: decl.leadingTrivia)) + XCTAssertEqual(commentText.introducer, .line) XCTAssertEqual( - documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + commentText.text, """ A doc comment. @@ -97,8 +107,10 @@ final class DocumentationCommentTextTests: XCTestCase { /// A doc comment. func f(x: Int) -> Int {} """ + let commentText = try XCTUnwrap(DocumentationCommentText(extractedFrom: decl.leadingTrivia)) + XCTAssertEqual(commentText.introducer, .line) XCTAssertEqual( - documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + commentText.text, """ A doc comment. @@ -116,8 +128,10 @@ final class DocumentationCommentTextTests: XCTestCase { /** so is this */ func f(x: Int) -> Int {} """ + let commentText = try XCTUnwrap(DocumentationCommentText(extractedFrom: decl.leadingTrivia)) + XCTAssertEqual(commentText.introducer, .block) XCTAssertEqual( - documentationCommentText(extractedFrom: decl.leadingTrivia)?.text, + commentText.text, """ This is part of the comment. so is this\u{0020} @@ -126,10 +140,28 @@ final class DocumentationCommentTextTests: XCTestCase { ) } + func testDocCommentHasMixedIntroducers() throws { + let decl: DeclSyntax = """ + /// This is part of the comment. + /** This is too. */ + func f(x: Int) -> Int {} + """ + let commentText = try XCTUnwrap(DocumentationCommentText(extractedFrom: decl.leadingTrivia)) + XCTAssertEqual(commentText.introducer, .mixed) + XCTAssertEqual( + commentText.text, + """ + This is part of the comment. + This is too.\u{0020} + + """ + ) + } + func testNilIfNoComment() throws { let decl: DeclSyntax = """ func f(x: Int) -> Int {} """ - XCTAssertNil(documentationCommentText(extractedFrom: decl.leadingTrivia)) + XCTAssertNil(DocumentationCommentText(extractedFrom: decl.leadingTrivia)) } } diff --git a/Tests/SwiftFormatRulesTests/UseTripleSlashForDocumentationCommentsTests.swift b/Tests/SwiftFormatRulesTests/UseTripleSlashForDocumentationCommentsTests.swift index f0626275a..b7e4894c4 100644 --- a/Tests/SwiftFormatRulesTests/UseTripleSlashForDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatRulesTests/UseTripleSlashForDocumentationCommentsTests.swift @@ -148,4 +148,27 @@ final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCas } """) } + + func testDocLineCommentsAreNotNormalized() { + XCTAssertFormatting( + UseTripleSlashForDocumentationComments.self, + input: """ + /// + /// Normally that initial blank line and these leading spaces + /// would be removed by DocumentationCommentText. But we don't + /// touch the comment if it's already a doc line comment. + /// + public class AClazz { + } + """, + expected: """ + /// + /// Normally that initial blank line and these leading spaces + /// would be removed by DocumentationCommentText. But we don't + /// touch the comment if it's already a doc line comment. + /// + public class AClazz { + } + """) + } } From 18f7dc4711a9925eaa012b4bf837196dd1343cbd Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 9 Aug 2023 09:54:31 -0400 Subject: [PATCH 078/332] Improve a bunch of diagnostic messages. In general, this PR attempts to make messages clearer, easier to read, and less terse. I've also tried to unify punctuation surrounding identifiers and other syntactic constructs mentioned in messages to use single quotes (instead of backticks or nothing). --- .../AlwaysUseLowerCamelCase.swift | 2 +- .../AmbiguousTrailingClosureOverload.swift | 2 +- .../DontRepeatTypeInStaticProperties.swift | 2 +- .../SwiftFormatRules/FullyIndirectEnum.swift | 2 +- ...NeverUseImplicitlyUnwrappedOptionals.swift | 2 +- .../NoAccessLevelOnExtensionDeclaration.swift | 4 +- .../NoAssignmentInExpressions.swift | 2 +- .../SwiftFormatRules/NoBlockComments.swift | 2 +- .../NoCasesWithOnlyFallthrough.swift | 6 +- .../NoEmptyTrailingClosureParentheses.swift | 2 +- .../NoLabelsInCasePatterns.swift | 2 +- .../NoLeadingUnderscores.swift | 2 +- .../NoParensAroundConditions.swift | 2 +- .../NoVoidReturnOnFunctionSignature.swift | 2 +- Sources/SwiftFormatRules/OneCasePerLine.swift | 2 +- .../OneVariableDeclarationPerLine.swift | 7 ++- .../OnlyOneTrailingClosureArgument.swift | 2 +- Sources/SwiftFormatRules/OrderedImports.swift | 2 +- Sources/SwiftFormatRules/UseEarlyExits.swift | 2 +- .../UseLetInEveryBoundCaseVariable.swift | 2 +- .../UseShorthandTypeNames.swift | 2 +- .../UseSingleLinePropertyGetter.swift | 2 +- .../UseSynthesizedInitializer.swift | 2 +- .../ValidateDocumentationComments.swift | 16 ++--- .../NoCasesWithOnlyFallthroughTests.swift | 62 +++++++++---------- .../OneVariableDeclarationPerLineTests.swift | 40 ++++++------ 26 files changed, 88 insertions(+), 87 deletions(-) diff --git a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift index 0b06157e7..bfa6e7a8d 100644 --- a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift @@ -217,6 +217,6 @@ extension Finding.Message { public static func nameMustBeLowerCamelCase( _ name: String, description: String ) -> Finding.Message { - "rename \(description) '\(name)' using lower-camel-case" + "rename the \(description) '\(name)' using lowerCamelCase" } } diff --git a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift index 66444ca33..809853223 100644 --- a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift +++ b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift @@ -74,7 +74,7 @@ public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { extension Finding.Message { public static func ambiguousTrailingClosureOverload(_ decl: String) -> Finding.Message { - "rename '\(decl)' so it is no longer ambiguous with a trailing closure" + "rename '\(decl)' so it is no longer ambiguous when called with a trailing closure" } public static func otherAmbiguousOverloadHere(_ decl: String) -> Finding.Message { diff --git a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift index 1c9b9e1f1..3f2c68b7a 100644 --- a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift @@ -104,6 +104,6 @@ public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { extension Finding.Message { public static func removeTypeFromName(name: String, type: Substring) -> Finding.Message { - "remove '\(type)' from '\(name)'" + "remove the suffix '\(type)' from the name of the variable '\(name)'" } } diff --git a/Sources/SwiftFormatRules/FullyIndirectEnum.swift b/Sources/SwiftFormatRules/FullyIndirectEnum.swift index 60ce164e2..0a91c1fad 100644 --- a/Sources/SwiftFormatRules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormatRules/FullyIndirectEnum.swift @@ -109,6 +109,6 @@ public final class FullyIndirectEnum: SyntaxFormatRule { extension Finding.Message { public static func moveIndirectKeywordToEnumDecl(name: String) -> Finding.Message { - "move 'indirect' to \(name) enum declaration when all cases are indirect" + "move 'indirect' before the enum declaration '\(name)' when all cases are indirect" } } diff --git a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift index 13e504404..d9e7a0cce 100644 --- a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -65,6 +65,6 @@ public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule { extension Finding.Message { public static func doNotUseImplicitUnwrapping(identifier: String) -> Finding.Message { - "use \(identifier) or \(identifier)? instead of \(identifier)!" + "use '\(identifier)' or '\(identifier)?' instead of '\(identifier)!'" } } diff --git a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift index 71ed1f50e..912ccbcdc 100644 --- a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift @@ -102,10 +102,10 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { extension Finding.Message { public static func removeRedundantAccessKeyword(name: String) -> Finding.Message { - "remove redundant 'internal' access keyword from \(name)" + "remove redundant 'internal' access keyword from '\(name)'" } public static func moveAccessKeyword(keyword: String) -> Finding.Message { - "specify \(keyword) access level for each member inside the extension" + "move the '\(keyword)' access keyword to precede each member inside the extension" } } diff --git a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift index a478c04f2..7420ade74 100644 --- a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift @@ -163,5 +163,5 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { extension Finding.Message { public static let moveAssignmentToOwnStatement: Finding.Message = - "move assignment expression into its own statement" + "move this assignment expression into its own statement" } diff --git a/Sources/SwiftFormatRules/NoBlockComments.swift b/Sources/SwiftFormatRules/NoBlockComments.swift index 3b9f79310..5c07983d5 100644 --- a/Sources/SwiftFormatRules/NoBlockComments.swift +++ b/Sources/SwiftFormatRules/NoBlockComments.swift @@ -30,5 +30,5 @@ public final class NoBlockComments: SyntaxLintRule { extension Finding.Message { public static let avoidBlockComment: Finding.Message = - "replace block comment with line comments" + "replace this block comment with line comments" } diff --git a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift b/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift index 6ad5e9bed..4a76ca618 100644 --- a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift +++ b/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift @@ -183,7 +183,7 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { // Diagnose the cases being collapsed. We do this for all but the last one in the array; the // last one isn't diagnosed because it will contain the body that applies to all the previous // cases. - diagnose(.collapseCase(name: label.caseItems.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description), on: label) + diagnose(.collapseCase, on: label) } newCaseItems.append(contentsOf: labels.last!.caseItems) @@ -219,7 +219,7 @@ extension TriviaPiece { } extension Finding.Message { - public static func collapseCase(name: String) -> Finding.Message { - "combine fallthrough-only case \(name) with a following case" + public static var collapseCase: Finding.Message { + "combine this fallthrough-only 'case' and the following 'case' into a single 'case'" } } diff --git a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift index 6b7602f8a..5466934a8 100644 --- a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift +++ b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift @@ -53,6 +53,6 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { extension Finding.Message { public static func removeEmptyTrailingParentheses(name: String) -> Finding.Message { - "remove '()' after \(name)" + "remove the empty parentheses following '\(name)'" } } diff --git a/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift b/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift index 2d09524e3..53228899f 100644 --- a/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift +++ b/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift @@ -72,6 +72,6 @@ public final class NoLabelsInCasePatterns: SyntaxFormatRule { extension Finding.Message { public static func removeRedundantLabel(name: String) -> Finding.Message { - "remove \(name) label from case argument" + "remove the label '\(name)' from this 'case' pattern" } } diff --git a/Sources/SwiftFormatRules/NoLeadingUnderscores.swift b/Sources/SwiftFormatRules/NoLeadingUnderscores.swift index e8a8da0b0..f760a5bbc 100644 --- a/Sources/SwiftFormatRules/NoLeadingUnderscores.swift +++ b/Sources/SwiftFormatRules/NoLeadingUnderscores.swift @@ -126,6 +126,6 @@ public final class NoLeadingUnderscores: SyntaxLintRule { extension Finding.Message { public static func doNotStartWithUnderscore(identifier: String) -> Finding.Message { - "remove leading '_' from identifier '\(identifier)'" + "remove the leading '_' from the name '\(identifier)'" } } diff --git a/Sources/SwiftFormatRules/NoParensAroundConditions.swift b/Sources/SwiftFormatRules/NoParensAroundConditions.swift index facc00671..a6cadf147 100644 --- a/Sources/SwiftFormatRules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormatRules/NoParensAroundConditions.swift @@ -104,5 +104,5 @@ public final class NoParensAroundConditions: SyntaxFormatRule { extension Finding.Message { public static let removeParensAroundExpression: Finding.Message = - "remove parentheses around this expression" + "remove the parentheses around this expression" } diff --git a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift b/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift index 27636c712..f0693ceb7 100644 --- a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift +++ b/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift @@ -38,6 +38,6 @@ public final class NoVoidReturnOnFunctionSignature: SyntaxFormatRule { extension Finding.Message { public static func removeRedundantReturn(_ type: String) -> Finding.Message { - "remove explicit '\(type)' return type" + "remove the explicit return type '\(type)' from this function" } } diff --git a/Sources/SwiftFormatRules/OneCasePerLine.swift b/Sources/SwiftFormatRules/OneCasePerLine.swift index f1a4226fb..29dc537a7 100644 --- a/Sources/SwiftFormatRules/OneCasePerLine.swift +++ b/Sources/SwiftFormatRules/OneCasePerLine.swift @@ -130,6 +130,6 @@ public final class OneCasePerLine: SyntaxFormatRule { extension Finding.Message { public static func moveAssociatedOrRawValueCase(name: String) -> Finding.Message { - "move '\(name)' to its own case declaration" + "move '\(name)' to its own 'case' declaration" } } diff --git a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift b/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift index ff35a1ed1..e9c62b433 100644 --- a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift +++ b/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift @@ -41,7 +41,7 @@ public final class OneVariableDeclarationPerLine: SyntaxFormatRule { continue } - diagnose(.onlyOneVariableDeclaration, on: varDecl) + diagnose(.onlyOneVariableDeclaration(specifier: varDecl.bindingSpecifier.text), on: varDecl) // Visit the decl recursively to make sure nested code block items in the // bindings (for example, an initializer expression that contains a @@ -74,8 +74,9 @@ public final class OneVariableDeclarationPerLine: SyntaxFormatRule { } extension Finding.Message { - public static let onlyOneVariableDeclaration: Finding.Message = - "split this variable declaration to have one variable per declaration" + public static func onlyOneVariableDeclaration(specifier: String) -> Finding.Message { + "split this variable declaration to introduce only one variable per '\(specifier)'" + } } /// Splits a variable declaration with multiple bindings into individual diff --git a/Sources/SwiftFormatRules/OnlyOneTrailingClosureArgument.swift b/Sources/SwiftFormatRules/OnlyOneTrailingClosureArgument.swift index cf8b934cf..984429270 100644 --- a/Sources/SwiftFormatRules/OnlyOneTrailingClosureArgument.swift +++ b/Sources/SwiftFormatRules/OnlyOneTrailingClosureArgument.swift @@ -31,5 +31,5 @@ public final class OnlyOneTrailingClosureArgument: SyntaxLintRule { extension Finding.Message { public static let removeTrailingClosure: Finding.Message = - "revise function call to avoid using both closure arguments and a trailing closure" + "revise this function call to avoid using both closure arguments and a trailing closure" } diff --git a/Sources/SwiftFormatRules/OrderedImports.swift b/Sources/SwiftFormatRules/OrderedImports.swift index 8fd189486..06df09977 100644 --- a/Sources/SwiftFormatRules/OrderedImports.swift +++ b/Sources/SwiftFormatRules/OrderedImports.swift @@ -579,7 +579,7 @@ extension Finding.Message { "place \(before) imports before \(after) imports" } - public static let removeDuplicateImport: Finding.Message = "remove duplicate import" + public static let removeDuplicateImport: Finding.Message = "remove this duplicate import" public static let sortImports: Finding.Message = "sort import statements lexicographically" } diff --git a/Sources/SwiftFormatRules/UseEarlyExits.swift b/Sources/SwiftFormatRules/UseEarlyExits.swift index 49b7f6cee..c7fe11b99 100644 --- a/Sources/SwiftFormatRules/UseEarlyExits.swift +++ b/Sources/SwiftFormatRules/UseEarlyExits.swift @@ -108,5 +108,5 @@ public final class UseEarlyExits: SyntaxFormatRule { extension Finding.Message { public static let useGuardStatement: Finding.Message = - "replace the `if/else` block with a `guard` statement containing the early exit" + "replace the 'if/else' block with a 'guard' statement containing the early exit" } diff --git a/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift b/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift index 577df511c..745ea0b1a 100644 --- a/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift +++ b/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift @@ -67,5 +67,5 @@ public final class UseLetInEveryBoundCaseVariable: SyntaxLintRule { extension Finding.Message { public static let useLetInBoundCaseVariables: Finding.Message = - "move 'let' keyword to precede each variable bound in the `case` pattern" + "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" } diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index f535f70cb..c5f4d39af 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -564,6 +564,6 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { extension Finding.Message { public static func useTypeShorthand(type: String) -> Finding.Message { - "use \(type) type shorthand form" + "use shorthand syntax for this '\(type)' type" } } diff --git a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift index e2cb7431f..a40029627 100644 --- a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift @@ -43,5 +43,5 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { extension Finding.Message { public static let removeExtraneousGetBlock: Finding.Message = - "remove extraneous 'get {}' block" + "remove 'get {...}' around the accessor and move its body directly into the computed property" } diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift index 5d3509ba6..3f70f363e 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift @@ -186,7 +186,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { extension Finding.Message { public static let removeRedundantInitializer: Finding.Message = - "remove initializer and use the synthesized initializer" + "remove this explicit initializer, which is identical to the compiler-synthesized initializer" } /// Defines the access levels which may be assigned to a synthesized memberwise initializer. diff --git a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift index 090d000e8..4a7db1ddc 100644 --- a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormatRules/ValidateDocumentationComments.swift @@ -168,31 +168,31 @@ fileprivate func parametersAreEqual( extension Finding.Message { public static func documentReturnValue(funcName: String) -> Finding.Message { - "document the return value of \(funcName)" + "add a 'Returns:' section to document the return value of '\(funcName)'" } public static func removeReturnComment(funcName: String) -> Finding.Message { - "remove the return comment of \(funcName), it doesn't return a value" + "remove the 'Returns:' section of '\(funcName)'; it does not return a value" } public static func parametersDontMatch(funcName: String) -> Finding.Message { - "change the parameters of \(funcName)'s documentation to match its parameters" + "change the parameters of the documentation of '\(funcName)' to match its parameters" } public static let useSingularParameter: Finding.Message = - "replace the plural form of 'Parameters' with a singular inline form of the 'Parameter' tag" + "replace the plural 'Parameters:' section with a singular inline 'Parameter' section" public static let usePluralParameters: Finding.Message = """ - replace the singular inline form of 'Parameter' tag with a plural 'Parameters' tag \ - and group each parameter as a nested list + replace the singular inline 'Parameter' section with a plural 'Parameters:' section \ + that has the parameters nested inside it """ public static func removeThrowsComment(funcName: String) -> Finding.Message { - "remove the 'Throws' tag for non-throwing function \(funcName)" + "remove the 'Throws:' sections of '\(funcName)'; it does not throw any errors" } public static func documentErrorsThrown(funcName: String) -> Finding.Message { - "add a 'Throws' tag describing the errors thrown by \(funcName)" + "add a 'Throws:' section to document the errors thrown by '\(funcName)'" } } diff --git a/Tests/SwiftFormatRulesTests/NoCasesWithOnlyFallthroughTests.swift b/Tests/SwiftFormatRulesTests/NoCasesWithOnlyFallthroughTests.swift index 0d8b18448..eb9653968 100644 --- a/Tests/SwiftFormatRulesTests/NoCasesWithOnlyFallthroughTests.swift +++ b/Tests/SwiftFormatRulesTests/NoCasesWithOnlyFallthroughTests.swift @@ -56,14 +56,14 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true) - XCTAssertDiagnosed(.collapseCase(name: "2"), line: 3, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "3"), line: 4, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "5"), line: 6, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "\"a\""), line: 11, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "\"b\", \"c\""), line: 12, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "\"f\""), line: 15, column: 1) - XCTAssertDiagnosed(.collapseCase(name: ".rightBrace"), line: 21, column: 1) - XCTAssertDiagnosed(.collapseCase(name: ".leftBrace"), line: 22, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 3, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 4, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 6, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 11, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 12, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 15, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 21, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 22, column: 1) } func testFallthroughCasesWithCommentsAreNotCombined() { @@ -104,8 +104,8 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true) - XCTAssertDiagnosed(.collapseCase(name: "2"), line: 4, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "6"), line: 12, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 4, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 12, column: 1) } func testCommentsAroundCombinedCasesStayInPlace() { @@ -137,8 +137,8 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true) - XCTAssertDiagnosed(.collapseCase(name: "6"), line: 4, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "8"), line: 7, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 4, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 7, column: 1) } func testNestedSwitches() { @@ -174,11 +174,11 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true) - XCTAssertDiagnosed(.collapseCase(name: "1"), line: 2, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "2"), line: 3, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 2, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 3, column: 1) // TODO: Column 9 seems wrong here; it should be 3. Look into this. - XCTAssertDiagnosed(.collapseCase(name: "1"), line: 6, column: 9) - XCTAssertDiagnosed(.collapseCase(name: "1"), line: 11, column: 3) + XCTAssertDiagnosed(.collapseCase, line: 6, column: 9) + XCTAssertDiagnosed(.collapseCase, line: 11, column: 3) } func testCasesInsideConditionalCompilationBlock() { @@ -224,8 +224,8 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true) - XCTAssertDiagnosed(.collapseCase(name: "2"), line: 4, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "5"), line: 8, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 4, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 8, column: 1) } func testCasesWithWhereClauses() { @@ -258,14 +258,14 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true) - XCTAssertDiagnosed(.collapseCase(name: "1 where y < 0"), line: 2, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "2 where y == 0"), line: 3, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "3 where y < 0"), line: 4, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "5"), line: 6, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "6"), line: 7, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "7"), line: 8, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "8"), line: 9, column: 1) - XCTAssertDiagnosed(.collapseCase(name: "9"), line: 10, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 2, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 3, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 4, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 6, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 7, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 8, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 9, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 10, column: 1) } func testCasesWithValueBindingsAreNotMerged() { @@ -300,9 +300,9 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true) - XCTAssertDiagnosed(.collapseCase(name: ".a"), line: 2, column: 1) - XCTAssertDiagnosed(.collapseCase(name: ".e"), line: 6, column: 1) - XCTAssertDiagnosed(.collapseCase(name: ".i"), line: 9, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 2, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 6, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 9, column: 1) } func testFallthroughOnlyCasesAreNotMergedWithDefault() { @@ -323,7 +323,7 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true) - XCTAssertDiagnosed(.collapseCase(name: ".a"), line: 2, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 2, column: 1) } func testFallthroughOnlyCasesAreNotMergedWithUnknownDefault() { @@ -344,6 +344,6 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true) - XCTAssertDiagnosed(.collapseCase(name: ".a"), line: 2, column: 1) + XCTAssertDiagnosed(.collapseCase, line: 2, column: 1) } } diff --git a/Tests/SwiftFormatRulesTests/OneVariableDeclarationPerLineTests.swift b/Tests/SwiftFormatRulesTests/OneVariableDeclarationPerLineTests.swift index fb6af3bb3..d9dc6a518 100644 --- a/Tests/SwiftFormatRulesTests/OneVariableDeclarationPerLineTests.swift +++ b/Tests/SwiftFormatRulesTests/OneVariableDeclarationPerLineTests.swift @@ -30,10 +30,10 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 1, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 2, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 4, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 5, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "var"), line: 1, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 2, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 4, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "var"), line: 5, column: 1) } func testNestedVariableBindings() { @@ -112,13 +112,13 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 2, column: 3) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 7, column: 3) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 11, column: 3) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 17, column: 5) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 21, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 23, column: 5) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 27, column: 5) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 2, column: 3) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 7, column: 3) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 11, column: 3) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 17, column: 5) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 21, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 23, column: 5) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "var"), line: 27, column: 5) } func testMixedInitializedAndTypedBindings() { @@ -140,8 +140,8 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 1, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 2, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "var"), line: 1, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 2, column: 1) } func testCommentPrecedingDeclIsNotRepeated() { @@ -161,7 +161,7 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 2, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 2, column: 1) } func testCommentsPrecedingBindingsAreKept() { @@ -179,7 +179,7 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 1, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 1, column: 1) } func testInvalidBindingsAreNotDestroyed() { @@ -204,10 +204,10 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 1, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 2, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 3, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 4, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 1, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 2, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 3, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 4, column: 1) } func testMultipleBindingsWithAccessorsAreCorrected() { @@ -227,6 +227,6 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { """, checkForUnassertedDiagnostics: true ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration, line: 1, column: 1) + XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "var"), line: 1, column: 1) } } From 4c817aeab03776f50a393867ea0eef59b0634491 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 9 Aug 2023 12:44:30 -0400 Subject: [PATCH 079/332] Migrate away from the latest deprecated APIs. --- Sources/SwiftFormat/Parsing.swift | 2 +- Sources/SwiftFormatCore/Context.swift | 3 +- .../AddModifierRewriter.swift | 80 +++++++++--------- .../SwiftFormatRules/FullyIndirectEnum.swift | 5 +- .../ModifierListSyntax+Convenience.swift | 81 +++++++------------ Sources/generate-pipeline/RuleCollector.swift | 8 +- .../SwiftFormatCoreTests/RuleMaskTests.swift | 2 +- 7 files changed, 81 insertions(+), 100 deletions(-) diff --git a/Sources/SwiftFormat/Parsing.swift b/Sources/SwiftFormat/Parsing.swift index 8b7c20adf..0dcfb6308 100644 --- a/Sources/SwiftFormat/Parsing.swift +++ b/Sources/SwiftFormat/Parsing.swift @@ -46,7 +46,7 @@ func parseAndEmitDiagnostics( var hasErrors = false if let parsingDiagnosticHandler = parsingDiagnosticHandler { let expectedConverter = - SourceLocationConverter(file: url?.path ?? "", tree: sourceFile) + SourceLocationConverter(fileName: url?.path ?? "", tree: sourceFile) for diagnostic in diagnostics { let location = diagnostic.location(converter: expectedConverter) diff --git a/Sources/SwiftFormatCore/Context.swift b/Sources/SwiftFormatCore/Context.swift index 9dbd886c8..0d9e6d1dd 100644 --- a/Sources/SwiftFormatCore/Context.swift +++ b/Sources/SwiftFormatCore/Context.swift @@ -76,7 +76,8 @@ public final class Context { self.fileURL = fileURL self.importsXCTest = .notDetermined let tree = source.map { Parser.parse(source: $0) } ?? sourceFileSyntax - self.sourceLocationConverter = SourceLocationConverter(file: fileURL.relativePath, tree: tree) + self.sourceLocationConverter = + SourceLocationConverter(fileName: fileURL.relativePath, tree: tree) self.ruleMask = RuleMask( syntaxNode: Syntax(sourceFileSyntax), sourceLocationConverter: sourceLocationConverter diff --git a/Sources/SwiftFormatRules/AddModifierRewriter.swift b/Sources/SwiftFormatRules/AddModifierRewriter.swift index fe27c046c..94265e23f 100644 --- a/Sources/SwiftFormatRules/AddModifierRewriter.swift +++ b/Sources/SwiftFormatRules/AddModifierRewriter.swift @@ -23,8 +23,8 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { override func visit(_ node: VariableDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard let modifiers = node.modifiers else { - let nodeWithModifier = node.addModifier(modifierKeyword) + guard var modifiers = node.modifiers else { + let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } return DeclSyntax(result) } @@ -32,125 +32,125 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } // Put accessor keyword before the first modifier keyword in the declaration - let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.with(\.modifiers, newModifiers)) + modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) + return DeclSyntax(node.with(\.modifiers, modifiers)) } override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard let modifiers = node.modifiers else { - let nodeWithModifier = node.addModifier(modifierKeyword) + guard var modifiers = node.modifiers else { + let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.with(\.modifiers, newModifiers)) + modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) + return DeclSyntax(node.with(\.modifiers, modifiers)) } override func visit(_ node: AssociatedTypeDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard let modifiers = node.modifiers else { - let nodeWithModifier = node.addModifier(modifierKeyword) + guard var modifiers = node.modifiers else { + let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.with(\.modifiers, newModifiers)) + modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) + return DeclSyntax(node.with(\.modifiers, modifiers)) } override func visit(_ node: ClassDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard let modifiers = node.modifiers else { - let nodeWithModifier = node.addModifier(modifierKeyword) + guard var modifiers = node.modifiers else { + let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.with(\.modifiers, newModifiers)) + modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) + return DeclSyntax(node.with(\.modifiers, modifiers)) } override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard let modifiers = node.modifiers else { - let nodeWithModifier = node.addModifier(modifierKeyword) + guard var modifiers = node.modifiers else { + let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.with(\.modifiers, newModifiers)) + modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) + return DeclSyntax(node.with(\.modifiers, modifiers)) } override func visit(_ node: ProtocolDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard let modifiers = node.modifiers else { - let nodeWithModifier = node.addModifier(modifierKeyword) + guard var modifiers = node.modifiers else { + let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.with(\.modifiers, newModifiers)) + modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) + return DeclSyntax(node.with(\.modifiers, modifiers)) } override func visit(_ node: StructDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard let modifiers = node.modifiers else { - let nodeWithModifier = node.addModifier(modifierKeyword) + guard var modifiers = node.modifiers else { + let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.with(\.modifiers, newModifiers)) + modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) + return DeclSyntax(node.with(\.modifiers, modifiers)) } override func visit(_ node: TypeAliasDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard let modifiers = node.modifiers else { - let nodeWithModifier = node.addModifier(modifierKeyword) + guard var modifiers = node.modifiers else { + let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.with(\.modifiers, newModifiers)) + modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) + return DeclSyntax(node.with(\.modifiers, modifiers)) } override func visit(_ node: InitializerDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard let modifiers = node.modifiers else { - let nodeWithModifier = node.addModifier(modifierKeyword) + guard var modifiers = node.modifiers else { + let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.with(\.modifiers, newModifiers)) + modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) + return DeclSyntax(node.with(\.modifiers, modifiers)) } override func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard let modifiers = node.modifiers else { - let nodeWithModifier = node.addModifier(modifierKeyword) + guard var modifiers = node.modifiers else { + let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - let newModifiers = modifiers.prepend(modifier: modifierKeyword) - return DeclSyntax(node.with(\.modifiers, newModifiers)) + modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) + return DeclSyntax(node.with(\.modifiers, modifiers)) } /// Moves trivia in the given node to correct the placement of potentially displaced trivia in the diff --git a/Sources/SwiftFormatRules/FullyIndirectEnum.swift b/Sources/SwiftFormatRules/FullyIndirectEnum.swift index 60ce164e2..d323eb52f 100644 --- a/Sources/SwiftFormatRules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormatRules/FullyIndirectEnum.swift @@ -71,7 +71,10 @@ public final class FullyIndirectEnum: SyntaxFormatRule { "indirect", leadingTrivia: leadingTrivia, trailingTrivia: .spaces(1)), detail: nil) let newMemberBlock = node.memberBlock.with(\.members, MemberBlockItemListSyntax(newMembers)) - return DeclSyntax(newEnumDecl.addModifier(newModifier).with(\.memberBlock, newMemberBlock)) + return DeclSyntax( + newEnumDecl + .with(\.modifiers, (newEnumDecl.modifiers ?? DeclModifierListSyntax([])) + [newModifier]) + .with(\.memberBlock, newMemberBlock)) } /// Returns a value indicating whether all enum cases in the given list are indirect. diff --git a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift index a4991c42c..fef102fbc 100644 --- a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift +++ b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift @@ -37,8 +37,7 @@ extension DeclModifierListSyntax { /// Returns modifier list without the given modifier. func remove(name: String) -> DeclModifierListSyntax { - let newModifiers = filter { $0.name.text != name } - return DeclModifierListSyntax(newModifiers) + return filter { $0.name.text != name } } /// Returns a formatted declaration modifier token with the given name. @@ -48,58 +47,36 @@ extension DeclModifierListSyntax { return newModifier } - /// Returns modifiers with the given modifier inserted at the given index. - /// Preserves existing trivia and formats new trivia, given true for 'formatTrivia.' - func insert( - modifier: DeclModifierSyntax, at index: Int, - formatTrivia: Bool = true - ) -> DeclModifierListSyntax { - guard index >= 0, index <= count else { return self } + /// Inserts the given modifier into the list at a specific index. + /// + /// If the modifier is being inserted at the front of the list, the current front element's + /// leading trivia will be moved to the new element to preserve any leading comments and newlines. + mutating func triviaPreservingInsert( + _ modifier: DeclModifierSyntax, at index: SyntaxChildrenIndex + ) { + let modifier = replaceTrivia( + on: modifier, + token: modifier.name, + trailingTrivia: .spaces(1)) - var newModifiers: [DeclModifierSyntax] = [] - newModifiers.append(contentsOf: self) - - let modifier = formatTrivia - ? replaceTrivia( - on: modifier, - token: modifier.name, - trailingTrivia: .spaces(1)) : modifier - - if index == 0 { - guard formatTrivia else { - newModifiers.insert(modifier, at: index) - return DeclModifierListSyntax(newModifiers) - } - guard let firstMod = first, let firstTok = firstMod.firstToken(viewMode: .sourceAccurate) else { - newModifiers.insert(modifier, at: index) - return DeclModifierListSyntax(newModifiers) - } - let formattedMod = replaceTrivia( - on: modifier, - token: modifier.firstToken(viewMode: .sourceAccurate), - leadingTrivia: firstTok.leadingTrivia) - newModifiers[0] = replaceTrivia( - on: firstMod, - token: firstTok, - leadingTrivia: [], - trailingTrivia: .spaces(1)) - newModifiers.insert(formattedMod, at: 0) - return DeclModifierListSyntax(newModifiers) - } else { - newModifiers.insert(modifier, at: index) - return DeclModifierListSyntax(newModifiers) + guard index == self.startIndex else { + self.insert(modifier, at: index) + return + } + guard let firstMod = first, let firstTok = firstMod.firstToken(viewMode: .sourceAccurate) else { + self.insert(modifier, at: index) + return } - } - - /// Returns modifier list with the given modifier at the end. - /// Trivia manipulation optional by 'formatTrivia' - func append(modifier: DeclModifierSyntax, formatTrivia: Bool = true) -> DeclModifierListSyntax { - return insert(modifier: modifier, at: count, formatTrivia: formatTrivia) - } - /// Returns modifier list with the given modifier at the beginning. - /// Trivia manipulation optional by 'formatTrivia' - func prepend(modifier: DeclModifierSyntax, formatTrivia: Bool = true) -> DeclModifierListSyntax { - return insert(modifier: modifier, at: 0, formatTrivia: formatTrivia) + let formattedMod = replaceTrivia( + on: modifier, + token: modifier.firstToken(viewMode: .sourceAccurate), + leadingTrivia: firstTok.leadingTrivia) + self[self.startIndex] = replaceTrivia( + on: firstMod, + token: firstTok, + leadingTrivia: [], + trailingTrivia: .spaces(1)) + self.insert(formattedMod, at: self.startIndex) } } diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index 5473fb8fc..0cc0d1d31 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -83,8 +83,8 @@ final class RuleCollector { /// Determine the rule kind for the declaration in the given statement, if any. private func detectedRule(at statement: CodeBlockItemSyntax) -> DetectedRule? { let typeName: String - let members: MemberDeclListSyntax - let maybeInheritanceClause: TypeInheritanceClauseSyntax? + let members: MemberBlockItemListSyntax + let maybeInheritanceClause: InheritanceClauseSyntax? if let classDecl = statement.item.as(ClassDeclSyntax.self) { typeName = classDecl.name.text @@ -105,7 +105,7 @@ final class RuleCollector { // Scan through the inheritance clause to find one of the protocols/types we're interested in. for inheritance in inheritanceClause.inheritedTypes { - guard let identifier = inheritance.type.as(SimpleTypeIdentifierSyntax.self) else { + guard let identifier = inheritance.type.as(IdentifierTypeSyntax.self) else { continue } @@ -126,7 +126,7 @@ final class RuleCollector { guard let function = member.decl.as(FunctionDeclSyntax.self) else { continue } guard function.name.text == "visit" else { continue } let params = function.signature.parameterClause.parameters - guard let firstType = params.firstAndOnly?.type.as(SimpleTypeIdentifierSyntax.self) else { + guard let firstType = params.firstAndOnly?.type.as(IdentifierTypeSyntax.self) else { continue } visitedNodes.append(firstType.name.text) diff --git a/Tests/SwiftFormatCoreTests/RuleMaskTests.swift b/Tests/SwiftFormatCoreTests/RuleMaskTests.swift index 8ba56b179..399e214d0 100644 --- a/Tests/SwiftFormatCoreTests/RuleMaskTests.swift +++ b/Tests/SwiftFormatCoreTests/RuleMaskTests.swift @@ -12,7 +12,7 @@ final class RuleMaskTests: XCTestCase { private func createMask(sourceText: String) -> RuleMask { let fileURL = URL(fileURLWithPath: "/tmp/test.swift") let syntax = Parser.parse(source: sourceText) - converter = SourceLocationConverter(file: fileURL.path, tree: syntax) + converter = SourceLocationConverter(fileName: fileURL.path, tree: syntax) return RuleMask(syntaxNode: Syntax(syntax), sourceLocationConverter: converter) } From 9a1f74fbb52c096312d99cc76af6132cf90c3579 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 21 Jul 2023 17:06:51 -0700 Subject: [PATCH 080/332] [Lint] Add a rule to detect that type declarations are not capitalized --- Sources/SwiftFormat/Pipelines+Generated.swift | 32 +++++++----- .../RuleRegistry+Generated.swift | 1 + .../RuleNameCache+Generated.swift | 1 + .../TypeNamesShouldBeCapitalized.swift | 52 +++++++++++++++++++ .../TypeNamesShouldBeCapitalizedTests.swift | 27 ++++++++++ 5 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift create mode 100644 Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift diff --git a/Sources/SwiftFormat/Pipelines+Generated.swift b/Sources/SwiftFormat/Pipelines+Generated.swift index 2c39463eb..d9d913c62 100644 --- a/Sources/SwiftFormat/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Pipelines+Generated.swift @@ -51,6 +51,7 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } @@ -108,6 +109,7 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(FullyIndirectEnum.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) visitIfEnabled(OneCasePerLine.visit, for: node) + visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } @@ -165,12 +167,22 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } + override func visit(_ node: GenericSpecializationExprSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(UseShorthandTypeNames.visit, for: node) + return .visitChildren + } + override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(IdentifiersMustBeASCII.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visit(_ node: IdentifierTypeSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(UseShorthandTypeNames.visit, for: node) + return .visitChildren + } + override func visit(_ node: IfExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren @@ -194,13 +206,13 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } - override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind { - visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) + override func visit(_ node: MemberBlockItemListSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(DoNotUseSemicolons.visit, for: node) return .visitChildren } - override func visit(_ node: MemberBlockItemListSyntax) -> SyntaxVisitorContinueKind { - visitIfEnabled(DoNotUseSemicolons.visit, for: node) + override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) return .visitChildren } @@ -224,6 +236,7 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } @@ -233,11 +246,6 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } - override func visit(_ node: IdentifierTypeSyntax) -> SyntaxVisitorContinueKind { - visitIfEnabled(UseShorthandTypeNames.visit, for: node) - return .visitChildren - } - override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) @@ -249,16 +257,12 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } - override func visit(_ node: GenericSpecializationExprSyntax) -> SyntaxVisitorContinueKind { - visitIfEnabled(UseShorthandTypeNames.visit, for: node) - return .visitChildren - } - override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) visitIfEnabled(UseSynthesizedInitializer.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren diff --git a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift index 6264ade27..b234d90bd 100644 --- a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift @@ -41,6 +41,7 @@ enum RuleRegistry { "OnlyOneTrailingClosureArgument": true, "OrderedImports": true, "ReturnVoidInsteadOfEmptyTuple": true, + "TypeNamesShouldBeCapitalized": true, "UseEarlyExits": false, "UseLetInEveryBoundCaseVariable": true, "UseShorthandTypeNames": true, diff --git a/Sources/SwiftFormatRules/RuleNameCache+Generated.swift b/Sources/SwiftFormatRules/RuleNameCache+Generated.swift index ec75e73b1..a5adbd68f 100644 --- a/Sources/SwiftFormatRules/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormatRules/RuleNameCache+Generated.swift @@ -41,6 +41,7 @@ public let ruleNameCache: [ObjectIdentifier: String] = [ ObjectIdentifier(OnlyOneTrailingClosureArgument.self): "OnlyOneTrailingClosureArgument", ObjectIdentifier(OrderedImports.self): "OrderedImports", ObjectIdentifier(ReturnVoidInsteadOfEmptyTuple.self): "ReturnVoidInsteadOfEmptyTuple", + ObjectIdentifier(TypeNamesShouldBeCapitalized.self): "TypeNamesShouldBeCapitalized", ObjectIdentifier(UseEarlyExits.self): "UseEarlyExits", ObjectIdentifier(UseLetInEveryBoundCaseVariable.self): "UseLetInEveryBoundCaseVariable", ObjectIdentifier(UseShorthandTypeNames.self): "UseShorthandTypeNames", diff --git a/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift b/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift new file mode 100644 index 000000000..eed5a7fdd --- /dev/null +++ b/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftFormatCore +import SwiftSyntax + +/// `struct`, `class`, `enum` and `protocol` declarations should have a capitalized name. +/// +/// Lint: Types with un-capitalized names will yield a lint error. +public final class TypeNamesShouldBeCapitalized : SyntaxLintRule { + public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + diagnoseNameConventionMismatch(node, name: node.name) + return .visitChildren + } + + public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { + diagnoseNameConventionMismatch(node, name: node.name) + return .visitChildren + } + + public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { + diagnoseNameConventionMismatch(node, name: node.name) + return .visitChildren + } + + public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { + diagnoseNameConventionMismatch(node, name: node.name) + return .visitChildren + } + + private func diagnoseNameConventionMismatch(_ type: T, name: TokenSyntax) { + if let firstChar = name.text.first, !firstChar.isUppercase { + diagnose(.capitalizeTypeName(name: name.text), on: type, severity: .convention) + } + } +} + +extension Finding.Message { + public static func capitalizeTypeName(name: String) -> Finding.Message { + let capitalized = name.prefix(1).uppercased() + name.dropFirst() + return "type names should be capitalized: \(name) -> \(capitalized)" + } +} diff --git a/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift b/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift new file mode 100644 index 000000000..1b4c4545f --- /dev/null +++ b/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift @@ -0,0 +1,27 @@ +import SwiftFormatRules + +final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { + func testConstruction() { + let input = + """ + struct a {} + class klassName { + struct subType {} + } + protocol myProtocol {} + + extension myType { + struct innerType {} + } + """ + + performLint(TypeNamesShouldBeCapitalized.self, input: input) + + XCTAssertDiagnosed(.capitalizeTypeName(name: "a")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "klassName")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "subType")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "myProtocol")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "myType")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "innerType")) + } +} From 201ee75cdd4ec003921bf264b4cd6faca402a57c Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 10 Aug 2023 15:16:26 -0400 Subject: [PATCH 081/332] Replace the `ReplaceTrivia` rewriter with direct trivia mutations. Previously, we were using a `SyntaxRewriter` to replace trivia on nodes. Historically, this originated from a time (I believe) when trivia wasn't directly mutable on nodes. The vast majority of these uses were replacing the extents: the leading trivia of the first token of a node or the trailing trivia of the last token. This can be done more easily now by just mutating those properties of the nodes. Even in cases where we were doing something slightly more advanced, it's clearer to do in-place mutation and reconstruction; having this specialized rewriter no longer held its weight. --- .../AddModifierRewriter.swift | 54 ++++++---------- .../SwiftFormatRules/DoNotUseSemicolons.swift | 8 +-- .../SwiftFormatRules/FullyIndirectEnum.swift | 33 +++++----- .../ModifierListSyntax+Convenience.swift | 23 +++---- .../NoAccessLevelOnExtensionDeclaration.swift | 19 +++--- .../NoEmptyTrailingClosureParentheses.swift | 20 +++--- .../NoParensAroundConditions.swift | 11 ++-- .../OneVariableDeclarationPerLine.swift | 3 +- Sources/SwiftFormatRules/OrderedImports.swift | 11 ++-- Sources/SwiftFormatRules/ReplaceTrivia.swift | 63 ------------------- .../UseShorthandTypeNames.swift | 8 +-- ...eTripleSlashForDocumentationComments.swift | 8 +-- .../FullyIndirectEnumTests.swift | 25 ++++++++ 13 files changed, 100 insertions(+), 186 deletions(-) delete mode 100644 Sources/SwiftFormatRules/ReplaceTrivia.swift diff --git a/Sources/SwiftFormatRules/AddModifierRewriter.swift b/Sources/SwiftFormatRules/AddModifierRewriter.swift index 94265e23f..36ffc017e 100644 --- a/Sources/SwiftFormatRules/AddModifierRewriter.swift +++ b/Sources/SwiftFormatRules/AddModifierRewriter.swift @@ -24,8 +24,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard var modifiers = node.modifiers else { - let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) - let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } + let result = setOnlyModifier(in: node, keywordKeypath: \.bindingSpecifier) return DeclSyntax(result) } // If variable already has an accessor keyword, skip (do not overwrite) @@ -40,8 +39,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard var modifiers = node.modifiers else { - let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) - let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } + let result = setOnlyModifier(in: node, keywordKeypath: \.funcKeyword) return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } @@ -53,8 +51,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard var modifiers = node.modifiers else { - let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) - let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } + let result = setOnlyModifier(in: node, keywordKeypath: \.associatedtypeKeyword) return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } @@ -66,8 +63,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard var modifiers = node.modifiers else { - let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) - let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } + let result = setOnlyModifier(in: node, keywordKeypath: \.classKeyword) return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } @@ -79,8 +75,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard var modifiers = node.modifiers else { - let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) - let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } + let result = setOnlyModifier(in: node, keywordKeypath: \.enumKeyword) return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } @@ -92,8 +87,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard var modifiers = node.modifiers else { - let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) - let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } + let result = setOnlyModifier(in: node, keywordKeypath: \.protocolKeyword) return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } @@ -105,8 +99,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard var modifiers = node.modifiers else { - let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) - let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } + let result = setOnlyModifier(in: node, keywordKeypath: \.structKeyword) return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } @@ -118,8 +111,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard var modifiers = node.modifiers else { - let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) - let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } + let result = setOnlyModifier(in: node, keywordKeypath: \.typealiasKeyword) return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } @@ -131,8 +123,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard var modifiers = node.modifiers else { - let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) - let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } + let result = setOnlyModifier(in: node, keywordKeypath: \.initKeyword) return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } @@ -144,8 +135,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. guard var modifiers = node.modifiers else { - let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword])) - let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers } + let result = setOnlyModifier(in: node, keywordKeypath: \.subscriptKeyword) return DeclSyntax(result) } guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } @@ -161,24 +151,16 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { /// this method does nothing and returns the given node as-is. /// - Parameter node: A node that was updated to include a new modifier. /// - Parameter modifiersProvider: A closure that returns all modifiers for the given node. - private func nodeByRelocatingTrivia( + private func setOnlyModifier( in node: NodeType, - for modifiersProvider: (NodeType) -> DeclModifierListSyntax? + keywordKeypath: WritableKeyPath ) -> NodeType { - guard let modifier = modifiersProvider(node)?.firstAndOnly, - let movingLeadingTrivia = modifier.nextToken(viewMode: .sourceAccurate)?.leadingTrivia - else { - // Otherwise, there's no trivia that needs to be relocated so the node is fine. - return node - } - let nodeWithTrivia = replaceTrivia( - on: node, - token: modifier.firstToken(viewMode: .sourceAccurate), - leadingTrivia: movingLeadingTrivia) - return replaceTrivia( - on: nodeWithTrivia, - token: modifiersProvider(nodeWithTrivia)?.first?.nextToken(viewMode: .sourceAccurate), - leadingTrivia: []) + var node = node + var modifier = modifierKeyword + modifier.leadingTrivia = node[keyPath: keywordKeypath].leadingTrivia + node[keyPath: keywordKeypath].leadingTrivia = [] + node.modifiers = .init([modifier]) + return node } } diff --git a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift index 6877a4a0f..230091fce 100644 --- a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormatRules/DoNotUseSemicolons.swift @@ -59,17 +59,11 @@ public final class DoNotUseSemicolons: SyntaxFormatRule { if previousHadSemicolon, let firstToken = newItem.firstToken(viewMode: .sourceAccurate), !firstToken.leadingTrivia.containsNewlines { - let leadingTrivia = .newlines(1) + firstToken.leadingTrivia - newItem = replaceTrivia( - on: newItem, - token: firstToken, - leadingTrivia: leadingTrivia - ) + newItem.leadingTrivia = .newlines(1) + firstToken.leadingTrivia } // If there's a semicolon, diagnose and remove it. if let semicolon = item.semicolon { - // Exception: do not remove the semicolon if it is separating a 'do' statement from a // 'while' statement. if Syntax(item).as(CodeBlockItemSyntax.self)? diff --git a/Sources/SwiftFormatRules/FullyIndirectEnum.swift b/Sources/SwiftFormatRules/FullyIndirectEnum.swift index cc440fe3d..1ae68067b 100644 --- a/Sources/SwiftFormatRules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormatRules/FullyIndirectEnum.swift @@ -45,8 +45,7 @@ public final class FullyIndirectEnum: SyntaxFormatRule { } let newCase = caseMember.with(\.modifiers, modifiers.remove(name: "indirect")) - let formattedCase = formatCase( - unformattedCase: newCase, leadingTrivia: firstModifier.leadingTrivia) + let formattedCase = rearrangeLeadingTrivia(firstModifier.leadingTrivia, on: newCase) return member.with(\.decl, DeclSyntax(formattedCase)) } @@ -55,15 +54,13 @@ public final class FullyIndirectEnum: SyntaxFormatRule { // line breaks/comments/indentation. let firstTok = node.firstToken(viewMode: .sourceAccurate)! let leadingTrivia: Trivia - let newEnumDecl: EnumDeclSyntax + var newEnumDecl = node if firstTok.tokenKind == .keyword(.enum) { leadingTrivia = firstTok.leadingTrivia - newEnumDecl = replaceTrivia( - on: node, token: node.firstToken(viewMode: .sourceAccurate), leadingTrivia: []) + newEnumDecl.leadingTrivia = [] } else { leadingTrivia = [] - newEnumDecl = node } let newModifier = DeclModifierSyntax( @@ -94,19 +91,23 @@ public final class FullyIndirectEnum: SyntaxFormatRule { } /// Transfers given leading trivia to the first token in the case declaration. - private func formatCase( - unformattedCase: EnumCaseDeclSyntax, - leadingTrivia: Trivia? + private func rearrangeLeadingTrivia( + _ leadingTrivia: Trivia, + on enumCaseDecl: EnumCaseDeclSyntax ) -> EnumCaseDeclSyntax { - if let modifiers = unformattedCase.modifiers, let first = modifiers.first { - return replaceTrivia( - on: unformattedCase, token: first.firstToken(viewMode: .sourceAccurate), leadingTrivia: leadingTrivia - ) + var formattedCase = enumCaseDecl + + if var modifiers = formattedCase.modifiers, var firstModifier = modifiers.first { + // If the case has modifiers, attach the leading trivia to the first one. + firstModifier.leadingTrivia = leadingTrivia + modifiers[modifiers.startIndex] = firstModifier + formattedCase.modifiers = modifiers } else { - return replaceTrivia( - on: unformattedCase, token: unformattedCase.caseKeyword, leadingTrivia: leadingTrivia - ) + // Otherwise, attach the trivia to the `case` keyword itself. + formattedCase.caseKeyword.leadingTrivia = leadingTrivia } + + return formattedCase } } diff --git a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift index fef102fbc..9bd081037 100644 --- a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift +++ b/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift @@ -54,29 +54,22 @@ extension DeclModifierListSyntax { mutating func triviaPreservingInsert( _ modifier: DeclModifierSyntax, at index: SyntaxChildrenIndex ) { - let modifier = replaceTrivia( - on: modifier, - token: modifier.name, - trailingTrivia: .spaces(1)) + var modifier = modifier + modifier.trailingTrivia = [.spaces(1)] guard index == self.startIndex else { self.insert(modifier, at: index) return } - guard let firstMod = first, let firstTok = firstMod.firstToken(viewMode: .sourceAccurate) else { + guard var firstMod = first, let firstTok = firstMod.firstToken(viewMode: .sourceAccurate) else { self.insert(modifier, at: index) return } - let formattedMod = replaceTrivia( - on: modifier, - token: modifier.firstToken(viewMode: .sourceAccurate), - leadingTrivia: firstTok.leadingTrivia) - self[self.startIndex] = replaceTrivia( - on: firstMod, - token: firstTok, - leadingTrivia: [], - trailingTrivia: .spaces(1)) - self.insert(formattedMod, at: self.startIndex) + modifier.leadingTrivia = firstTok.leadingTrivia + firstMod.leadingTrivia = [] + firstMod.trailingTrivia = [.spaces(1)] + self[self.startIndex] = firstMod + self.insert(modifier, at: self.startIndex) } } diff --git a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift index 912ccbcdc..831af4b1e 100644 --- a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift @@ -48,10 +48,8 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { leftBrace: node.memberBlock.leftBrace, members: addMemberAccessKeywords(memDeclBlock: node.memberBlock, keyword: accessKeywordToAdd), rightBrace: node.memberBlock.rightBrace) - let newKeyword = replaceTrivia( - on: node.extensionKeyword, - token: node.extensionKeyword, - leadingTrivia: accessKeyword.leadingTrivia) + var newKeyword = node.extensionKeyword + newKeyword.leadingTrivia = accessKeyword.leadingTrivia let result = node.with(\.memberBlock, newMembers) .with(\.modifiers, modifiers.remove(name: accessKeyword.name.text)) .with(\.extensionKeyword, newKeyword) @@ -62,10 +60,8 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { diagnose( .removeRedundantAccessKeyword(name: node.extendedType.description), on: accessKeyword) - let newKeyword = replaceTrivia( - on: node.extensionKeyword, - token: node.extensionKeyword, - leadingTrivia: accessKeyword.leadingTrivia) + var newKeyword = node.extensionKeyword + newKeyword.leadingTrivia = accessKeyword.leadingTrivia let result = node.with(\.modifiers, modifiers.remove(name: accessKeyword.name.text)) .with(\.extensionKeyword, newKeyword) return DeclSyntax(result) @@ -82,10 +78,9 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { keyword: DeclModifierSyntax ) -> MemberBlockItemListSyntax { var newMembers: [MemberBlockItemSyntax] = [] - let formattedKeyword = replaceTrivia( - on: keyword, - token: keyword.name, - leadingTrivia: []) + + var formattedKeyword = keyword + formattedKeyword.leadingTrivia = [] for memberItem in memDeclBlock.members { let member = memberItem.decl diff --git a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift index 5466934a8..469796fd4 100644 --- a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift +++ b/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift @@ -29,24 +29,24 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { { return super.visit(node) } - guard let name = node.calledExpression.lastToken(viewMode: .sourceAccurate)?.with(\.leadingTrivia, []).with(\.trailingTrivia, []) else { + guard let name = node.calledExpression.lastToken(viewMode: .sourceAccurate) else { return super.visit(node) } - diagnose(.removeEmptyTrailingParentheses(name: "\(name)"), on: node) + diagnose(.removeEmptyTrailingParentheses(name: "\(name.trimmedDescription)"), on: node) // Need to visit `calledExpression` before creating a new node so that the location data (column // and line numbers) is available. - guard let rewrittenCalledExpr = ExprSyntax(rewrite(Syntax(node.calledExpression))) else { + guard var rewrittenCalledExpr = ExprSyntax(rewrite(Syntax(node.calledExpression))) else { return super.visit(node) } - let formattedExp = replaceTrivia( - on: rewrittenCalledExpr, - token: rewrittenCalledExpr.lastToken(viewMode: .sourceAccurate), - trailingTrivia: .spaces(1)) - let formattedClosure = visit(trailingClosure).as(ClosureExprSyntax.self) - let result = node.with(\.leftParen, nil).with(\.rightParen, nil).with(\.calledExpression, formattedExp) - .with(\.trailingClosure, formattedClosure) + rewrittenCalledExpr.trailingTrivia = [.spaces(1)] + + var result = node + result.leftParen = nil + result.rightParen = nil + result.calledExpression = rewrittenCalledExpr + result.trailingClosure = rewrite(trailingClosure).as(ClosureExprSyntax.self) return ExprSyntax(result) } } diff --git a/Sources/SwiftFormatRules/NoParensAroundConditions.swift b/Sources/SwiftFormatRules/NoParensAroundConditions.swift index a6cadf147..3a8713b5c 100644 --- a/Sources/SwiftFormatRules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormatRules/NoParensAroundConditions.swift @@ -46,16 +46,13 @@ public final class NoParensAroundConditions: SyntaxFormatRule { guard let visitedTuple = visit(tuple).as(TupleExprSyntax.self), - let visitedExpr = visitedTuple.elements.first?.expression + var visitedExpr = visitedTuple.elements.first?.expression else { return expr } - return replaceTrivia( - on: visitedExpr, - token: visitedExpr.lastToken(viewMode: .sourceAccurate), - leadingTrivia: visitedTuple.leftParen.leadingTrivia, - trailingTrivia: visitedTuple.rightParen.trailingTrivia - ) + visitedExpr.leadingTrivia = visitedTuple.leftParen.leadingTrivia + visitedExpr.trailingTrivia = visitedTuple.rightParen.trailingTrivia + return visitedExpr } public override func visit(_ node: IfExprSyntax) -> ExprSyntax { diff --git a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift b/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift index e9c62b433..80079c24d 100644 --- a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift +++ b/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift @@ -163,8 +163,7 @@ private struct VariableDeclSplitter { // We intentionally don't try to infer the indentation for subsequent // lines because the pretty printer will re-indent them correctly; we just // need to ensure that a newline is inserted before new decls. - varDecl = replaceTrivia( - on: varDecl, token: varDecl.firstToken(viewMode: .sourceAccurate), leadingTrivia: .newlines(1)) + varDecl.leadingTrivia = [.newlines(1)] fixedUpTrivia = true } diff --git a/Sources/SwiftFormatRules/OrderedImports.swift b/Sources/SwiftFormatRules/OrderedImports.swift index 06df09977..aab84ae25 100644 --- a/Sources/SwiftFormatRules/OrderedImports.swift +++ b/Sources/SwiftFormatRules/OrderedImports.swift @@ -359,13 +359,10 @@ fileprivate func convertToCodeBlockItems(lines: [Line]) -> [CodeBlockItemSyntax] func append(codeBlockItem: CodeBlockItemSyntax) { // Comments and newlines are always located in the leading trivia of an AST node, so we need // not deal with trailing trivia. - output.append( - replaceTrivia( - on: codeBlockItem, - token: codeBlockItem.firstToken(viewMode: .sourceAccurate), - leadingTrivia: Trivia(pieces: triviaBuffer) - ) - ) + var codeBlockItem = codeBlockItem + codeBlockItem.leadingTrivia = Trivia(pieces: triviaBuffer) + output.append(codeBlockItem) + triviaBuffer = [] triviaBuffer += line.trailingTrivia } diff --git a/Sources/SwiftFormatRules/ReplaceTrivia.swift b/Sources/SwiftFormatRules/ReplaceTrivia.swift deleted file mode 100644 index e389d9e77..000000000 --- a/Sources/SwiftFormatRules/ReplaceTrivia.swift +++ /dev/null @@ -1,63 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import Foundation -import SwiftSyntax - -/// Rewriter that replaces the trivia of a given token inside a node with the provided -/// leading/trailing trivia. -fileprivate final class ReplaceTrivia: SyntaxRewriter { - private let leadingTrivia: Trivia? - private let trailingTrivia: Trivia? - private let token: TokenSyntax - - init(token: TokenSyntax, leadingTrivia: Trivia? = nil, trailingTrivia: Trivia? = nil) { - self.token = token - self.leadingTrivia = leadingTrivia - self.trailingTrivia = trailingTrivia - } - - override func visit(_ token: TokenSyntax) -> TokenSyntax { - guard token == self.token else { return token } - return token - .with(\.leadingTrivia, leadingTrivia ?? token.leadingTrivia) - .with(\.trailingTrivia, trailingTrivia ?? token.trailingTrivia) - } -} - -/// Replaces the leading or trailing trivia of a given node to the provided -/// leading and trailing trivia. -/// - Parameters: -/// - node: The Syntax node whose containing token will have its trivia replaced. -/// - token: The token whose trivia will be replaced. Must be a child of `node`. If `nil`, this -/// function is a no-op. -/// - leadingTrivia: The new leading trivia, if applicable. If nothing is provided, no change -/// will be made. -/// - trailingTrivia: The new trailing trivia, if applicable. If nothing is provided, no change -/// will be made. -/// - Note: Most of the time this function is called, `token` will be `node.firstToken` or -/// `node.lastToken`, which is almost always not `nil`. But in some very rare cases, like a -/// collection, it may be empty and not have a `firstToken`. Since there's nothing to -/// replace if token is `nil`, this function just exits early. -func replaceTrivia( - on node: SyntaxType, - token: TokenSyntax?, - leadingTrivia: Trivia? = nil, - trailingTrivia: Trivia? = nil -) -> SyntaxType { - guard let token = token else { return node } - return ReplaceTrivia( - token: token, - leadingTrivia: leadingTrivia, - trailingTrivia: trailingTrivia - ).rewrite(Syntax(node)).as(SyntaxType.self)! -} diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index c5f4d39af..2fd2e3720 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -254,9 +254,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // we need to transfer the leading trivia from the original `Optional` token over to it. // By doing so, something like `/* comment */ Optional` will become `/* comment */ Foo?` // instead of discarding the comment. - wrappedType = - replaceTrivia( - on: wrappedType, token: wrappedType.firstToken(viewMode: .sourceAccurate), leadingTrivia: leadingTrivia) + wrappedType.leadingTrivia = leadingTrivia } let optionalType = OptionalTypeSyntax( @@ -342,9 +340,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // converting a long-form to short-form), we need to transfer it over. By doing so, something // like `/* comment */ Optional` will become `/* comment */ Foo?` instead of discarding // the comment. - wrappedTypeExpr = - replaceTrivia( - on: wrappedTypeExpr, token: wrappedTypeExpr.firstToken(viewMode: .sourceAccurate), leadingTrivia: leadingTrivia) + wrappedTypeExpr.leadingTrivia = leadingTrivia ?? [] } return OptionalChainingExprSyntax( diff --git a/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift b/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift index 48335ffc8..2b2546d6e 100644 --- a/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift +++ b/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift @@ -100,11 +100,9 @@ public final class UseTripleSlashForDocumentationComments: SyntaxFormatRule { pieces.append(.newlines(1)) } - return replaceTrivia( - on: decl, - token: decl.firstToken(viewMode: .sourceAccurate), - leadingTrivia: Trivia(pieces: pieces) - ) + var decl = decl + decl.leadingTrivia = Trivia(pieces: pieces) + return decl } } diff --git a/Tests/SwiftFormatRulesTests/FullyIndirectEnumTests.swift b/Tests/SwiftFormatRulesTests/FullyIndirectEnumTests.swift index 9d24ed7a9..69977104f 100644 --- a/Tests/SwiftFormatRulesTests/FullyIndirectEnumTests.swift +++ b/Tests/SwiftFormatRulesTests/FullyIndirectEnumTests.swift @@ -26,6 +26,31 @@ class FullyIndirectEnumTests: LintOrFormatRuleTestCase { """) } + func testAllIndirectCasesWithAttributes() { + XCTAssertFormatting( + FullyIndirectEnum.self, + input: """ + // Comment 1 + public enum DependencyGraphNode { + @someAttr internal indirect case userDefined(dependencies: [DependencyGraphNode]) + // Comment 2 + @someAttr indirect case synthesized(dependencies: [DependencyGraphNode]) + @someAttr indirect case other(dependencies: [DependencyGraphNode]) + var x: Int + } + """, + expected: """ + // Comment 1 + public indirect enum DependencyGraphNode { + @someAttr internal case userDefined(dependencies: [DependencyGraphNode]) + // Comment 2 + @someAttr case synthesized(dependencies: [DependencyGraphNode]) + @someAttr case other(dependencies: [DependencyGraphNode]) + var x: Int + } + """) + } + func testNotAllIndirectCases() { let input = """ public enum CompassPoint { From 711a7a0749bb2cbca04e981c1e7e86ca3efbb68e Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Fri, 11 Aug 2023 08:35:58 -0400 Subject: [PATCH 082/332] Parenthesize `some/any` types when converting `Optional` to `?`. Fixes #586. --- .../UseShorthandTypeNames.swift | 81 ++++++++++++------- .../UseShorthandTypeNamesTests.swift | 35 ++++++++ 2 files changed, 85 insertions(+), 31 deletions(-) diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift index 2fd2e3720..c6da7b9f4 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormatRules/UseShorthandTypeNames.swift @@ -236,20 +236,16 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { ) -> TypeSyntax { var wrappedType = wrappedType - if let functionType = wrappedType.as(FunctionTypeSyntax.self) { - // Function types must be wrapped as a tuple before using shorthand optional syntax, - // otherwise the "?" applies to the return type instead of the function type. Attach the - // leading trivia to the left-paren that we're adding in this case. - let tupleTypeElement = TupleTypeElementSyntax( - inoutKeyword: nil, firstName: nil, secondName: nil, colon: nil, type: TypeSyntax(functionType), - ellipsis: nil, trailingComma: nil) - let tupleTypeElementList = TupleTypeElementListSyntax([tupleTypeElement]) - let tupleType = TupleTypeSyntax( - leftParen: TokenSyntax.leftParenToken(leadingTrivia: leadingTrivia), - elements: tupleTypeElementList, - rightParen: TokenSyntax.rightParenToken()) - wrappedType = TypeSyntax(tupleType) - } else { + // Function types and some-or-any types must be wrapped in parentheses before using shorthand + // optional syntax, otherwise the "?" will bind incorrectly (in the function case it binds to + // only the result, and in the some-or-any case it only binds to the child protocol). Attach the + // leading trivia to the left-paren that we're adding in these cases. + switch Syntax(wrappedType).as(SyntaxEnum.self) { + case .functionType(let functionType): + wrappedType = parenthesizedType(functionType, leadingTrivia: leadingTrivia) + case .someOrAnyType(let someOrAnyType): + wrappedType = parenthesizedType(someOrAnyType, leadingTrivia: leadingTrivia) + default: // Otherwise, the argument type can safely become an optional by simply appending a "?", but // we need to transfer the leading trivia from the original `Optional` token over to it. // By doing so, something like `/* comment */ Optional` will become `/* comment */ Foo?` @@ -316,31 +312,25 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { /// key type or value type does not have a valid expression representation. private func makeOptionalTypeExpression( wrapping wrappedType: TypeSyntax, - leadingTrivia: Trivia? = nil, + leadingTrivia: Trivia = [], questionMark: TokenSyntax ) -> OptionalChainingExprSyntax? { guard var wrappedTypeExpr = expressionRepresentation(of: wrappedType) else { return nil } - if wrappedType.is(FunctionTypeSyntax.self) { - // Function types must be wrapped as a tuple before using shorthand optional syntax, - // otherwise the "?" applies to the return type instead of the function type. Attach the - // leading trivia to the left-paren that we're adding in this case. - let tupleExprElement = - LabeledExprSyntax( - label: nil, colon: nil, expression: wrappedTypeExpr, trailingComma: nil) - let tupleExprElementList = LabeledExprListSyntax([tupleExprElement]) - let tupleExpr = TupleExprSyntax( - leftParen: TokenSyntax.leftParenToken(leadingTrivia: leadingTrivia ?? []), - elements: tupleExprElementList, - rightParen: TokenSyntax.rightParenToken()) - wrappedTypeExpr = ExprSyntax(tupleExpr) - } else { + // Function types and some-or-any types must be wrapped in parentheses before using shorthand + // optional syntax, otherwise the "?" will bind incorrectly (in the function case it binds to + // only the result, and in the some-or-any case it only binds to the child protocol). Attach the + // leading trivia to the left-paren that we're adding in these cases. + switch Syntax(wrappedType).as(SyntaxEnum.self) { + case .functionType, .someOrAnyType: + wrappedTypeExpr = parenthesizedExpr(wrappedTypeExpr, leadingTrivia: leadingTrivia) + default: // Otherwise, the argument type can safely become an optional by simply appending a "?". If // we were given leading trivia from another node (for example, from `Optional` when // converting a long-form to short-form), we need to transfer it over. By doing so, something // like `/* comment */ Optional` will become `/* comment */ Foo?` instead of discarding // the comment. - wrappedTypeExpr.leadingTrivia = leadingTrivia ?? [] + wrappedTypeExpr.leadingTrivia = leadingTrivia } return OptionalChainingExprSyntax( @@ -348,6 +338,32 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { questionMark: questionMark) } + /// Returns the given type wrapped in parentheses. + private func parenthesizedType( + _ typeToWrap: TypeNode, + leadingTrivia: Trivia + ) -> TypeSyntax { + let tupleTypeElement = TupleTypeElementSyntax(type: TypeSyntax(typeToWrap)) + let tupleType = TupleTypeSyntax( + leftParen: .leftParenToken(leadingTrivia: leadingTrivia), + elements: TupleTypeElementListSyntax([tupleTypeElement]), + rightParen: .rightParenToken()) + return TypeSyntax(tupleType) + } + + /// Returns the given expression wrapped in parentheses. + private func parenthesizedExpr( + _ exprToWrap: ExprNode, + leadingTrivia: Trivia + ) -> ExprSyntax { + let tupleExprElement = LabeledExprSyntax(expression: exprToWrap) + let tupleExpr = TupleExprSyntax( + leftParen: .leftParenToken(leadingTrivia: leadingTrivia), + elements: LabeledExprListSyntax([tupleExprElement]), + rightParen: .rightParenToken()) + return ExprSyntax(tupleExpr) + } + /// Returns an `ExprSyntax` that is syntactically equivalent to the given `TypeSyntax`, or nil if /// it wouldn't be valid. /// @@ -404,7 +420,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { case .optionalType(let optionalType): let result = makeOptionalTypeExpression( wrapping: optionalType.wrappedType, - leadingTrivia: optionalType.firstToken(viewMode: .sourceAccurate)?.leadingTrivia, + leadingTrivia: optionalType.firstToken(viewMode: .sourceAccurate)?.leadingTrivia ?? [], questionMark: optionalType.questionMark) return ExprSyntax(result) @@ -427,6 +443,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { rightParen: tupleType.rightParen) return ExprSyntax(result) + case .someOrAnyType(let someOrAnyType): + return ExprSyntax(TypeExprSyntax(type: someOrAnyType)) + default: return nil } diff --git a/Tests/SwiftFormatRulesTests/UseShorthandTypeNamesTests.swift b/Tests/SwiftFormatRulesTests/UseShorthandTypeNamesTests.swift index bdcd4ac5a..388c4386d 100644 --- a/Tests/SwiftFormatRulesTests/UseShorthandTypeNamesTests.swift +++ b/Tests/SwiftFormatRulesTests/UseShorthandTypeNamesTests.swift @@ -413,4 +413,39 @@ final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { var e: [String: Int?] """) } + + func testSomeAnyTypesInOptionalsAreParenthesized() { + // If we need to insert parentheses, verify that we do, but also verify that we don't insert + // them unnecessarily. + XCTAssertFormatting( + UseShorthandTypeNames.self, + input: + """ + func f(_: Optional) {} + func g(_: Optional) {} + var x: Optional = S() + var y: Optional = S() + var z = [Optional]([S()]) + + func f(_: Optional<(some P)>) {} + func g(_: Optional<(any P)>) {} + var x: Optional<(some P)> = S() + var y: Optional<(any P)> = S() + var z = [Optional<(any P)>]([S()]) + """, + expected: + """ + func f(_: (some P)?) {} + func g(_: (any P)?) {} + var x: (some P)? = S() + var y: (any P)? = S() + var z = [(any P)?]([S()]) + + func f(_: (some P)?) {} + func g(_: (any P)?) {} + var x: (some P)? = S() + var y: (any P)? = S() + var z = [(any P)?]([S()]) + """) + } } From d81e9876e0744339bfcb3e094da919caa2f101c1 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 9 Aug 2023 13:56:42 -0700 Subject: [PATCH 083/332] Update for the fact that syntax collections are always non-optional in SwiftSyntax now --- .../TokenStreamCreator.swift | 32 +++---- .../AddModifierRewriter.swift | 92 +++++++++++-------- .../AlwaysUseLowerCamelCase.swift | 4 +- .../AmbiguousTrailingClosureOverload.swift | 2 +- .../DontRepeatTypeInStaticProperties.swift | 3 +- .../FileScopedDeclarationPrivacy.swift | 2 +- .../SwiftFormatRules/FullyIndirectEnum.swift | 20 ++-- ...NeverUseImplicitlyUnwrappedOptionals.swift | 8 +- .../NoAccessLevelOnExtensionDeclaration.swift | 8 +- Sources/SwiftFormatRules/OrderedImports.swift | 2 +- .../UseSingleLinePropertyGetter.swift | 1 - .../UseSynthesizedInitializer.swift | 12 +-- 12 files changed, 92 insertions(+), 94 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 0fe1209f2..c4f769c8f 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -274,7 +274,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Prioritize keeping " macro (" together. Also include the ")" if the // parameter list is empty. let firstTokenAfterAttributes = - node.modifiers?.firstToken(viewMode: .sourceAccurate) ?? node.macroKeyword + node.modifiers.firstToken(viewMode: .sourceAccurate) ?? node.macroKeyword before(firstTokenAfterAttributes, tokens: .open) after(node.macroKeyword, tokens: .break) if hasArguments || node.genericParameterClause != nil { @@ -352,7 +352,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Prioritize keeping " func (" together. Also include the ")" if the parameter // list is empty. - let firstTokenAfterAttributes = node.modifiers?.firstToken(viewMode: .sourceAccurate) ?? node.funcKeyword + let firstTokenAfterAttributes = node.modifiers.firstToken(viewMode: .sourceAccurate) ?? node.funcKeyword before(firstTokenAfterAttributes, tokens: .open) after(node.funcKeyword, tokens: .break) if hasArguments || node.genericParameterClause != nil { @@ -392,7 +392,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { arrangeParameterClause(node.signature.parameterClause, forcesBreakBeforeRightParen: node.body != nil) // Prioritize keeping " init" together. - let firstTokenAfterAttributes = node.modifiers?.firstToken(viewMode: .sourceAccurate) ?? node.initKeyword + let firstTokenAfterAttributes = node.modifiers.firstToken(viewMode: .sourceAccurate) ?? node.initKeyword before(firstTokenAfterAttributes, tokens: .open) if hasArguments || node.genericParameterClause != nil { @@ -427,7 +427,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) // Prioritize keeping " subscript" together. - if let firstModifierToken = node.modifiers?.firstToken(viewMode: .sourceAccurate) { + if let firstModifierToken = node.modifiers.firstToken(viewMode: .sourceAccurate) { before(firstModifierToken, tokens: .open) if hasArguments || node.genericParameterClause != nil { @@ -700,18 +700,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ? Token.break(.same, newlines: .soft) : Token.space before(node.catchKeyword, tokens: catchPrecedingBreak) - if let catchItems = node.catchItems { - // If there are multiple items in the `catch` clause, wrap each in open/close breaks so that - // their internal breaks stack correctly. Otherwise, if there is only a single clause, use the - // old (pre-SE-0276) behavior (a fixed space after the `catch` keyword). - if catchItems.count > 1 { - for catchItem in catchItems { - before(catchItem.firstToken(viewMode: .sourceAccurate), tokens: .break(.open(kind: .continuation))) - after(catchItem.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) - } - } else { - before(node.catchItems?.firstToken(viewMode: .sourceAccurate), tokens: .space) + // If there are multiple items in the `catch` clause, wrap each in open/close breaks so that + // their internal breaks stack correctly. Otherwise, if there is only a single clause, use the + // old (pre-SE-0276) behavior (a fixed space after the `catch` keyword). + if node.catchItems.count > 1 { + for catchItem in node.catchItems { + before(catchItem.firstToken(viewMode: .sourceAccurate), tokens: .break(.open(kind: .continuation))) + after(catchItem.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) } + } else { + before(node.catchItems.firstToken(viewMode: .sourceAccurate), tokens: .space) } arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) @@ -1023,11 +1021,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { preVisitInsertingContextualBreaks(node) // If there are multiple trailing closures, force all the closures in the call to break. - if let additionalTrailingClosures = node.additionalTrailingClosures { + if !node.additionalTrailingClosures.isEmpty { if let closure = node.trailingClosure { forcedBreakingClosures.insert(closure.id) } - for additionalTrailingClosure in additionalTrailingClosures { + for additionalTrailingClosure in node.additionalTrailingClosures { forcedBreakingClosures.insert(additionalTrailingClosure.closure.id) } } diff --git a/Sources/SwiftFormatRules/AddModifierRewriter.swift b/Sources/SwiftFormatRules/AddModifierRewriter.swift index 36ffc017e..a233a9f2b 100644 --- a/Sources/SwiftFormatRules/AddModifierRewriter.swift +++ b/Sources/SwiftFormatRules/AddModifierRewriter.swift @@ -23,124 +23,136 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter { override func visit(_ node: VariableDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard var modifiers = node.modifiers else { + guard !node.modifiers.isEmpty else { let result = setOnlyModifier(in: node, keywordKeypath: \.bindingSpecifier) return DeclSyntax(result) } + var node = node + // If variable already has an accessor keyword, skip (do not overwrite) - guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } + guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } // Put accessor keyword before the first modifier keyword in the declaration - modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) - return DeclSyntax(node.with(\.modifiers, modifiers)) + node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) + return DeclSyntax(node) } override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard var modifiers = node.modifiers else { + guard !node.modifiers.isEmpty else { let result = setOnlyModifier(in: node, keywordKeypath: \.funcKeyword) return DeclSyntax(result) } - guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) - return DeclSyntax(node.with(\.modifiers, modifiers)) + guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } + var node = node + node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) + return DeclSyntax(node) } override func visit(_ node: AssociatedTypeDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard var modifiers = node.modifiers else { + guard !node.modifiers.isEmpty else { let result = setOnlyModifier(in: node, keywordKeypath: \.associatedtypeKeyword) return DeclSyntax(result) } - guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) - return DeclSyntax(node.with(\.modifiers, modifiers)) + guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } + var node = node + node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) + return DeclSyntax(node) } override func visit(_ node: ClassDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard var modifiers = node.modifiers else { + guard !node.modifiers.isEmpty else { let result = setOnlyModifier(in: node, keywordKeypath: \.classKeyword) return DeclSyntax(result) } - guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) - return DeclSyntax(node.with(\.modifiers, modifiers)) + guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } + var node = node + node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) + return DeclSyntax(node) } override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard var modifiers = node.modifiers else { + guard !node.modifiers.isEmpty else { let result = setOnlyModifier(in: node, keywordKeypath: \.enumKeyword) return DeclSyntax(result) } - guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) - return DeclSyntax(node.with(\.modifiers, modifiers)) + guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } + var node = node + node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) + return DeclSyntax(node) } override func visit(_ node: ProtocolDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard var modifiers = node.modifiers else { + + guard !node.modifiers.isEmpty else { let result = setOnlyModifier(in: node, keywordKeypath: \.protocolKeyword) return DeclSyntax(result) } - guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) - return DeclSyntax(node.with(\.modifiers, modifiers)) + guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } + var node = node + node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) + return DeclSyntax(node) } override func visit(_ node: StructDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard var modifiers = node.modifiers else { + guard !node.modifiers.isEmpty else { let result = setOnlyModifier(in: node, keywordKeypath: \.structKeyword) return DeclSyntax(result) } - guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) - return DeclSyntax(node.with(\.modifiers, modifiers)) + guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } + var node = node + node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) + return DeclSyntax(node) } override func visit(_ node: TypeAliasDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard var modifiers = node.modifiers else { + guard !node.modifiers.isEmpty else { let result = setOnlyModifier(in: node, keywordKeypath: \.typealiasKeyword) return DeclSyntax(result) } - guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) - return DeclSyntax(node.with(\.modifiers, modifiers)) + guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } + var node = node + node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) + return DeclSyntax(node) } override func visit(_ node: InitializerDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard var modifiers = node.modifiers else { + guard !node.modifiers.isEmpty else { let result = setOnlyModifier(in: node, keywordKeypath: \.initKeyword) return DeclSyntax(result) } - guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) - return DeclSyntax(node.with(\.modifiers, modifiers)) + guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } + var node = node + node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) + return DeclSyntax(node) } override func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax { // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced // token. - guard var modifiers = node.modifiers else { + guard !node.modifiers.isEmpty else { let result = setOnlyModifier(in: node, keywordKeypath: \.subscriptKeyword) return DeclSyntax(result) } - guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - modifiers.triviaPreservingInsert(modifierKeyword, at: modifiers.startIndex) - return DeclSyntax(node.with(\.modifiers, modifiers)) + guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } + var node = node + node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) + return DeclSyntax(node) } /// Moves trivia in the given node to correct the placement of potentially displaced trivia in the diff --git a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift index bfa6e7a8d..2baa29cbb 100644 --- a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift @@ -46,7 +46,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { // Don't diagnose any issues when the variable is overriding, because this declaration can't // rename the variable. If the user analyzes the code where the variable is really declared, // then the diagnostic can be raised for just that location. - if let modifiers = node.modifiers, modifiers.has(modifier: "override") { + if node.modifiers.has(modifier: "override") { return .visitChildren } @@ -114,7 +114,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { // Don't diagnose any issues when the function is overriding, because this declaration can't // rename the function. If the user analyzes the code where the function is really declared, // then the diagnostic can be raised for just that location. - if let modifiers = node.modifiers, modifiers.has(modifier: "override") { + if node.modifiers.has(modifier: "override") { return .visitChildren } diff --git a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift index 809853223..4ba408259 100644 --- a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift +++ b/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift @@ -42,7 +42,7 @@ public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { let params = fn.signature.parameterClause.parameters guard let firstParam = params.firstAndOnly else { continue } guard firstParam.type.is(FunctionTypeSyntax.self) else { continue } - if let mods = fn.modifiers, mods.has(modifier: "static") || mods.has(modifier: "class") { + if fn.modifiers.has(modifier: "static") || fn.modifiers.has(modifier: "class") { staticOverloads[fn.name.text, default: []].append(fn) } else { overloads[fn.name.text, default: []].append(fn) diff --git a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift index 3f2c68b7a..85eb7faeb 100644 --- a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift @@ -69,8 +69,7 @@ public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { for member in members { guard let varDecl = member.decl.as(VariableDeclSyntax.self), - let modifiers = varDecl.modifiers, - modifiers.has(modifier: "static") || modifiers.has(modifier: "class") + varDecl.modifiers.has(modifier: "static") || varDecl.modifiers.has(modifier: "class") else { continue } let bareTypeName = removingPossibleNamespacePrefix(from: typeName) diff --git a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift index 9050c6db9..aee84aa53 100644 --- a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift +++ b/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift @@ -136,7 +136,7 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { private func rewrittenDecl( _ decl: DeclType, modifiers: DeclModifierListSyntax?, - factory: (DeclModifierListSyntax?) -> DeclType + factory: (DeclModifierListSyntax) -> DeclType ) -> DeclType { let invalidAccess: TokenKind let validAccess: TokenKind diff --git a/Sources/SwiftFormatRules/FullyIndirectEnum.swift b/Sources/SwiftFormatRules/FullyIndirectEnum.swift index 1ae68067b..dc02ebde9 100644 --- a/Sources/SwiftFormatRules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormatRules/FullyIndirectEnum.swift @@ -24,8 +24,7 @@ public final class FullyIndirectEnum: SyntaxFormatRule { public override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { let enumMembers = node.memberBlock.members - guard let enumModifiers = node.modifiers, - !enumModifiers.has(modifier: "indirect"), + guard !node.modifiers.has(modifier: "indirect"), allCasesAreIndirect(in: enumMembers) else { return DeclSyntax(node) @@ -37,14 +36,13 @@ public final class FullyIndirectEnum: SyntaxFormatRule { let newMembers = enumMembers.map { (member: MemberBlockItemSyntax) -> MemberBlockItemSyntax in guard let caseMember = member.decl.as(EnumCaseDeclSyntax.self), - let modifiers = caseMember.modifiers, - modifiers.has(modifier: "indirect"), - let firstModifier = modifiers.first + caseMember.modifiers.has(modifier: "indirect"), + let firstModifier = caseMember.modifiers.first else { return member } - let newCase = caseMember.with(\.modifiers, modifiers.remove(name: "indirect")) + let newCase = caseMember.with(\.modifiers, caseMember.modifiers.remove(name: "indirect")) let formattedCase = rearrangeLeadingTrivia(firstModifier.leadingTrivia, on: newCase) return member.with(\.decl, DeclSyntax(formattedCase)) } @@ -70,7 +68,7 @@ public final class FullyIndirectEnum: SyntaxFormatRule { let newMemberBlock = node.memberBlock.with(\.members, MemberBlockItemListSyntax(newMembers)) return DeclSyntax( newEnumDecl - .with(\.modifiers, (newEnumDecl.modifiers ?? DeclModifierListSyntax([])) + [newModifier]) + .with(\.modifiers, newEnumDecl.modifiers + [newModifier]) .with(\.memberBlock, newMemberBlock)) } @@ -82,7 +80,7 @@ public final class FullyIndirectEnum: SyntaxFormatRule { for member in members { if let caseMember = member.decl.as(EnumCaseDeclSyntax.self) { hadCases = true - guard let modifiers = caseMember.modifiers, modifiers.has(modifier: "indirect") else { + guard caseMember.modifiers.has(modifier: "indirect") else { return false } } @@ -97,11 +95,11 @@ public final class FullyIndirectEnum: SyntaxFormatRule { ) -> EnumCaseDeclSyntax { var formattedCase = enumCaseDecl - if var modifiers = formattedCase.modifiers, var firstModifier = modifiers.first { + if var firstModifier = formattedCase.modifiers.first { // If the case has modifiers, attach the leading trivia to the first one. firstModifier.leadingTrivia = leadingTrivia - modifiers[modifiers.startIndex] = firstModifier - formattedCase.modifiers = modifiers + formattedCase.modifiers[formattedCase.modifiers.startIndex] = firstModifier + formattedCase.modifiers = formattedCase.modifiers } else { // Otherwise, attach the trivia to the `case` keyword itself. formattedCase.caseKeyword.leadingTrivia = leadingTrivia diff --git a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift index d9e7a0cce..803f31c2a 100644 --- a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -40,11 +40,9 @@ public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule { public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren } // Ignores IBOutlet variables - if let attributes = node.attributes { - for attribute in attributes { - if (attribute.as(AttributeSyntax.self))?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "IBOutlet" { - return .skipChildren - } + for attribute in node.attributes { + if (attribute.as(AttributeSyntax.self))?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "IBOutlet" { + return .skipChildren } } // Finds type annotation for variable(s) diff --git a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift index 831af4b1e..d59a4bbc5 100644 --- a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift @@ -25,8 +25,8 @@ import SwiftSyntax public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { public override func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax { - guard let modifiers = node.modifiers, modifiers.count != 0 else { return DeclSyntax(node) } - guard let accessKeyword = modifiers.accessLevelModifier else { return DeclSyntax(node) } + guard !node.modifiers.isEmpty else { return DeclSyntax(node) } + guard let accessKeyword = node.modifiers.accessLevelModifier else { return DeclSyntax(node) } let keywordKind = accessKeyword.name.tokenKind switch keywordKind { @@ -51,7 +51,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { var newKeyword = node.extensionKeyword newKeyword.leadingTrivia = accessKeyword.leadingTrivia let result = node.with(\.memberBlock, newMembers) - .with(\.modifiers, modifiers.remove(name: accessKeyword.name.text)) + .with(\.modifiers, node.modifiers.remove(name: accessKeyword.name.text)) .with(\.extensionKeyword, newKeyword) return DeclSyntax(result) @@ -62,7 +62,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { on: accessKeyword) var newKeyword = node.extensionKeyword newKeyword.leadingTrivia = accessKeyword.leadingTrivia - let result = node.with(\.modifiers, modifiers.remove(name: accessKeyword.name.text)) + let result = node.with(\.modifiers, node.modifiers.remove(name: accessKeyword.name.text)) .with(\.extensionKeyword, newKeyword) return DeclSyntax(result) diff --git a/Sources/SwiftFormatRules/OrderedImports.swift b/Sources/SwiftFormatRules/OrderedImports.swift index aab84ae25..313af4e1e 100644 --- a/Sources/SwiftFormatRules/OrderedImports.swift +++ b/Sources/SwiftFormatRules/OrderedImports.swift @@ -514,7 +514,7 @@ fileprivate class Line { /// Returns a `LineType` the represents the type of import from the given import decl. private func importType(of importDecl: ImportDeclSyntax) -> LineType { - if let attr = importDecl.attributes?.firstToken(viewMode: .sourceAccurate), + if let attr = importDecl.attributes.firstToken(viewMode: .sourceAccurate), attr.tokenKind == .atSign, attr.nextToken(viewMode: .sourceAccurate)?.text == "testable" { diff --git a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift index a40029627..ddc6dbd00 100644 --- a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift @@ -28,7 +28,6 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { let body = acc.body, accessors.count == 1, acc.accessorSpecifier.tokenKind == .keyword(.get), - acc.attributes == nil, acc.modifier == nil, acc.effectSpecifiers == nil else { return node } diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift index 3f70f363e..d7bede3a1 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift @@ -31,11 +31,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { let member = memberItem.decl // Collect all stored variables into a list if let varDecl = member.as(VariableDeclSyntax.self) { - guard let modifiers = varDecl.modifiers else { - storedProperties.append(varDecl) - continue - } - guard !modifiers.has(modifier: "static") else { continue } + guard !varDecl.modifiers.has(modifier: "static") else { continue } storedProperties.append(varDecl) // Collect any possible redundant initializers into a list } else if let initDecl = member.as(InitializerDeclSyntax.self) { @@ -208,13 +204,11 @@ fileprivate enum AccessLevel { fileprivate func synthesizedInitAccessLevel(using properties: [VariableDeclSyntax]) -> AccessLevel { var hasFileprivate = false for property in properties { - guard let modifiers = property.modifiers else { continue } - // Private takes precedence, so finding 1 private property defines the access level. - if modifiers.contains(where: {$0.name.tokenKind == .keyword(.private) && $0.detail == nil}) { + if property.modifiers.contains(where: {$0.name.tokenKind == .keyword(.private) && $0.detail == nil}) { return .private } - if modifiers.contains(where: {$0.name.tokenKind == .keyword(.fileprivate) && $0.detail == nil}) { + if property.modifiers.contains(where: {$0.name.tokenKind == .keyword(.fileprivate) && $0.detail == nil}) { hasFileprivate = true // Can't break here because a later property might be private. } From 5538ecbf291f60af54752fc71a55c4904e579858 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 11 Aug 2023 15:13:23 -0700 Subject: [PATCH 084/332] [Lint] Extend type capitalization rule to cover actors, associated types and typealias --- Sources/SwiftFormat/Pipelines+Generated.swift | 7 +++ .../TypeNamesShouldBeCapitalized.swift | 15 ++++++ .../TypeNamesShouldBeCapitalizedTests.swift | 47 +++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/Sources/SwiftFormat/Pipelines+Generated.swift b/Sources/SwiftFormat/Pipelines+Generated.swift index d9d913c62..a8331cb34 100644 --- a/Sources/SwiftFormat/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Pipelines+Generated.swift @@ -34,6 +34,11 @@ class LintPipeline: SyntaxVisitor { super.init(viewMode: .sourceAccurate) } + override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) + return .visitChildren + } + override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren @@ -42,6 +47,7 @@ class LintPipeline: SyntaxVisitor { override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) return .visitChildren } @@ -304,6 +310,7 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } diff --git a/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift b/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift index eed5a7fdd..1b3a3f3f9 100644 --- a/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift +++ b/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift @@ -37,6 +37,21 @@ public final class TypeNamesShouldBeCapitalized : SyntaxLintRule { return .visitChildren } + public override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { + diagnoseNameConventionMismatch(node, name: node.name) + return .visitChildren + } + + public override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind { + diagnoseNameConventionMismatch(node, name: node.name) + return .visitChildren + } + + public override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { + diagnoseNameConventionMismatch(node, name: node.name) + return .visitChildren + } + private func diagnoseNameConventionMismatch(_ type: T, name: TokenSyntax) { if let firstChar = name.text.first, !firstChar.isUppercase { diagnose(.capitalizeTypeName(name: name.text), on: type, severity: .convention) diff --git a/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift b/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift index 1b4c4545f..3b292d521 100644 --- a/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift +++ b/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift @@ -24,4 +24,51 @@ final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { XCTAssertNotDiagnosed(.capitalizeTypeName(name: "myType")) XCTAssertDiagnosed(.capitalizeTypeName(name: "innerType")) } + + func testActors() { + let input = + """ + actor myActor {} + actor OtherActor {} + distributed actor greeter {} + distributed actor DistGreeter {} + """ + + performLint(TypeNamesShouldBeCapitalized.self, input: input) + + XCTAssertDiagnosed(.capitalizeTypeName(name: "myActor")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "OtherActor")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "greeter")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "DistGreeter")) + } + + func testAssociatedTypeandTypeAlias() { + let input = + """ + protocol P { + associatedtype kind + associatedtype OtherKind + } + + typealias x = Int + typealias Y = String + + struct MyType { + typealias data = Y + + func test() { + typealias Value = Y + } + } + """ + + performLint(TypeNamesShouldBeCapitalized.self, input: input) + + XCTAssertDiagnosed(.capitalizeTypeName(name: "kind")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "OtherKind")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "x")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "Y")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "data")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "Value")) + } } From be4493c93dfb626965c56c4dddca10e15d7c482a Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 11 Aug 2023 15:35:35 -0700 Subject: [PATCH 085/332] [Lint] Adjust capitalization rule to properly handle underscored names --- .../TypeNamesShouldBeCapitalized.swift | 9 +++- .../TypeNamesShouldBeCapitalizedTests.swift | 54 +++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift b/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift index 1b3a3f3f9..153adba1e 100644 --- a/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift +++ b/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift @@ -53,7 +53,9 @@ public final class TypeNamesShouldBeCapitalized : SyntaxLintRule { } private func diagnoseNameConventionMismatch(_ type: T, name: TokenSyntax) { - if let firstChar = name.text.first, !firstChar.isUppercase { + let leadingUnderscores = name.text.prefix { $0 == "_" } + if let firstChar = name.text[leadingUnderscores.endIndex...].first, + firstChar.uppercased() != String(firstChar) { diagnose(.capitalizeTypeName(name: name.text), on: type, severity: .convention) } } @@ -61,7 +63,10 @@ public final class TypeNamesShouldBeCapitalized : SyntaxLintRule { extension Finding.Message { public static func capitalizeTypeName(name: String) -> Finding.Message { - let capitalized = name.prefix(1).uppercased() + name.dropFirst() + var capitalized = name + let leadingUnderscores = capitalized.prefix { $0 == "_" } + let charAt = leadingUnderscores.endIndex + capitalized.replaceSubrange(charAt...charAt, with: capitalized[charAt].uppercased()) return "type names should be capitalized: \(name) -> \(capitalized)" } } diff --git a/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift b/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift index 3b292d521..8d0341571 100644 --- a/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift +++ b/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift @@ -71,4 +71,58 @@ final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { XCTAssertDiagnosed(.capitalizeTypeName(name: "data")) XCTAssertNotDiagnosed(.capitalizeTypeName(name: "Value")) } + + func testThatUnderscoredNamesAreDiagnosed() { + let input = + """ + protocol _p { + associatedtype _value + associatedtype __Value + } + + protocol ___Q { + } + + struct _data { + typealias _x = Int + } + + struct _Data {} + + actor _internalActor {} + + enum __e { + } + + enum _OtherE { + } + + func test() { + class _myClass {} + do { + class _MyClass {} + } + } + + distributed actor __greeter {} + distributed actor __InternalGreeter {} + """ + + performLint(TypeNamesShouldBeCapitalized.self, input: input) + + XCTAssertDiagnosed(.capitalizeTypeName(name: "_p")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "___Q")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "_value")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "__Value")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "_data")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "_Data")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "_x")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "_internalActor")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "__e")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "__OtherE")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "_myClass")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "_MyClass")) + XCTAssertDiagnosed(.capitalizeTypeName(name: "__greeter")) + XCTAssertNotDiagnosed(.capitalizeTypeName(name: "__InternalGreeter")) + } } From 94bec52b0dc27b50117a38ceceaa32fc9041cb73 Mon Sep 17 00:00:00 2001 From: Ziyang Huang Date: Sat, 12 Aug 2023 21:19:26 +0800 Subject: [PATCH 086/332] Make `shebang` a child of `SourceFileSyntax` --- Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift | 5 +++-- Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index c4f769c8f..bad5c8ee1 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -1519,6 +1519,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { appendFormatterIgnored(node: Syntax(node)) return .skipChildren } + after(node.shebang, tokens: .break(.same, newlines: .soft)) after(node.endOfFileToken, tokens: .break(.same, newlines: .soft)) return .visitChildren } @@ -2673,7 +2674,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { var verbatimText = "" for piece in trailingTrivia[...lastIndex] { switch piece { - case .shebang, .unexpectedText, .spaces, .tabs, .formfeeds, .verticalTabs: + case .unexpectedText, .spaces, .tabs, .formfeeds, .verticalTabs: piece.write(to: &verbatimText) default: // The implementation of the lexer today ensures that newlines, carriage returns, and @@ -3160,7 +3161,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } } - case .shebang(let text), .unexpectedText(let text): + case .unexpectedText(let text): // Garbage text in leading trivia might be something meaningful that would be disruptive to // throw away when formatting the file, like a hashbang line or Unicode byte-order marker at // the beginning of a file, or source control conflict markers. Keep it as verbatim text so diff --git a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift index 34d9f1566..25d169159 100644 --- a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift @@ -88,8 +88,7 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { private func hasNonWhitespaceTrivia(_ token: TokenSyntax, at position: TriviaPosition) -> Bool { for piece in position == .leading ? token.leadingTrivia : token.trailingTrivia { switch piece { - case .blockComment, .docBlockComment, .docLineComment, .unexpectedText, .lineComment, - .shebang: + case .blockComment, .docBlockComment, .docLineComment, .unexpectedText, .lineComment: return true default: break From 00eb59b20c69f6ed8912ed905798f91d8e27297a Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 14 Aug 2023 16:26:09 -0400 Subject: [PATCH 087/332] Don't warn about a redundant synthesized memberwise init if it has attributes. Fixes #591. --- .../UseSynthesizedInitializer.swift | 18 +++++++++++------- .../UseSynthesizedInitializerTests.swift | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift index d7bede3a1..097328bfb 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift @@ -46,17 +46,21 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { var extraneousInitializers = [InitializerDeclSyntax]() for initializer in initializers { guard + // Attributes signify intent that isn't automatically synthesized by the compiler. + initializer.attributes.isEmpty, matchesPropertyList( parameters: initializer.signature.parameterClause.parameters, - properties: storedProperties) - else { continue } - guard + properties: storedProperties), matchesAssignmentBody( variables: storedProperties, - initBody: initializer.body) - else { continue } - guard matchesAccessLevel(modifiers: initializer.modifiers, properties: storedProperties) - else { continue } + initBody: initializer.body), + matchesAccessLevel( + modifiers: initializer.modifiers, + properties: storedProperties) + else { + continue + } + extraneousInitializers.append(initializer) } diff --git a/Tests/SwiftFormatRulesTests/UseSynthesizedInitializerTests.swift b/Tests/SwiftFormatRulesTests/UseSynthesizedInitializerTests.swift index 3dbee0502..f10a72e7c 100644 --- a/Tests/SwiftFormatRulesTests/UseSynthesizedInitializerTests.swift +++ b/Tests/SwiftFormatRulesTests/UseSynthesizedInitializerTests.swift @@ -404,4 +404,22 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { XCTAssertDiagnosed(.removeRedundantInitializer, line: 15) XCTAssertDiagnosed(.removeRedundantInitializer, line: 25) } + + func testMemberwiseInitializerWithAttributeIsNotDiagnosed() { + let input = + """ + public struct Person { + let phoneNumber: String + let address: String + + @inlinable init(phoneNumber: String, address: String) { + self.address = address + self.phoneNumber = phoneNumber + } + } + """ + + performLint(UseSynthesizedInitializer.self, input: input) + XCTAssertNotDiagnosed(.removeRedundantInitializer) + } } From 4639fb1662f94eeae00a0e7fe03495c23fdbfdc3 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 15 Aug 2023 12:32:10 -0400 Subject: [PATCH 088/332] Consolidate tests. Move `Core`, `PrettyPrint`, `Rules`, and `WhitespaceLinter` tests into subdirectories of a new `SwiftFormatTests` target. (`WhitespaceLinter` is folded into the `PrettyPrint` subdirectory.) --- Package.swift | 39 ++----------------- .../Core}/DocumentationCommentTests.swift | 0 .../Core}/DocumentationCommentTextTests.swift | 0 .../Core}/RuleMaskTests.swift | 0 .../PrettyPrint}/AccessorTests.swift | 0 .../PrettyPrint}/ArrayDeclTests.swift | 0 .../PrettyPrint}/AsExprTests.swift | 0 .../PrettyPrint}/AssignmentExprTests.swift | 0 .../PrettyPrint}/AttributeTests.swift | 0 .../AvailabilityConditionTests.swift | 0 .../PrettyPrint}/AwaitExprTests.swift | 0 .../BackDeployAttributeTests.swift | 0 .../PrettyPrint}/BacktickTests.swift | 0 .../BinaryOperatorExprTests.swift | 0 .../PrettyPrint}/ClassDeclTests.swift | 0 .../PrettyPrint}/ClosureExprTests.swift | 0 .../PrettyPrint}/CommentTests.swift | 0 .../ConstrainedSugarTypeTests.swift | 0 .../PrettyPrint}/DeclNameArgumentTests.swift | 0 .../PrettyPrint}/DeinitializerDeclTests.swift | 0 .../PrettyPrint}/DictionaryDeclTests.swift | 0 .../DifferentiationAttributeTests.swift | 0 .../PrettyPrint}/DoStmtTests.swift | 0 .../PrettyPrint}/EnumDeclTests.swift | 0 .../PrettyPrint}/ExtensionDeclTests.swift | 0 .../PrettyPrint}/ForInStmtTests.swift | 0 .../PrettyPrint}/FunctionCallTests.swift | 0 .../PrettyPrint}/FunctionDeclTests.swift | 0 .../PrettyPrint}/FunctionTypeTests.swift | 0 .../PrettyPrint}/GarbageTextTests.swift | 0 .../PrettyPrint}/GuardStmtTests.swift | 0 .../PrettyPrint}/IfConfigTests.swift | 0 .../PrettyPrint}/IfStmtTests.swift | 0 .../PrettyPrint}/IgnoreNodeTests.swift | 0 .../PrettyPrint}/ImportTests.swift | 0 .../PrettyPrint}/InitializerDeclTests.swift | 0 .../PrettyPrint}/KeyPathExprTests.swift | 0 .../PrettyPrint}/MacroCallTests.swift | 0 .../PrettyPrint}/MacroDeclTests.swift | 0 .../PrettyPrint}/MemberAccessExprTests.swift | 0 .../MemberTypeIdentifierTests.swift | 0 .../PrettyPrint}/NewlineTests.swift | 0 .../PrettyPrint}/ObjectLiteralExprTests.swift | 0 .../PrettyPrint}/OperatorDeclTests.swift | 0 .../PrettyPrint}/ParenthesizedExprTests.swift | 0 .../PrettyPrint}/PatternBindingTests.swift | 0 .../PrettyPrint}/PrettyPrintTestCase.swift | 0 .../PrettyPrint}/ProtocolDeclTests.swift | 0 .../PrettyPrint}/RepeatStmtTests.swift | 0 .../RespectsExistingLineBreaksTests.swift | 0 .../PrettyPrint}/SemicolonTests.swift | 0 .../PrettyPrint}/StringTests.swift | 0 .../PrettyPrint}/StructDeclTests.swift | 0 .../PrettyPrint}/SubscriptDeclTests.swift | 0 .../PrettyPrint}/SubscriptExprTests.swift | 0 .../SwitchCaseIndentConfigTests.swift | 0 .../PrettyPrint}/SwitchStmtTests.swift | 0 .../PrettyPrint}/TernaryExprTests.swift | 0 .../PrettyPrint}/TryCatchTests.swift | 0 .../PrettyPrint}/TupleDeclTests.swift | 0 .../PrettyPrint}/TypeAliasTests.swift | 0 .../PrettyPrint}/VariableDeclTests.swift | 0 .../PrettyPrint}/WhileStmtTests.swift | 0 .../PrettyPrint}/WhitespaceLintTests.swift | 0 .../PrettyPrint}/WhitespaceTestCase.swift | 0 .../PrettyPrint}/YieldStmtTests.swift | 0 ...icDeclarationsHaveDocumentationTests.swift | 0 .../Rules}/AlwaysUseLowerCamelCaseTests.swift | 0 ...mbiguousTrailingClosureOverloadTests.swift | 0 ...tationCommentWithOneLineSummaryTests.swift | 0 .../Rules}/DoNotUseSemicolonsTests.swift | 0 ...ontRepeatTypeInStaticPropertiesTests.swift | 0 .../FileScopedDeclarationPrivacyTests.swift | 0 .../Rules}/FullyIndirectEnumTests.swift | 0 .../Rules}/GroupNumericLiteralsTests.swift | 0 .../Rules}/IdentifiersMustBeASCIITests.swift | 0 .../Rules}/ImportsXCTestVisitorTests.swift | 0 .../Rules}/LintOrFormatRuleTestCase.swift | 0 .../Rules}/NeverForceUnwrapTests.swift | 0 .../Rules}/NeverUseForceTryTests.swift | 0 ...UseImplicitlyUnwrappedOptionalsTests.swift | 0 ...cessLevelOnExtensionDeclarationTests.swift | 0 .../NoAssignmentInExpressionsTests.swift | 0 .../Rules}/NoBlockCommentsTests.swift | 0 .../NoCasesWithOnlyFallthroughTests.swift | 0 ...EmptyTrailingClosureParenthesesTests.swift | 0 .../Rules}/NoLabelsInCasePatternsTests.swift | 0 .../Rules}/NoLeadingUnderscoresTests.swift | 0 .../NoParensAroundConditionsTests.swift | 0 ...NoVoidReturnOnFunctionSignatureTests.swift | 0 .../Rules}/OneCasePerLineTests.swift | 0 .../OneVariableDeclarationPerLineTests.swift | 0 .../OnlyOneTrailingClosureArgumentTests.swift | 0 .../Rules}/OrderedImportsTests.swift | 0 .../ReturnVoidInsteadOfEmptyTupleTests.swift | 0 .../TypeNamesShouldBeCapitalizedTests.swift | 0 .../Rules}/UseEarlyExitsTests.swift | 0 .../UseLetInEveryBoundCaseVariableTests.swift | 0 .../Rules}/UseShorthandTypeNamesTests.swift | 0 .../UseSingleLinePropertyGetterTests.swift | 0 .../UseSynthesizedInitializerTests.swift | 0 ...leSlashForDocumentationCommentsTests.swift | 0 .../UseWhereClausesInForLoopsTests.swift | 0 .../ValidateDocumentationCommentsTests.swift | 0 104 files changed, 4 insertions(+), 35 deletions(-) rename Tests/{SwiftFormatCoreTests => SwiftFormatTests/Core}/DocumentationCommentTests.swift (100%) rename Tests/{SwiftFormatCoreTests => SwiftFormatTests/Core}/DocumentationCommentTextTests.swift (100%) rename Tests/{SwiftFormatCoreTests => SwiftFormatTests/Core}/RuleMaskTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/AccessorTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/ArrayDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/AsExprTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/AssignmentExprTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/AttributeTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/AvailabilityConditionTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/AwaitExprTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/BackDeployAttributeTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/BacktickTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/BinaryOperatorExprTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/ClassDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/ClosureExprTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/CommentTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/ConstrainedSugarTypeTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/DeclNameArgumentTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/DeinitializerDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/DictionaryDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/DifferentiationAttributeTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/DoStmtTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/EnumDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/ExtensionDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/ForInStmtTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/FunctionCallTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/FunctionDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/FunctionTypeTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/GarbageTextTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/GuardStmtTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/IfConfigTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/IfStmtTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/IgnoreNodeTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/ImportTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/InitializerDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/KeyPathExprTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/MacroCallTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/MacroDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/MemberAccessExprTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/MemberTypeIdentifierTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/NewlineTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/ObjectLiteralExprTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/OperatorDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/ParenthesizedExprTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/PatternBindingTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/PrettyPrintTestCase.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/ProtocolDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/RepeatStmtTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/RespectsExistingLineBreaksTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/SemicolonTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/StringTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/StructDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/SubscriptDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/SubscriptExprTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/SwitchCaseIndentConfigTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/SwitchStmtTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/TernaryExprTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/TryCatchTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/TupleDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/TypeAliasTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/VariableDeclTests.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/WhileStmtTests.swift (100%) rename Tests/{SwiftFormatWhitespaceLinterTests => SwiftFormatTests/PrettyPrint}/WhitespaceLintTests.swift (100%) rename Tests/{SwiftFormatWhitespaceLinterTests => SwiftFormatTests/PrettyPrint}/WhitespaceTestCase.swift (100%) rename Tests/{SwiftFormatPrettyPrintTests => SwiftFormatTests/PrettyPrint}/YieldStmtTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/AllPublicDeclarationsHaveDocumentationTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/AlwaysUseLowerCamelCaseTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/AmbiguousTrailingClosureOverloadTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/BeginDocumentationCommentWithOneLineSummaryTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/DoNotUseSemicolonsTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/DontRepeatTypeInStaticPropertiesTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/FileScopedDeclarationPrivacyTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/FullyIndirectEnumTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/GroupNumericLiteralsTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/IdentifiersMustBeASCIITests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/ImportsXCTestVisitorTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/LintOrFormatRuleTestCase.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NeverForceUnwrapTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NeverUseForceTryTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NeverUseImplicitlyUnwrappedOptionalsTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NoAccessLevelOnExtensionDeclarationTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NoAssignmentInExpressionsTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NoBlockCommentsTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NoCasesWithOnlyFallthroughTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NoEmptyTrailingClosureParenthesesTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NoLabelsInCasePatternsTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NoLeadingUnderscoresTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NoParensAroundConditionsTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/NoVoidReturnOnFunctionSignatureTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/OneCasePerLineTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/OneVariableDeclarationPerLineTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/OnlyOneTrailingClosureArgumentTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/OrderedImportsTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/ReturnVoidInsteadOfEmptyTupleTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/TypeNamesShouldBeCapitalizedTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/UseEarlyExitsTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/UseLetInEveryBoundCaseVariableTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/UseShorthandTypeNamesTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/UseSingleLinePropertyGetterTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/UseSynthesizedInitializerTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/UseTripleSlashForDocumentationCommentsTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/UseWhereClausesInForLoopsTests.swift (100%) rename Tests/{SwiftFormatRulesTests => SwiftFormatTests/Rules}/ValidateDocumentationCommentsTests.swift (100%) diff --git a/Package.swift b/Package.swift index d4023c8ce..8d50b0646 100644 --- a/Package.swift +++ b/Package.swift @@ -155,17 +155,6 @@ let package = Package( name: "SwiftFormatConfigurationTests", dependencies: ["SwiftFormatConfiguration"] ), - .testTarget( - name: "SwiftFormatCoreTests", - dependencies: [ - "SwiftFormatConfiguration", - "SwiftFormatCore", - .product(name: "Markdown", package: "swift-markdown"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - ] - ), .testTarget( name: "SwiftFormatPerformanceTests", dependencies: [ @@ -176,39 +165,19 @@ let package = Package( ] ), .testTarget( - name: "SwiftFormatPrettyPrintTests", + name: "SwiftFormatTests", dependencies: [ "SwiftFormatConfiguration", "SwiftFormatCore", "SwiftFormatPrettyPrint", "SwiftFormatRules", "SwiftFormatTestSupport", - .product(name: "SwiftSyntax", package: "swift-syntax"), + "SwiftFormatWhitespaceLinter", + .product(name: "Markdown", package: "swift-markdown"), .product(name: "SwiftOperators", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), - ] - ), - .testTarget( - name: "SwiftFormatRulesTests", - dependencies: [ - "SwiftFormatConfiguration", - "SwiftFormatCore", - "SwiftFormatPrettyPrint", - "SwiftFormatRules", - "SwiftFormatTestSupport", - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - ] - ), - .testTarget( - name: "SwiftFormatWhitespaceLinterTests", - dependencies: [ - "SwiftFormatConfiguration", - "SwiftFormatCore", - "SwiftFormatTestSupport", - "SwiftFormatWhitespaceLinter", .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ] ), ] diff --git a/Tests/SwiftFormatCoreTests/DocumentationCommentTests.swift b/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift similarity index 100% rename from Tests/SwiftFormatCoreTests/DocumentationCommentTests.swift rename to Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift diff --git a/Tests/SwiftFormatCoreTests/DocumentationCommentTextTests.swift b/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift similarity index 100% rename from Tests/SwiftFormatCoreTests/DocumentationCommentTextTests.swift rename to Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift diff --git a/Tests/SwiftFormatCoreTests/RuleMaskTests.swift b/Tests/SwiftFormatTests/Core/RuleMaskTests.swift similarity index 100% rename from Tests/SwiftFormatCoreTests/RuleMaskTests.swift rename to Tests/SwiftFormatTests/Core/RuleMaskTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/AccessorTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AccessorTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/AccessorTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/AccessorTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/ArrayDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/ArrayDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/AsExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AsExprTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/AsExprTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/AsExprTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/AssignmentExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AssignmentExprTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/AssignmentExprTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/AssignmentExprTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/AttributeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/AttributeTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/AvailabilityConditionTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AvailabilityConditionTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/AvailabilityConditionTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/AvailabilityConditionTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/AwaitExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AwaitExprTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/AwaitExprTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/AwaitExprTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/BackDeployAttributeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/BackDeployAttributeTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/BackDeployAttributeTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/BackDeployAttributeTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/BacktickTests.swift b/Tests/SwiftFormatTests/PrettyPrint/BacktickTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/BacktickTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/BacktickTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/BinaryOperatorExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/BinaryOperatorExprTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/BinaryOperatorExprTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/BinaryOperatorExprTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/ClassDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ClassDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/ClassDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/ClassDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/ClosureExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ClosureExprTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/ClosureExprTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/ClosureExprTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/CommentTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/CommentTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/ConstrainedSugarTypeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ConstrainedSugarTypeTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/ConstrainedSugarTypeTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/ConstrainedSugarTypeTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/DeclNameArgumentTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DeclNameArgumentTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/DeclNameArgumentTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/DeclNameArgumentTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/DeinitializerDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DeinitializerDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/DeinitializerDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/DeinitializerDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/DictionaryDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/DictionaryDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/DifferentiationAttributeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DifferentiationAttributeTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/DifferentiationAttributeTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/DifferentiationAttributeTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/DoStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/DoStmtTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/EnumDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/EnumDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/ExtensionDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ExtensionDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/ExtensionDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/ExtensionDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/ForInStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ForInStmtTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/ForInStmtTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/ForInStmtTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/FunctionCallTests.swift b/Tests/SwiftFormatTests/PrettyPrint/FunctionCallTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/FunctionCallTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/FunctionCallTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/FunctionDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/FunctionDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/FunctionDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/FunctionDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/FunctionTypeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/FunctionTypeTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/FunctionTypeTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/FunctionTypeTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/GarbageTextTests.swift b/Tests/SwiftFormatTests/PrettyPrint/GarbageTextTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/GarbageTextTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/GarbageTextTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/GuardStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/GuardStmtTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/GuardStmtTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/GuardStmtTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/IfConfigTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IfStmtTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/IfStmtTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/IfStmtTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/IgnoreNodeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/IgnoreNodeTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/ImportTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ImportTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/ImportTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/ImportTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/InitializerDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/InitializerDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/InitializerDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/InitializerDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/KeyPathExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/KeyPathExprTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/KeyPathExprTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/KeyPathExprTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/MacroCallTests.swift b/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/MacroCallTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/MacroDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/MacroDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/MacroDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/MacroDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/MemberAccessExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/MemberAccessExprTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/MemberAccessExprTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/MemberAccessExprTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/MemberTypeIdentifierTests.swift b/Tests/SwiftFormatTests/PrettyPrint/MemberTypeIdentifierTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/MemberTypeIdentifierTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/MemberTypeIdentifierTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/NewlineTests.swift b/Tests/SwiftFormatTests/PrettyPrint/NewlineTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/NewlineTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/NewlineTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/ObjectLiteralExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ObjectLiteralExprTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/ObjectLiteralExprTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/ObjectLiteralExprTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/OperatorDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/OperatorDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/OperatorDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/OperatorDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/ParenthesizedExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ParenthesizedExprTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/ParenthesizedExprTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/ParenthesizedExprTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/PatternBindingTests.swift b/Tests/SwiftFormatTests/PrettyPrint/PatternBindingTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/PatternBindingTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/PatternBindingTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/PrettyPrintTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/PrettyPrintTestCase.swift rename to Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/ProtocolDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ProtocolDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/ProtocolDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/ProtocolDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/RepeatStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/RepeatStmtTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/RepeatStmtTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/RepeatStmtTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/RespectsExistingLineBreaksTests.swift b/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/RespectsExistingLineBreaksTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/SemicolonTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SemicolonTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/SemicolonTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/SemicolonTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift b/Tests/SwiftFormatTests/PrettyPrint/StringTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/StringTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/StringTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/StructDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/StructDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/StructDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/StructDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/SubscriptDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SubscriptDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/SubscriptDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/SubscriptDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/SubscriptExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SubscriptExprTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/SubscriptExprTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/SubscriptExprTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/SwitchCaseIndentConfigTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SwitchCaseIndentConfigTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/SwitchCaseIndentConfigTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/SwitchCaseIndentConfigTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SwitchStmtTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/SwitchStmtTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/SwitchStmtTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/TernaryExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/TernaryExprTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/TernaryExprTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/TernaryExprTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/TryCatchTests.swift b/Tests/SwiftFormatTests/PrettyPrint/TryCatchTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/TryCatchTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/TryCatchTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/TupleDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/TupleDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/TupleDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/TupleDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/TypeAliasTests.swift b/Tests/SwiftFormatTests/PrettyPrint/TypeAliasTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/TypeAliasTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/TypeAliasTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/VariableDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/VariableDeclTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/VariableDeclTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/VariableDeclTests.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/WhileStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/WhileStmtTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/WhileStmtTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/WhileStmtTests.swift diff --git a/Tests/SwiftFormatWhitespaceLinterTests/WhitespaceLintTests.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift similarity index 100% rename from Tests/SwiftFormatWhitespaceLinterTests/WhitespaceLintTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift diff --git a/Tests/SwiftFormatWhitespaceLinterTests/WhitespaceTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift similarity index 100% rename from Tests/SwiftFormatWhitespaceLinterTests/WhitespaceTestCase.swift rename to Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift diff --git a/Tests/SwiftFormatPrettyPrintTests/YieldStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/YieldStmtTests.swift similarity index 100% rename from Tests/SwiftFormatPrettyPrintTests/YieldStmtTests.swift rename to Tests/SwiftFormatTests/PrettyPrint/YieldStmtTests.swift diff --git a/Tests/SwiftFormatRulesTests/AllPublicDeclarationsHaveDocumentationTests.swift b/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/AllPublicDeclarationsHaveDocumentationTests.swift rename to Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift diff --git a/Tests/SwiftFormatRulesTests/AlwaysUseLowerCamelCaseTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/AlwaysUseLowerCamelCaseTests.swift rename to Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift diff --git a/Tests/SwiftFormatRulesTests/AmbiguousTrailingClosureOverloadTests.swift b/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/AmbiguousTrailingClosureOverloadTests.swift rename to Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift diff --git a/Tests/SwiftFormatRulesTests/BeginDocumentationCommentWithOneLineSummaryTests.swift b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/BeginDocumentationCommentWithOneLineSummaryTests.swift rename to Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift diff --git a/Tests/SwiftFormatRulesTests/DoNotUseSemicolonsTests.swift b/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/DoNotUseSemicolonsTests.swift rename to Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift diff --git a/Tests/SwiftFormatRulesTests/DontRepeatTypeInStaticPropertiesTests.swift b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/DontRepeatTypeInStaticPropertiesTests.swift rename to Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift diff --git a/Tests/SwiftFormatRulesTests/FileScopedDeclarationPrivacyTests.swift b/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/FileScopedDeclarationPrivacyTests.swift rename to Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift diff --git a/Tests/SwiftFormatRulesTests/FullyIndirectEnumTests.swift b/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/FullyIndirectEnumTests.swift rename to Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift diff --git a/Tests/SwiftFormatRulesTests/GroupNumericLiteralsTests.swift b/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/GroupNumericLiteralsTests.swift rename to Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift diff --git a/Tests/SwiftFormatRulesTests/IdentifiersMustBeASCIITests.swift b/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/IdentifiersMustBeASCIITests.swift rename to Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift diff --git a/Tests/SwiftFormatRulesTests/ImportsXCTestVisitorTests.swift b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/ImportsXCTestVisitorTests.swift rename to Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift diff --git a/Tests/SwiftFormatRulesTests/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/LintOrFormatRuleTestCase.swift rename to Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift diff --git a/Tests/SwiftFormatRulesTests/NeverForceUnwrapTests.swift b/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NeverForceUnwrapTests.swift rename to Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift diff --git a/Tests/SwiftFormatRulesTests/NeverUseForceTryTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NeverUseForceTryTests.swift rename to Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift diff --git a/Tests/SwiftFormatRulesTests/NeverUseImplicitlyUnwrappedOptionalsTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NeverUseImplicitlyUnwrappedOptionalsTests.swift rename to Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift diff --git a/Tests/SwiftFormatRulesTests/NoAccessLevelOnExtensionDeclarationTests.swift b/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NoAccessLevelOnExtensionDeclarationTests.swift rename to Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift diff --git a/Tests/SwiftFormatRulesTests/NoAssignmentInExpressionsTests.swift b/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NoAssignmentInExpressionsTests.swift rename to Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift diff --git a/Tests/SwiftFormatRulesTests/NoBlockCommentsTests.swift b/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NoBlockCommentsTests.swift rename to Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift diff --git a/Tests/SwiftFormatRulesTests/NoCasesWithOnlyFallthroughTests.swift b/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NoCasesWithOnlyFallthroughTests.swift rename to Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift diff --git a/Tests/SwiftFormatRulesTests/NoEmptyTrailingClosureParenthesesTests.swift b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NoEmptyTrailingClosureParenthesesTests.swift rename to Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift diff --git a/Tests/SwiftFormatRulesTests/NoLabelsInCasePatternsTests.swift b/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NoLabelsInCasePatternsTests.swift rename to Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift diff --git a/Tests/SwiftFormatRulesTests/NoLeadingUnderscoresTests.swift b/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NoLeadingUnderscoresTests.swift rename to Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift diff --git a/Tests/SwiftFormatRulesTests/NoParensAroundConditionsTests.swift b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NoParensAroundConditionsTests.swift rename to Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift diff --git a/Tests/SwiftFormatRulesTests/NoVoidReturnOnFunctionSignatureTests.swift b/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/NoVoidReturnOnFunctionSignatureTests.swift rename to Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift diff --git a/Tests/SwiftFormatRulesTests/OneCasePerLineTests.swift b/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/OneCasePerLineTests.swift rename to Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift diff --git a/Tests/SwiftFormatRulesTests/OneVariableDeclarationPerLineTests.swift b/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/OneVariableDeclarationPerLineTests.swift rename to Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift diff --git a/Tests/SwiftFormatRulesTests/OnlyOneTrailingClosureArgumentTests.swift b/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/OnlyOneTrailingClosureArgumentTests.swift rename to Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift diff --git a/Tests/SwiftFormatRulesTests/OrderedImportsTests.swift b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/OrderedImportsTests.swift rename to Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift diff --git a/Tests/SwiftFormatRulesTests/ReturnVoidInsteadOfEmptyTupleTests.swift b/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/ReturnVoidInsteadOfEmptyTupleTests.swift rename to Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift diff --git a/Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift b/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/TypeNamesShouldBeCapitalizedTests.swift rename to Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift diff --git a/Tests/SwiftFormatRulesTests/UseEarlyExitsTests.swift b/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/UseEarlyExitsTests.swift rename to Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift diff --git a/Tests/SwiftFormatRulesTests/UseLetInEveryBoundCaseVariableTests.swift b/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/UseLetInEveryBoundCaseVariableTests.swift rename to Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift diff --git a/Tests/SwiftFormatRulesTests/UseShorthandTypeNamesTests.swift b/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/UseShorthandTypeNamesTests.swift rename to Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift diff --git a/Tests/SwiftFormatRulesTests/UseSingleLinePropertyGetterTests.swift b/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/UseSingleLinePropertyGetterTests.swift rename to Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift diff --git a/Tests/SwiftFormatRulesTests/UseSynthesizedInitializerTests.swift b/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/UseSynthesizedInitializerTests.swift rename to Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift diff --git a/Tests/SwiftFormatRulesTests/UseTripleSlashForDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/UseTripleSlashForDocumentationCommentsTests.swift rename to Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift diff --git a/Tests/SwiftFormatRulesTests/UseWhereClausesInForLoopsTests.swift b/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/UseWhereClausesInForLoopsTests.swift rename to Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift diff --git a/Tests/SwiftFormatRulesTests/ValidateDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift similarity index 100% rename from Tests/SwiftFormatRulesTests/ValidateDocumentationCommentsTests.swift rename to Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift From 6238ab59e9b31a9d48567b9d9c773eff4e122ece Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 15 Aug 2023 12:36:23 -0400 Subject: [PATCH 089/332] Rename test support module to `_SwiftFormatTestSupport`. This indicates that it should not be used by users. (In the future, it should use `package` access-level declarations.) --- Package.swift | 16 ++++++++-------- .../Configuration+Testing.swift | 0 .../DiagnosingTestCase.swift | 0 .../TestingFindingConsumer.swift | 0 .../WhitespaceLinterPerformanceTests.swift | 2 +- .../PrettyPrint/PrettyPrintTestCase.swift | 2 +- .../PrettyPrint/WhitespaceTestCase.swift | 2 +- .../Rules/ImportsXCTestVisitorTests.swift | 2 +- .../Rules/LintOrFormatRuleTestCase.swift | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) rename Sources/{SwiftFormatTestSupport => _SwiftFormatTestSupport}/Configuration+Testing.swift (100%) rename Sources/{SwiftFormatTestSupport => _SwiftFormatTestSupport}/DiagnosingTestCase.swift (100%) rename Sources/{SwiftFormatTestSupport => _SwiftFormatTestSupport}/TestingFindingConsumer.swift (100%) diff --git a/Package.swift b/Package.swift index 8d50b0646..ecc93810d 100644 --- a/Package.swift +++ b/Package.swift @@ -89,19 +89,19 @@ let package = Package( ] ), .target( - name: "SwiftFormatTestSupport", + name: "SwiftFormatWhitespaceLinter", dependencies: [ "SwiftFormatCore", - "SwiftFormatRules", - "SwiftFormatConfiguration", - .product(name: "SwiftOperators", package: "swift-syntax"), + .product(name: "SwiftSyntax", package: "swift-syntax"), ] ), .target( - name: "SwiftFormatWhitespaceLinter", + name: "_SwiftFormatTestSupport", dependencies: [ "SwiftFormatCore", - .product(name: "SwiftSyntax", package: "swift-syntax"), + "SwiftFormatRules", + "SwiftFormatConfiguration", + .product(name: "SwiftOperators", package: "swift-syntax"), ] ), .plugin( @@ -158,8 +158,8 @@ let package = Package( .testTarget( name: "SwiftFormatPerformanceTests", dependencies: [ - "SwiftFormatTestSupport", "SwiftFormatWhitespaceLinter", + "_SwiftFormatTestSupport", .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), ] @@ -171,8 +171,8 @@ let package = Package( "SwiftFormatCore", "SwiftFormatPrettyPrint", "SwiftFormatRules", - "SwiftFormatTestSupport", "SwiftFormatWhitespaceLinter", + "_SwiftFormatTestSupport", .product(name: "Markdown", package: "swift-markdown"), .product(name: "SwiftOperators", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), diff --git a/Sources/SwiftFormatTestSupport/Configuration+Testing.swift b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift similarity index 100% rename from Sources/SwiftFormatTestSupport/Configuration+Testing.swift rename to Sources/_SwiftFormatTestSupport/Configuration+Testing.swift diff --git a/Sources/SwiftFormatTestSupport/DiagnosingTestCase.swift b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift similarity index 100% rename from Sources/SwiftFormatTestSupport/DiagnosingTestCase.swift rename to Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift diff --git a/Sources/SwiftFormatTestSupport/TestingFindingConsumer.swift b/Sources/_SwiftFormatTestSupport/TestingFindingConsumer.swift similarity index 100% rename from Sources/SwiftFormatTestSupport/TestingFindingConsumer.swift rename to Sources/_SwiftFormatTestSupport/TestingFindingConsumer.swift diff --git a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift index 7ca3ecde8..e6c18f797 100644 --- a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift +++ b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift @@ -1,8 +1,8 @@ -import SwiftFormatTestSupport import SwiftFormatWhitespaceLinter import SwiftSyntax import SwiftParser import XCTest +import _SwiftFormatTestSupport final class WhitespaceLinterPerformanceTests: DiagnosingTestCase { func testWhitespaceLinterPerformance() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift index 0328b6f82..1e3c691b9 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift @@ -1,11 +1,11 @@ import SwiftFormatConfiguration import SwiftFormatCore import SwiftFormatPrettyPrint -import SwiftFormatTestSupport import SwiftOperators import SwiftSyntax import SwiftParser import XCTest +import _SwiftFormatTestSupport class PrettyPrintTestCase: DiagnosingTestCase { /// Asserts that the input string, when pretty printed, is equal to the expected string. diff --git a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift index 899eade3e..c84e7098b 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift @@ -1,10 +1,10 @@ import SwiftFormatConfiguration import SwiftFormatCore -import SwiftFormatTestSupport import SwiftFormatWhitespaceLinter import SwiftSyntax import SwiftParser import XCTest +import _SwiftFormatTestSupport class WhitespaceTestCase: DiagnosingTestCase { override func setUp() { diff --git a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift index b133678ea..3b06da18e 100644 --- a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift +++ b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift @@ -1,8 +1,8 @@ import SwiftFormatCore import SwiftFormatRules -import SwiftFormatTestSupport import SwiftParser import XCTest +import _SwiftFormatTestSupport class ImportsXCTestVisitorTests: DiagnosingTestCase { func testDoesNotImportXCTest() throws { diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index 81e3d8167..60bdaa293 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -1,10 +1,10 @@ import SwiftFormatConfiguration import SwiftFormatCore -import SwiftFormatTestSupport import SwiftOperators import SwiftParser import SwiftSyntax import XCTest +import _SwiftFormatTestSupport class LintOrFormatRuleTestCase: DiagnosingTestCase { /// Performs a lint using the provided linter rule on the provided input. From 7c53d2cc44e02e6c3cfef8572cde4ea7f87a230b Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 15 Aug 2023 12:55:41 -0400 Subject: [PATCH 090/332] Partition public (API) types from internal (Core) types in `SwiftFormat` module. --- Sources/SwiftFormat/{ => API}/DebugOptions.swift | 0 Sources/SwiftFormat/{ => API}/SwiftFormatError.swift | 0 Sources/SwiftFormat/{ => API}/SwiftFormatter.swift | 0 Sources/SwiftFormat/{ => API}/SwiftLinter.swift | 0 Sources/SwiftFormat/{ => Core}/Exports.swift | 0 Sources/SwiftFormat/{ => Core}/FormatPipeline.swift | 0 Sources/SwiftFormat/{ => Core}/LintPipeline.swift | 0 Sources/SwiftFormat/{ => Core}/Parsing.swift | 0 Sources/SwiftFormat/{ => Core}/Pipelines+Generated.swift | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename Sources/SwiftFormat/{ => API}/DebugOptions.swift (100%) rename Sources/SwiftFormat/{ => API}/SwiftFormatError.swift (100%) rename Sources/SwiftFormat/{ => API}/SwiftFormatter.swift (100%) rename Sources/SwiftFormat/{ => API}/SwiftLinter.swift (100%) rename Sources/SwiftFormat/{ => Core}/Exports.swift (100%) rename Sources/SwiftFormat/{ => Core}/FormatPipeline.swift (100%) rename Sources/SwiftFormat/{ => Core}/LintPipeline.swift (100%) rename Sources/SwiftFormat/{ => Core}/Parsing.swift (100%) rename Sources/SwiftFormat/{ => Core}/Pipelines+Generated.swift (100%) diff --git a/Sources/SwiftFormat/DebugOptions.swift b/Sources/SwiftFormat/API/DebugOptions.swift similarity index 100% rename from Sources/SwiftFormat/DebugOptions.swift rename to Sources/SwiftFormat/API/DebugOptions.swift diff --git a/Sources/SwiftFormat/SwiftFormatError.swift b/Sources/SwiftFormat/API/SwiftFormatError.swift similarity index 100% rename from Sources/SwiftFormat/SwiftFormatError.swift rename to Sources/SwiftFormat/API/SwiftFormatError.swift diff --git a/Sources/SwiftFormat/SwiftFormatter.swift b/Sources/SwiftFormat/API/SwiftFormatter.swift similarity index 100% rename from Sources/SwiftFormat/SwiftFormatter.swift rename to Sources/SwiftFormat/API/SwiftFormatter.swift diff --git a/Sources/SwiftFormat/SwiftLinter.swift b/Sources/SwiftFormat/API/SwiftLinter.swift similarity index 100% rename from Sources/SwiftFormat/SwiftLinter.swift rename to Sources/SwiftFormat/API/SwiftLinter.swift diff --git a/Sources/SwiftFormat/Exports.swift b/Sources/SwiftFormat/Core/Exports.swift similarity index 100% rename from Sources/SwiftFormat/Exports.swift rename to Sources/SwiftFormat/Core/Exports.swift diff --git a/Sources/SwiftFormat/FormatPipeline.swift b/Sources/SwiftFormat/Core/FormatPipeline.swift similarity index 100% rename from Sources/SwiftFormat/FormatPipeline.swift rename to Sources/SwiftFormat/Core/FormatPipeline.swift diff --git a/Sources/SwiftFormat/LintPipeline.swift b/Sources/SwiftFormat/Core/LintPipeline.swift similarity index 100% rename from Sources/SwiftFormat/LintPipeline.swift rename to Sources/SwiftFormat/Core/LintPipeline.swift diff --git a/Sources/SwiftFormat/Parsing.swift b/Sources/SwiftFormat/Core/Parsing.swift similarity index 100% rename from Sources/SwiftFormat/Parsing.swift rename to Sources/SwiftFormat/Core/Parsing.swift diff --git a/Sources/SwiftFormat/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift similarity index 100% rename from Sources/SwiftFormat/Pipelines+Generated.swift rename to Sources/SwiftFormat/Core/Pipelines+Generated.swift From 65e19dda81e3f6a462ac9fef5b5d1ff6bd63662c Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 15 Aug 2023 13:41:47 -0400 Subject: [PATCH 091/332] Consolidate `SwiftFormat{Core,PrettyPrint,Rules,WhitespaceLinter}` modules into `SwiftFormat`. This only moves files and updated imports; access level changes and SPIs will be introduced later. --- Package.swift | 51 ++----------------- Sources/SwiftFormat/API/SwiftFormatter.swift | 3 -- Sources/SwiftFormat/API/SwiftLinter.swift | 4 -- .../Core}/AddModifierRewriter.swift | 1 - .../Core}/Context.swift | 0 .../Core}/DocumentationComment.swift | 0 .../Core}/DocumentationCommentText.swift | 0 Sources/SwiftFormat/Core/Exports.swift | 20 -------- .../Core}/Finding+Convenience.swift | 0 .../Core}/Finding.swift | 0 .../Core}/FindingCategorizing.swift | 0 .../Core}/FindingEmitter.swift | 0 Sources/SwiftFormat/Core/FormatPipeline.swift | 2 - .../FunctionDeclSyntax+Convenience.swift | 0 .../Core}/ImportsXCTestVisitor.swift | 1 - .../Core}/LazySplitSequence.swift | 0 .../Core}/LegacyTriviaBehavior.swift | 0 Sources/SwiftFormat/Core/LintPipeline.swift | 1 - .../ModifierListSyntax+Convenience.swift | 0 Sources/SwiftFormat/Core/Parsing.swift | 1 - .../Core/Pipelines+Generated.swift | 2 - .../Core}/RememberingIterator.swift | 0 .../Core}/Rule.swift | 0 .../Core}/RuleBasedFindingCategory.swift | 0 .../Core}/RuleMask.swift | 0 .../Core}/RuleNameCache+Generated.swift | 0 .../Core}/RuleState.swift | 0 .../Core}/SemicolonSyntaxProtocol.swift | 0 .../Core}/SyntaxFormatRule.swift | 0 .../Core}/SyntaxLintRule.swift | 0 .../Core}/SyntaxProtocol+Convenience.swift | 0 .../Core}/TokenSyntax+Convenience.swift | 0 .../Core}/Trivia+Convenience.swift | 0 .../Core}/VarDeclSyntax+Convenience.swift | 0 .../PrettyPrint}/Comment.swift | 0 .../PrettyPrint}/Indent+Length.swift | 0 .../PrettyPrint}/PrettyPrint.swift | 1 - .../PrettyPrintFindingCategory.swift | 2 - .../PrettyPrint}/Token.swift | 0 .../PrettyPrint}/TokenStreamCreator.swift | 1 - .../PrettyPrint}/Verbatim.swift | 0 .../WhitespaceFindingCategory.swift | 2 - .../PrettyPrint}/WhitespaceLinter.swift | 1 - ...lPublicDeclarationsHaveDocumentation.swift | 1 - .../Rules}/AlwaysUseLowerCamelCase.swift | 1 - .../AmbiguousTrailingClosureOverload.swift | 1 - ...cumentationCommentWithOneLineSummary.swift | 1 - .../Rules}/DoNotUseSemicolons.swift | 1 - .../DontRepeatTypeInStaticProperties.swift | 1 - .../Rules}/FileScopedDeclarationPrivacy.swift | 1 - .../Rules}/FullyIndirectEnum.swift | 1 - .../Rules}/GroupNumericLiterals.swift | 1 - .../Rules}/IdentifiersMustBeASCII.swift | 1 - .../Rules}/NeverForceUnwrap.swift | 1 - .../Rules}/NeverUseForceTry.swift | 1 - ...NeverUseImplicitlyUnwrappedOptionals.swift | 1 - .../NoAccessLevelOnExtensionDeclaration.swift | 1 - .../Rules}/NoAssignmentInExpressions.swift | 1 - .../Rules}/NoBlockComments.swift | 1 - .../Rules}/NoCasesWithOnlyFallthrough.swift | 1 - .../NoEmptyTrailingClosureParentheses.swift | 1 - .../Rules}/NoLabelsInCasePatterns.swift | 1 - .../Rules}/NoLeadingUnderscores.swift | 1 - .../Rules}/NoParensAroundConditions.swift | 1 - .../Rules}/NoPlaygroundLiterals.swift | 1 - .../NoVoidReturnOnFunctionSignature.swift | 1 - .../Rules}/OneCasePerLine.swift | 1 - .../OneVariableDeclarationPerLine.swift | 1 - .../OnlyOneTrailingClosureArgument.swift | 1 - .../Rules}/OrderedImports.swift | 1 - .../ReturnVoidInsteadOfEmptyTuple.swift | 1 - .../Rules}/TypeNamesShouldBeCapitalized.swift | 1 - .../Rules}/UseEarlyExits.swift | 1 - .../UseLetInEveryBoundCaseVariable.swift | 1 - .../Rules}/UseShorthandTypeNames.swift | 1 - .../Rules}/UseSingleLinePropertyGetter.swift | 1 - .../Rules}/UseSynthesizedInitializer.swift | 1 - ...eTripleSlashForDocumentationComments.swift | 1 - .../Rules}/UseWhereClausesInForLoops.swift | 1 - .../ValidateDocumentationComments.swift | 1 - .../DiagnosingTestCase.swift | 3 +- .../TestingFindingConsumer.swift | 2 +- Sources/generate-pipeline/RuleCollector.swift | 2 +- .../swift-format/Utilities/Diagnostic.swift | 2 +- .../Utilities/DiagnosticsEngine.swift | 2 +- .../WhitespaceLinterPerformanceTests.swift | 2 +- .../Core/DocumentationCommentTests.swift | 2 +- .../Core/DocumentationCommentTextTests.swift | 2 +- .../SwiftFormatTests/Core/RuleMaskTests.swift | 2 +- .../PrettyPrint/ArrayDeclTests.swift | 2 +- .../PrettyPrint/DictionaryDeclTests.swift | 2 +- .../PrettyPrint/PrettyPrintTestCase.swift | 3 +- .../PrettyPrint/WhitespaceLintTests.swift | 2 +- .../PrettyPrint/WhitespaceTestCase.swift | 3 +- ...icDeclarationsHaveDocumentationTests.swift | 2 +- .../Rules/AlwaysUseLowerCamelCaseTests.swift | 2 +- ...mbiguousTrailingClosureOverloadTests.swift | 2 +- ...tationCommentWithOneLineSummaryTests.swift | 2 +- .../Rules/DoNotUseSemicolonsTests.swift | 2 +- ...ontRepeatTypeInStaticPropertiesTests.swift | 2 +- .../FileScopedDeclarationPrivacyTests.swift | 3 +- .../Rules/FullyIndirectEnumTests.swift | 2 +- .../Rules/GroupNumericLiteralsTests.swift | 2 +- .../Rules/IdentifiersMustBeASCIITests.swift | 2 +- .../Rules/ImportsXCTestVisitorTests.swift | 3 +- .../Rules/LintOrFormatRuleTestCase.swift | 2 +- .../Rules/NeverForceUnwrapTests.swift | 2 +- .../Rules/NeverUseForceTryTests.swift | 2 +- ...UseImplicitlyUnwrappedOptionalsTests.swift | 2 +- ...cessLevelOnExtensionDeclarationTests.swift | 2 +- .../NoAssignmentInExpressionsTests.swift | 2 +- .../Rules/NoBlockCommentsTests.swift | 2 +- .../NoCasesWithOnlyFallthroughTests.swift | 2 +- ...EmptyTrailingClosureParenthesesTests.swift | 2 +- .../Rules/NoLabelsInCasePatternsTests.swift | 2 +- .../Rules/NoLeadingUnderscoresTests.swift | 2 +- .../Rules/NoParensAroundConditionsTests.swift | 2 +- ...NoVoidReturnOnFunctionSignatureTests.swift | 2 +- .../Rules/OneCasePerLineTests.swift | 2 +- .../OneVariableDeclarationPerLineTests.swift | 2 +- .../OnlyOneTrailingClosureArgumentTests.swift | 2 +- .../Rules/OrderedImportsTests.swift | 10 ++-- .../ReturnVoidInsteadOfEmptyTupleTests.swift | 2 +- .../TypeNamesShouldBeCapitalizedTests.swift | 2 +- .../Rules/UseEarlyExitsTests.swift | 2 +- .../UseLetInEveryBoundCaseVariableTests.swift | 2 +- .../Rules/UseShorthandTypeNamesTests.swift | 2 +- .../UseSingleLinePropertyGetterTests.swift | 2 +- .../UseSynthesizedInitializerTests.swift | 2 +- ...leSlashForDocumentationCommentsTests.swift | 2 +- .../UseWhereClausesInForLoopsTests.swift | 2 +- .../ValidateDocumentationCommentsTests.swift | 2 +- 132 files changed, 61 insertions(+), 186 deletions(-) rename Sources/{SwiftFormatRules => SwiftFormat/Core}/AddModifierRewriter.swift (99%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/Context.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/DocumentationComment.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/DocumentationCommentText.swift (100%) delete mode 100644 Sources/SwiftFormat/Core/Exports.swift rename Sources/{SwiftFormatCore => SwiftFormat/Core}/Finding+Convenience.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/Finding.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/FindingCategorizing.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/FindingEmitter.swift (100%) rename Sources/{SwiftFormatRules => SwiftFormat/Core}/FunctionDeclSyntax+Convenience.swift (100%) rename Sources/{SwiftFormatRules => SwiftFormat/Core}/ImportsXCTestVisitor.swift (99%) rename Sources/{SwiftFormatWhitespaceLinter => SwiftFormat/Core}/LazySplitSequence.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/LegacyTriviaBehavior.swift (100%) rename Sources/{SwiftFormatRules => SwiftFormat/Core}/ModifierListSyntax+Convenience.swift (100%) rename Sources/{SwiftFormatWhitespaceLinter => SwiftFormat/Core}/RememberingIterator.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/Rule.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/RuleBasedFindingCategory.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/RuleMask.swift (100%) rename Sources/{SwiftFormatRules => SwiftFormat/Core}/RuleNameCache+Generated.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/RuleState.swift (100%) rename Sources/{SwiftFormatRules => SwiftFormat/Core}/SemicolonSyntaxProtocol.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/SyntaxFormatRule.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/SyntaxLintRule.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/SyntaxProtocol+Convenience.swift (100%) rename Sources/{SwiftFormatRules => SwiftFormat/Core}/TokenSyntax+Convenience.swift (100%) rename Sources/{SwiftFormatCore => SwiftFormat/Core}/Trivia+Convenience.swift (100%) rename Sources/{SwiftFormatRules => SwiftFormat/Core}/VarDeclSyntax+Convenience.swift (100%) rename Sources/{SwiftFormatPrettyPrint => SwiftFormat/PrettyPrint}/Comment.swift (100%) rename Sources/{SwiftFormatPrettyPrint => SwiftFormat/PrettyPrint}/Indent+Length.swift (100%) rename Sources/{SwiftFormatPrettyPrint => SwiftFormat/PrettyPrint}/PrettyPrint.swift (99%) rename Sources/{SwiftFormatPrettyPrint => SwiftFormat/PrettyPrint}/PrettyPrintFindingCategory.swift (97%) rename Sources/{SwiftFormatPrettyPrint => SwiftFormat/PrettyPrint}/Token.swift (100%) rename Sources/{SwiftFormatPrettyPrint => SwiftFormat/PrettyPrint}/TokenStreamCreator.swift (99%) rename Sources/{SwiftFormatPrettyPrint => SwiftFormat/PrettyPrint}/Verbatim.swift (100%) rename Sources/{SwiftFormatWhitespaceLinter => SwiftFormat/PrettyPrint}/WhitespaceFindingCategory.swift (98%) rename Sources/{SwiftFormatWhitespaceLinter => SwiftFormat/PrettyPrint}/WhitespaceLinter.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/AllPublicDeclarationsHaveDocumentation.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/AlwaysUseLowerCamelCase.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/AmbiguousTrailingClosureOverload.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/BeginDocumentationCommentWithOneLineSummary.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/DoNotUseSemicolons.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/DontRepeatTypeInStaticProperties.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/FileScopedDeclarationPrivacy.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/FullyIndirectEnum.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/GroupNumericLiterals.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/IdentifiersMustBeASCII.swift (98%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NeverForceUnwrap.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NeverUseForceTry.swift (98%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NeverUseImplicitlyUnwrappedOptionals.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NoAccessLevelOnExtensionDeclaration.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NoAssignmentInExpressions.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NoBlockComments.swift (98%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NoCasesWithOnlyFallthrough.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NoEmptyTrailingClosureParentheses.swift (98%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NoLabelsInCasePatterns.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NoLeadingUnderscores.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NoParensAroundConditions.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NoPlaygroundLiterals.swift (98%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/NoVoidReturnOnFunctionSignature.swift (98%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/OneCasePerLine.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/OneVariableDeclarationPerLine.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/OnlyOneTrailingClosureArgument.swift (98%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/OrderedImports.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/ReturnVoidInsteadOfEmptyTuple.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/TypeNamesShouldBeCapitalized.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/UseEarlyExits.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/UseLetInEveryBoundCaseVariable.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/UseShorthandTypeNames.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/UseSingleLinePropertyGetter.swift (98%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/UseSynthesizedInitializer.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/UseTripleSlashForDocumentationComments.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/UseWhereClausesInForLoops.swift (99%) rename Sources/{SwiftFormatRules => SwiftFormat/Rules}/ValidateDocumentationComments.swift (99%) diff --git a/Package.swift b/Package.swift index ecc93810d..6d7045c78 100644 --- a/Package.swift +++ b/Package.swift @@ -50,10 +50,7 @@ let package = Package( name: "SwiftFormat", dependencies: [ "SwiftFormatConfiguration", - "SwiftFormatCore", - "SwiftFormatPrettyPrint", - "SwiftFormatRules", - "SwiftFormatWhitespaceLinter", + .product(name: "Markdown", package: "swift-markdown"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftOperators", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), @@ -63,43 +60,10 @@ let package = Package( .target( name: "SwiftFormatConfiguration" ), - .target( - name: "SwiftFormatCore", - dependencies: [ - "SwiftFormatConfiguration", - .product(name: "Markdown", package: "swift-markdown"), - .product(name: "SwiftOperators", package: "swift-syntax"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - ] - ), - .target( - name: "SwiftFormatRules", - dependencies: [ - "SwiftFormatCore", - "SwiftFormatConfiguration", - .product(name: "Markdown", package: "swift-markdown"), - ] - ), - .target( - name: "SwiftFormatPrettyPrint", - dependencies: [ - "SwiftFormatCore", - "SwiftFormatConfiguration", - .product(name: "SwiftOperators", package: "swift-syntax"), - ] - ), - .target( - name: "SwiftFormatWhitespaceLinter", - dependencies: [ - "SwiftFormatCore", - .product(name: "SwiftSyntax", package: "swift-syntax"), - ] - ), .target( name: "_SwiftFormatTestSupport", dependencies: [ - "SwiftFormatCore", - "SwiftFormatRules", + "SwiftFormat", "SwiftFormatConfiguration", .product(name: "SwiftOperators", package: "swift-syntax"), ] @@ -133,8 +97,7 @@ let package = Package( .executableTarget( name: "generate-pipeline", dependencies: [ - "SwiftFormatCore", - "SwiftFormatRules", + "SwiftFormat", .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), ] @@ -144,7 +107,6 @@ let package = Package( dependencies: [ "SwiftFormat", "SwiftFormatConfiguration", - "SwiftFormatCore", .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), @@ -158,7 +120,7 @@ let package = Package( .testTarget( name: "SwiftFormatPerformanceTests", dependencies: [ - "SwiftFormatWhitespaceLinter", + "SwiftFormat", "_SwiftFormatTestSupport", .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), @@ -167,11 +129,8 @@ let package = Package( .testTarget( name: "SwiftFormatTests", dependencies: [ + "SwiftFormat", "SwiftFormatConfiguration", - "SwiftFormatCore", - "SwiftFormatPrettyPrint", - "SwiftFormatRules", - "SwiftFormatWhitespaceLinter", "_SwiftFormatTestSupport", .product(name: "Markdown", package: "swift-markdown"), .product(name: "SwiftOperators", package: "swift-syntax"), diff --git a/Sources/SwiftFormat/API/SwiftFormatter.swift b/Sources/SwiftFormat/API/SwiftFormatter.swift index 5902805e6..b676019aa 100644 --- a/Sources/SwiftFormat/API/SwiftFormatter.swift +++ b/Sources/SwiftFormat/API/SwiftFormatter.swift @@ -13,9 +13,6 @@ import Foundation import SwiftDiagnostics import SwiftFormatConfiguration -import SwiftFormatCore -import SwiftFormatPrettyPrint -import SwiftFormatRules import SwiftOperators import SwiftSyntax diff --git a/Sources/SwiftFormat/API/SwiftLinter.swift b/Sources/SwiftFormat/API/SwiftLinter.swift index 10b4e8089..f1787502e 100644 --- a/Sources/SwiftFormat/API/SwiftLinter.swift +++ b/Sources/SwiftFormat/API/SwiftLinter.swift @@ -13,10 +13,6 @@ import Foundation import SwiftDiagnostics import SwiftFormatConfiguration -import SwiftFormatCore -import SwiftFormatPrettyPrint -import SwiftFormatRules -import SwiftFormatWhitespaceLinter import SwiftOperators import SwiftSyntax diff --git a/Sources/SwiftFormatRules/AddModifierRewriter.swift b/Sources/SwiftFormat/Core/AddModifierRewriter.swift similarity index 99% rename from Sources/SwiftFormatRules/AddModifierRewriter.swift rename to Sources/SwiftFormat/Core/AddModifierRewriter.swift index a233a9f2b..b6319576e 100644 --- a/Sources/SwiftFormatRules/AddModifierRewriter.swift +++ b/Sources/SwiftFormat/Core/AddModifierRewriter.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax fileprivate final class AddModifierRewriter: SyntaxRewriter { diff --git a/Sources/SwiftFormatCore/Context.swift b/Sources/SwiftFormat/Core/Context.swift similarity index 100% rename from Sources/SwiftFormatCore/Context.swift rename to Sources/SwiftFormat/Core/Context.swift diff --git a/Sources/SwiftFormatCore/DocumentationComment.swift b/Sources/SwiftFormat/Core/DocumentationComment.swift similarity index 100% rename from Sources/SwiftFormatCore/DocumentationComment.swift rename to Sources/SwiftFormat/Core/DocumentationComment.swift diff --git a/Sources/SwiftFormatCore/DocumentationCommentText.swift b/Sources/SwiftFormat/Core/DocumentationCommentText.swift similarity index 100% rename from Sources/SwiftFormatCore/DocumentationCommentText.swift rename to Sources/SwiftFormat/Core/DocumentationCommentText.swift diff --git a/Sources/SwiftFormat/Core/Exports.swift b/Sources/SwiftFormat/Core/Exports.swift deleted file mode 100644 index be70db5b8..000000000 --- a/Sources/SwiftFormat/Core/Exports.swift +++ /dev/null @@ -1,20 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import SwiftFormatCore - -// The `SwiftFormatCore` module isn't meant for public use, but these types need to be since they -// are also part of the public `SwiftFormat` API. Use public typealiases to "re-export" them for -// now. - -public typealias Finding = SwiftFormatCore.Finding -public typealias FindingCategorizing = SwiftFormatCore.FindingCategorizing diff --git a/Sources/SwiftFormatCore/Finding+Convenience.swift b/Sources/SwiftFormat/Core/Finding+Convenience.swift similarity index 100% rename from Sources/SwiftFormatCore/Finding+Convenience.swift rename to Sources/SwiftFormat/Core/Finding+Convenience.swift diff --git a/Sources/SwiftFormatCore/Finding.swift b/Sources/SwiftFormat/Core/Finding.swift similarity index 100% rename from Sources/SwiftFormatCore/Finding.swift rename to Sources/SwiftFormat/Core/Finding.swift diff --git a/Sources/SwiftFormatCore/FindingCategorizing.swift b/Sources/SwiftFormat/Core/FindingCategorizing.swift similarity index 100% rename from Sources/SwiftFormatCore/FindingCategorizing.swift rename to Sources/SwiftFormat/Core/FindingCategorizing.swift diff --git a/Sources/SwiftFormatCore/FindingEmitter.swift b/Sources/SwiftFormat/Core/FindingEmitter.swift similarity index 100% rename from Sources/SwiftFormatCore/FindingEmitter.swift rename to Sources/SwiftFormat/Core/FindingEmitter.swift diff --git a/Sources/SwiftFormat/Core/FormatPipeline.swift b/Sources/SwiftFormat/Core/FormatPipeline.swift index 09492cd99..e72242e00 100644 --- a/Sources/SwiftFormat/Core/FormatPipeline.swift +++ b/Sources/SwiftFormat/Core/FormatPipeline.swift @@ -10,8 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore - /// A type that invokes individual format rules. /// /// Note that this type is not a `SyntaxVisitor` or `SyntaxRewriter`. That is because, at this time, diff --git a/Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift b/Sources/SwiftFormat/Core/FunctionDeclSyntax+Convenience.swift similarity index 100% rename from Sources/SwiftFormatRules/FunctionDeclSyntax+Convenience.swift rename to Sources/SwiftFormat/Core/FunctionDeclSyntax+Convenience.swift diff --git a/Sources/SwiftFormatRules/ImportsXCTestVisitor.swift b/Sources/SwiftFormat/Core/ImportsXCTestVisitor.swift similarity index 99% rename from Sources/SwiftFormatRules/ImportsXCTestVisitor.swift rename to Sources/SwiftFormat/Core/ImportsXCTestVisitor.swift index 29b608180..e5d4189aa 100644 --- a/Sources/SwiftFormatRules/ImportsXCTestVisitor.swift +++ b/Sources/SwiftFormat/Core/ImportsXCTestVisitor.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// A visitor that determines if the target source file imports `XCTest`. diff --git a/Sources/SwiftFormatWhitespaceLinter/LazySplitSequence.swift b/Sources/SwiftFormat/Core/LazySplitSequence.swift similarity index 100% rename from Sources/SwiftFormatWhitespaceLinter/LazySplitSequence.swift rename to Sources/SwiftFormat/Core/LazySplitSequence.swift diff --git a/Sources/SwiftFormatCore/LegacyTriviaBehavior.swift b/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift similarity index 100% rename from Sources/SwiftFormatCore/LegacyTriviaBehavior.swift rename to Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift diff --git a/Sources/SwiftFormat/Core/LintPipeline.swift b/Sources/SwiftFormat/Core/LintPipeline.swift index 2921975e4..94e02e6cf 100644 --- a/Sources/SwiftFormat/Core/LintPipeline.swift +++ b/Sources/SwiftFormat/Core/LintPipeline.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// A syntax visitor that delegates to individual rules for linting. diff --git a/Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift b/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift similarity index 100% rename from Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift rename to Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift diff --git a/Sources/SwiftFormat/Core/Parsing.swift b/Sources/SwiftFormat/Core/Parsing.swift index 0dcfb6308..f4087f124 100644 --- a/Sources/SwiftFormat/Core/Parsing.swift +++ b/Sources/SwiftFormat/Core/Parsing.swift @@ -12,7 +12,6 @@ import Foundation import SwiftDiagnostics -import SwiftFormatCore import SwiftOperators import SwiftParser import SwiftParserDiagnostics diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index a8331cb34..2516f874b 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -12,8 +12,6 @@ // This file is automatically generated with generate-pipeline. Do Not Edit! -import SwiftFormatCore -import SwiftFormatRules import SwiftSyntax /// A syntax visitor that delegates to individual rules for linting. diff --git a/Sources/SwiftFormatWhitespaceLinter/RememberingIterator.swift b/Sources/SwiftFormat/Core/RememberingIterator.swift similarity index 100% rename from Sources/SwiftFormatWhitespaceLinter/RememberingIterator.swift rename to Sources/SwiftFormat/Core/RememberingIterator.swift diff --git a/Sources/SwiftFormatCore/Rule.swift b/Sources/SwiftFormat/Core/Rule.swift similarity index 100% rename from Sources/SwiftFormatCore/Rule.swift rename to Sources/SwiftFormat/Core/Rule.swift diff --git a/Sources/SwiftFormatCore/RuleBasedFindingCategory.swift b/Sources/SwiftFormat/Core/RuleBasedFindingCategory.swift similarity index 100% rename from Sources/SwiftFormatCore/RuleBasedFindingCategory.swift rename to Sources/SwiftFormat/Core/RuleBasedFindingCategory.swift diff --git a/Sources/SwiftFormatCore/RuleMask.swift b/Sources/SwiftFormat/Core/RuleMask.swift similarity index 100% rename from Sources/SwiftFormatCore/RuleMask.swift rename to Sources/SwiftFormat/Core/RuleMask.swift diff --git a/Sources/SwiftFormatRules/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift similarity index 100% rename from Sources/SwiftFormatRules/RuleNameCache+Generated.swift rename to Sources/SwiftFormat/Core/RuleNameCache+Generated.swift diff --git a/Sources/SwiftFormatCore/RuleState.swift b/Sources/SwiftFormat/Core/RuleState.swift similarity index 100% rename from Sources/SwiftFormatCore/RuleState.swift rename to Sources/SwiftFormat/Core/RuleState.swift diff --git a/Sources/SwiftFormatRules/SemicolonSyntaxProtocol.swift b/Sources/SwiftFormat/Core/SemicolonSyntaxProtocol.swift similarity index 100% rename from Sources/SwiftFormatRules/SemicolonSyntaxProtocol.swift rename to Sources/SwiftFormat/Core/SemicolonSyntaxProtocol.swift diff --git a/Sources/SwiftFormatCore/SyntaxFormatRule.swift b/Sources/SwiftFormat/Core/SyntaxFormatRule.swift similarity index 100% rename from Sources/SwiftFormatCore/SyntaxFormatRule.swift rename to Sources/SwiftFormat/Core/SyntaxFormatRule.swift diff --git a/Sources/SwiftFormatCore/SyntaxLintRule.swift b/Sources/SwiftFormat/Core/SyntaxLintRule.swift similarity index 100% rename from Sources/SwiftFormatCore/SyntaxLintRule.swift rename to Sources/SwiftFormat/Core/SyntaxLintRule.swift diff --git a/Sources/SwiftFormatCore/SyntaxProtocol+Convenience.swift b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift similarity index 100% rename from Sources/SwiftFormatCore/SyntaxProtocol+Convenience.swift rename to Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift diff --git a/Sources/SwiftFormatRules/TokenSyntax+Convenience.swift b/Sources/SwiftFormat/Core/TokenSyntax+Convenience.swift similarity index 100% rename from Sources/SwiftFormatRules/TokenSyntax+Convenience.swift rename to Sources/SwiftFormat/Core/TokenSyntax+Convenience.swift diff --git a/Sources/SwiftFormatCore/Trivia+Convenience.swift b/Sources/SwiftFormat/Core/Trivia+Convenience.swift similarity index 100% rename from Sources/SwiftFormatCore/Trivia+Convenience.swift rename to Sources/SwiftFormat/Core/Trivia+Convenience.swift diff --git a/Sources/SwiftFormatRules/VarDeclSyntax+Convenience.swift b/Sources/SwiftFormat/Core/VarDeclSyntax+Convenience.swift similarity index 100% rename from Sources/SwiftFormatRules/VarDeclSyntax+Convenience.swift rename to Sources/SwiftFormat/Core/VarDeclSyntax+Convenience.swift diff --git a/Sources/SwiftFormatPrettyPrint/Comment.swift b/Sources/SwiftFormat/PrettyPrint/Comment.swift similarity index 100% rename from Sources/SwiftFormatPrettyPrint/Comment.swift rename to Sources/SwiftFormat/PrettyPrint/Comment.swift diff --git a/Sources/SwiftFormatPrettyPrint/Indent+Length.swift b/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift similarity index 100% rename from Sources/SwiftFormatPrettyPrint/Indent+Length.swift rename to Sources/SwiftFormat/PrettyPrint/Indent+Length.swift diff --git a/Sources/SwiftFormatPrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift similarity index 99% rename from Sources/SwiftFormatPrettyPrint/PrettyPrint.swift rename to Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index f35acc61b..5099cba6b 100644 --- a/Sources/SwiftFormatPrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import SwiftFormatConfiguration -import SwiftFormatCore import SwiftSyntax /// PrettyPrinter takes a Syntax node and outputs a well-formatted, re-indented reproduction of the diff --git a/Sources/SwiftFormatPrettyPrint/PrettyPrintFindingCategory.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintFindingCategory.swift similarity index 97% rename from Sources/SwiftFormatPrettyPrint/PrettyPrintFindingCategory.swift rename to Sources/SwiftFormat/PrettyPrint/PrettyPrintFindingCategory.swift index 377443350..ee81342f0 100644 --- a/Sources/SwiftFormatPrettyPrint/PrettyPrintFindingCategory.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintFindingCategory.swift @@ -10,8 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore - /// Categories for findings emitted by the pretty printer. enum PrettyPrintFindingCategory: FindingCategorizing { /// Finding related to an end-of-line comment. diff --git a/Sources/SwiftFormatPrettyPrint/Token.swift b/Sources/SwiftFormat/PrettyPrint/Token.swift similarity index 100% rename from Sources/SwiftFormatPrettyPrint/Token.swift rename to Sources/SwiftFormat/PrettyPrint/Token.swift diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift similarity index 99% rename from Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift rename to Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index bad5c8ee1..568f62a7d 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -12,7 +12,6 @@ import Foundation import SwiftFormatConfiguration -import SwiftFormatCore import SwiftOperators import SwiftSyntax diff --git a/Sources/SwiftFormatPrettyPrint/Verbatim.swift b/Sources/SwiftFormat/PrettyPrint/Verbatim.swift similarity index 100% rename from Sources/SwiftFormatPrettyPrint/Verbatim.swift rename to Sources/SwiftFormat/PrettyPrint/Verbatim.swift diff --git a/Sources/SwiftFormatWhitespaceLinter/WhitespaceFindingCategory.swift b/Sources/SwiftFormat/PrettyPrint/WhitespaceFindingCategory.swift similarity index 98% rename from Sources/SwiftFormatWhitespaceLinter/WhitespaceFindingCategory.swift rename to Sources/SwiftFormat/PrettyPrint/WhitespaceFindingCategory.swift index ea8e3b449..bc50fb38f 100644 --- a/Sources/SwiftFormatWhitespaceLinter/WhitespaceFindingCategory.swift +++ b/Sources/SwiftFormat/PrettyPrint/WhitespaceFindingCategory.swift @@ -10,8 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore - /// Categories for findings emitted by the whitespace linter. enum WhitespaceFindingCategory: FindingCategorizing { /// Findings related to trailing whitespace on a line. diff --git a/Sources/SwiftFormatWhitespaceLinter/WhitespaceLinter.swift b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift similarity index 99% rename from Sources/SwiftFormatWhitespaceLinter/WhitespaceLinter.swift rename to Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift index 1fb99126b..528d526e0 100644 --- a/Sources/SwiftFormatWhitespaceLinter/WhitespaceLinter.swift +++ b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import SwiftFormatConfiguration -import SwiftFormatCore import SwiftSyntax private let utf8Newline = UTF8.CodeUnit(ascii: "\n") diff --git a/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift similarity index 99% rename from Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift rename to Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift index 52e8ec79f..7bef875af 100644 --- a/Sources/SwiftFormatRules/AllPublicDeclarationsHaveDocumentation.swift +++ b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// All public or open declarations must have a top-level documentation comment. diff --git a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift similarity index 99% rename from Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift rename to Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift index 2baa29cbb..652ee8b75 100644 --- a/Sources/SwiftFormatRules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// All values should be written in lower camel-case (`lowerCamelCase`). diff --git a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift b/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift similarity index 99% rename from Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift rename to Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift index 4ba408259..1e448f8dc 100644 --- a/Sources/SwiftFormatRules/AmbiguousTrailingClosureOverload.swift +++ b/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Overloads with only a closure argument should not be disambiguated by parameter labels. diff --git a/Sources/SwiftFormatRules/BeginDocumentationCommentWithOneLineSummary.swift b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift similarity index 99% rename from Sources/SwiftFormatRules/BeginDocumentationCommentWithOneLineSummary.swift rename to Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift index d9e7cd60a..07fb24602 100644 --- a/Sources/SwiftFormatRules/BeginDocumentationCommentWithOneLineSummary.swift +++ b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormatCore import SwiftSyntax /// All documentation comments must begin with a one-line summary of the declaration. diff --git a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift similarity index 99% rename from Sources/SwiftFormatRules/DoNotUseSemicolons.swift rename to Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift index 230091fce..dc8e6f737 100644 --- a/Sources/SwiftFormatRules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Semicolons should not be present in Swift code. diff --git a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift similarity index 99% rename from Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift rename to Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift index 85eb7faeb..fc99c0aae 100644 --- a/Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Static properties of a type that return that type should not include a reference to their type. diff --git a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift b/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift similarity index 99% rename from Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift rename to Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift index aee84aa53..95bed05a8 100644 --- a/Sources/SwiftFormatRules/FileScopedDeclarationPrivacy.swift +++ b/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Declarations at file scope with effective private access should be consistently declared as diff --git a/Sources/SwiftFormatRules/FullyIndirectEnum.swift b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift similarity index 99% rename from Sources/SwiftFormatRules/FullyIndirectEnum.swift rename to Sources/SwiftFormat/Rules/FullyIndirectEnum.swift index dc02ebde9..7361b7f9a 100644 --- a/Sources/SwiftFormatRules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// If all cases of an enum are `indirect`, the entire enum should be marked `indirect`. diff --git a/Sources/SwiftFormatRules/GroupNumericLiterals.swift b/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift similarity index 99% rename from Sources/SwiftFormatRules/GroupNumericLiterals.swift rename to Sources/SwiftFormat/Rules/GroupNumericLiterals.swift index 32ee3eab5..0c4a41bf8 100644 --- a/Sources/SwiftFormatRules/GroupNumericLiterals.swift +++ b/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Numeric literals should be grouped with `_`s to delimit common separators. diff --git a/Sources/SwiftFormatRules/IdentifiersMustBeASCII.swift b/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift similarity index 98% rename from Sources/SwiftFormatRules/IdentifiersMustBeASCII.swift rename to Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift index b42376e54..be87ee8a6 100644 --- a/Sources/SwiftFormatRules/IdentifiersMustBeASCII.swift +++ b/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// All identifiers must be ASCII. diff --git a/Sources/SwiftFormatRules/NeverForceUnwrap.swift b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift similarity index 99% rename from Sources/SwiftFormatRules/NeverForceUnwrap.swift rename to Sources/SwiftFormat/Rules/NeverForceUnwrap.swift index 19e0243e1..770ade535 100644 --- a/Sources/SwiftFormatRules/NeverForceUnwrap.swift +++ b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Force-unwraps are strongly discouraged and must be documented. diff --git a/Sources/SwiftFormatRules/NeverUseForceTry.swift b/Sources/SwiftFormat/Rules/NeverUseForceTry.swift similarity index 98% rename from Sources/SwiftFormatRules/NeverUseForceTry.swift rename to Sources/SwiftFormat/Rules/NeverUseForceTry.swift index 253c64f69..b33deb969 100644 --- a/Sources/SwiftFormatRules/NeverUseForceTry.swift +++ b/Sources/SwiftFormat/Rules/NeverUseForceTry.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Force-try (`try!`) is forbidden. diff --git a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift similarity index 99% rename from Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift rename to Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift index 803f31c2a..266b208a5 100644 --- a/Sources/SwiftFormatRules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Implicitly unwrapped optionals (e.g. `var s: String!`) are forbidden. diff --git a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift similarity index 99% rename from Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift rename to Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift index d59a4bbc5..998da9cfc 100644 --- a/Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Specifying an access level for an extension declaration is forbidden. diff --git a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift similarity index 99% rename from Sources/SwiftFormatRules/NoAssignmentInExpressions.swift rename to Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift index 7420ade74..89306cf57 100644 --- a/Sources/SwiftFormatRules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import SwiftFormatConfiguration -import SwiftFormatCore import SwiftSyntax /// Assignment expressions must be their own statements. diff --git a/Sources/SwiftFormatRules/NoBlockComments.swift b/Sources/SwiftFormat/Rules/NoBlockComments.swift similarity index 98% rename from Sources/SwiftFormatRules/NoBlockComments.swift rename to Sources/SwiftFormat/Rules/NoBlockComments.swift index 5c07983d5..a22150e77 100644 --- a/Sources/SwiftFormatRules/NoBlockComments.swift +++ b/Sources/SwiftFormat/Rules/NoBlockComments.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Block comments should be avoided in favor of line comments. diff --git a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift similarity index 99% rename from Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift rename to Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift index 4a76ca618..b152e629d 100644 --- a/Sources/SwiftFormatRules/NoCasesWithOnlyFallthrough.swift +++ b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Cases that contain only the `fallthrough` statement are forbidden. diff --git a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift b/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift similarity index 98% rename from Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift rename to Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift index 469796fd4..96c6a7e80 100644 --- a/Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift +++ b/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Function calls with no arguments and a trailing closure should not have empty parentheses. diff --git a/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift b/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift similarity index 99% rename from Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift rename to Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift index 53228899f..fa05ce5c2 100644 --- a/Sources/SwiftFormatRules/NoLabelsInCasePatterns.swift +++ b/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormatCore import SwiftSyntax /// Redundant labels are forbidden in case patterns. diff --git a/Sources/SwiftFormatRules/NoLeadingUnderscores.swift b/Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift similarity index 99% rename from Sources/SwiftFormatRules/NoLeadingUnderscores.swift rename to Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift index f760a5bbc..840b86204 100644 --- a/Sources/SwiftFormatRules/NoLeadingUnderscores.swift +++ b/Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Identifiers in declarations and patterns should not have leading underscores. diff --git a/Sources/SwiftFormatRules/NoParensAroundConditions.swift b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift similarity index 99% rename from Sources/SwiftFormatRules/NoParensAroundConditions.swift rename to Sources/SwiftFormat/Rules/NoParensAroundConditions.swift index 3a8713b5c..721dc3308 100644 --- a/Sources/SwiftFormatRules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Enforces rules around parentheses in conditions or matched expressions. diff --git a/Sources/SwiftFormatRules/NoPlaygroundLiterals.swift b/Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift similarity index 98% rename from Sources/SwiftFormatRules/NoPlaygroundLiterals.swift rename to Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift index c06a69fc6..96ac6d3ab 100644 --- a/Sources/SwiftFormatRules/NoPlaygroundLiterals.swift +++ b/Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormatCore import SwiftSyntax /// Playground literals (e.g. `#colorLiteral`) are forbidden. diff --git a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift b/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift similarity index 98% rename from Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift rename to Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift index f0693ceb7..c6cc65f80 100644 --- a/Sources/SwiftFormatRules/NoVoidReturnOnFunctionSignature.swift +++ b/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Functions that return `()` or `Void` should omit the return signature. diff --git a/Sources/SwiftFormatRules/OneCasePerLine.swift b/Sources/SwiftFormat/Rules/OneCasePerLine.swift similarity index 99% rename from Sources/SwiftFormatRules/OneCasePerLine.swift rename to Sources/SwiftFormat/Rules/OneCasePerLine.swift index 29dc537a7..2605d44e9 100644 --- a/Sources/SwiftFormatRules/OneCasePerLine.swift +++ b/Sources/SwiftFormat/Rules/OneCasePerLine.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Each enum case with associated values or a raw value should appear in its own case declaration. diff --git a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift similarity index 99% rename from Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift rename to Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift index 80079c24d..e7843cbd7 100644 --- a/Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift +++ b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Each variable declaration, with the exception of tuple destructuring, should diff --git a/Sources/SwiftFormatRules/OnlyOneTrailingClosureArgument.swift b/Sources/SwiftFormat/Rules/OnlyOneTrailingClosureArgument.swift similarity index 98% rename from Sources/SwiftFormatRules/OnlyOneTrailingClosureArgument.swift rename to Sources/SwiftFormat/Rules/OnlyOneTrailingClosureArgument.swift index 984429270..9d2440251 100644 --- a/Sources/SwiftFormatRules/OnlyOneTrailingClosureArgument.swift +++ b/Sources/SwiftFormat/Rules/OnlyOneTrailingClosureArgument.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Function calls should never mix normal closure arguments and trailing closures. diff --git a/Sources/SwiftFormatRules/OrderedImports.swift b/Sources/SwiftFormat/Rules/OrderedImports.swift similarity index 99% rename from Sources/SwiftFormatRules/OrderedImports.swift rename to Sources/SwiftFormat/Rules/OrderedImports.swift index 313af4e1e..dbd60c3f7 100644 --- a/Sources/SwiftFormatRules/OrderedImports.swift +++ b/Sources/SwiftFormat/Rules/OrderedImports.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Imports must be lexicographically ordered and logically grouped at the top of each source file. diff --git a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift similarity index 99% rename from Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift rename to Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift index 25d169159..8253a6af9 100644 --- a/Sources/SwiftFormatRules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Return `Void`, not `()`, in signatures. diff --git a/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift b/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift similarity index 99% rename from Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift rename to Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift index 153adba1e..2d84fadcc 100644 --- a/Sources/SwiftFormatRules/TypeNamesShouldBeCapitalized.swift +++ b/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// `struct`, `class`, `enum` and `protocol` declarations should have a capitalized name. diff --git a/Sources/SwiftFormatRules/UseEarlyExits.swift b/Sources/SwiftFormat/Rules/UseEarlyExits.swift similarity index 99% rename from Sources/SwiftFormatRules/UseEarlyExits.swift rename to Sources/SwiftFormat/Rules/UseEarlyExits.swift index c7fe11b99..838bf80f3 100644 --- a/Sources/SwiftFormatRules/UseEarlyExits.swift +++ b/Sources/SwiftFormat/Rules/UseEarlyExits.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Early exits should be used whenever possible. diff --git a/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift b/Sources/SwiftFormat/Rules/UseLetInEveryBoundCaseVariable.swift similarity index 99% rename from Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift rename to Sources/SwiftFormat/Rules/UseLetInEveryBoundCaseVariable.swift index 745ea0b1a..c45c96878 100644 --- a/Sources/SwiftFormatRules/UseLetInEveryBoundCaseVariable.swift +++ b/Sources/SwiftFormat/Rules/UseLetInEveryBoundCaseVariable.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Every variable bound in a `case` pattern must have its own `let/var`. diff --git a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift similarity index 99% rename from Sources/SwiftFormatRules/UseShorthandTypeNames.swift rename to Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift index c6da7b9f4..55ea1289b 100644 --- a/Sources/SwiftFormatRules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Shorthand type forms must be used wherever possible. diff --git a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift similarity index 98% rename from Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift rename to Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift index ddc6dbd00..0b7b6e632 100644 --- a/Sources/SwiftFormatRules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// Read-only computed properties must use implicit `get` blocks. diff --git a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift similarity index 99% rename from Sources/SwiftFormatRules/UseSynthesizedInitializer.swift rename to Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift index 097328bfb..501192d6b 100644 --- a/Sources/SwiftFormatRules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormatCore import SwiftSyntax /// When possible, the synthesized `struct` initializer should be used. diff --git a/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift b/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift similarity index 99% rename from Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift rename to Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift index 2b2546d6e..5b07de095 100644 --- a/Sources/SwiftFormatRules/UseTripleSlashForDocumentationComments.swift +++ b/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormatCore import SwiftSyntax /// Documentation comments must use the `///` form. diff --git a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift similarity index 99% rename from Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift rename to Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift index 3ac45ef97..26c30554f 100644 --- a/Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore import SwiftSyntax /// `for` loops that consist of a single `if` statement must use `where` clauses instead. diff --git a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift similarity index 99% rename from Sources/SwiftFormatRules/ValidateDocumentationComments.swift rename to Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift index 4a7db1ddc..5886f3cdc 100644 --- a/Sources/SwiftFormatRules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift @@ -12,7 +12,6 @@ import Foundation import Markdown -import SwiftFormatCore import SwiftSyntax /// Documentation comments must be complete and valid. diff --git a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift index 6dad4f204..b011b0925 100644 --- a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift +++ b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift @@ -1,6 +1,5 @@ +import SwiftFormat import SwiftFormatConfiguration -import SwiftFormatCore -import SwiftFormatRules import SwiftSyntax import XCTest diff --git a/Sources/_SwiftFormatTestSupport/TestingFindingConsumer.swift b/Sources/_SwiftFormatTestSupport/TestingFindingConsumer.swift index 972981250..ca49696a9 100644 --- a/Sources/_SwiftFormatTestSupport/TestingFindingConsumer.swift +++ b/Sources/_SwiftFormatTestSupport/TestingFindingConsumer.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore +import SwiftFormat /// Information about a finding tracked by `TestingFindingConsumer`. /// diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index 0cc0d1d31..a9ef1dc43 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormatCore +import SwiftFormat import SwiftSyntax import SwiftParser diff --git a/Sources/swift-format/Utilities/Diagnostic.swift b/Sources/swift-format/Utilities/Diagnostic.swift index 42305eab9..636190046 100644 --- a/Sources/swift-format/Utilities/Diagnostic.swift +++ b/Sources/swift-format/Utilities/Diagnostic.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore +import SwiftFormat import SwiftSyntax /// Diagnostic data that retains the separation of a finding category (if present) from the rest of diff --git a/Sources/swift-format/Utilities/DiagnosticsEngine.swift b/Sources/swift-format/Utilities/DiagnosticsEngine.swift index 700a2c229..0eead4342 100644 --- a/Sources/swift-format/Utilities/DiagnosticsEngine.swift +++ b/Sources/swift-format/Utilities/DiagnosticsEngine.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatCore +import SwiftFormat import SwiftSyntax import SwiftDiagnostics diff --git a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift index e6c18f797..9e522c105 100644 --- a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift +++ b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatWhitespaceLinter +import SwiftFormat import SwiftSyntax import SwiftParser import XCTest diff --git a/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift b/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift index e038cda32..f376f42a4 100644 --- a/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift +++ b/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift @@ -1,5 +1,5 @@ import Markdown -import SwiftFormatCore +import SwiftFormat import SwiftSyntax import SwiftSyntaxBuilder import XCTest diff --git a/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift b/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift index 5fb0f24eb..28580a95f 100644 --- a/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift +++ b/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatCore +import SwiftFormat import SwiftSyntax import SwiftSyntaxBuilder import XCTest diff --git a/Tests/SwiftFormatTests/Core/RuleMaskTests.swift b/Tests/SwiftFormatTests/Core/RuleMaskTests.swift index 399e214d0..ee753a7b0 100644 --- a/Tests/SwiftFormatTests/Core/RuleMaskTests.swift +++ b/Tests/SwiftFormatTests/Core/RuleMaskTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatCore +import SwiftFormat import SwiftSyntax import SwiftParser import XCTest diff --git a/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift index 03b092915..f7382c5cc 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatPrettyPrint +import SwiftFormat import SwiftSyntax final class ArrayDeclTests: PrettyPrintTestCase { diff --git a/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift index 67bd96c5f..58a400ce5 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatPrettyPrint +import SwiftFormat import SwiftSyntax final class DictionaryDeclTests: PrettyPrintTestCase { diff --git a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift index 1e3c691b9..5818a02a2 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift @@ -1,6 +1,5 @@ +import SwiftFormat import SwiftFormatConfiguration -import SwiftFormatCore -import SwiftFormatPrettyPrint import SwiftOperators import SwiftSyntax import SwiftParser diff --git a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift index f2c9433c3..7b7d01bcd 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift @@ -1,5 +1,5 @@ +import SwiftFormat import SwiftFormatConfiguration -import SwiftFormatWhitespaceLinter final class WhitespaceLintTests: WhitespaceTestCase { func testSpacing() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift index c84e7098b..e2bd4fb55 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift @@ -1,6 +1,5 @@ +import SwiftFormat import SwiftFormatConfiguration -import SwiftFormatCore -import SwiftFormatWhitespaceLinter import SwiftSyntax import SwiftParser import XCTest diff --git a/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift b/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift index 4db69d3cb..b7faddd74 100644 --- a/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift +++ b/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCase { func testPublicDeclsWithoutDocs() { diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift index 2f8080745..718800b3e 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class AlwaysUseLowerCamelCaseTests: LintOrFormatRuleTestCase { override func setUp() { diff --git a/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift b/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift index f758509d7..169d5b916 100644 --- a/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift +++ b/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class AmbiguousTrailingClosureOverloadTests: LintOrFormatRuleTestCase { func testAmbiguousOverloads() { diff --git a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift index 9187eb947..7a31691d7 100644 --- a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift +++ b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTestCase { override func setUp() { diff --git a/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift b/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift index 7768c5192..554a0592d 100644 --- a/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift +++ b/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { func testSemicolonUse() { diff --git a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift index 0a9cb1ce1..c4ba2bf81 100644 --- a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift +++ b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase { func testRepetitiveProperties() { diff --git a/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift b/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift index 6e30a4dd9..42859d8a4 100644 --- a/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift +++ b/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift @@ -1,6 +1,5 @@ +import SwiftFormat import SwiftFormatConfiguration -import SwiftFormatCore -import SwiftFormatRules import SwiftSyntax private typealias TestConfiguration = ( diff --git a/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift b/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift index 69977104f..5f2424b38 100644 --- a/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift +++ b/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat class FullyIndirectEnumTests: LintOrFormatRuleTestCase { func testAllIndirectCases() { diff --git a/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift b/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift index 27e63842a..088da634f 100644 --- a/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift +++ b/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class GroupNumericLiteralsTests: LintOrFormatRuleTestCase { func testNumericGrouping() { diff --git a/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift b/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift index 95ff62cf1..390896f92 100644 --- a/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift +++ b/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class IdentifiersMustBeASCIITests: LintOrFormatRuleTestCase { func testInvalidIdentifiers() { diff --git a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift index 3b06da18e..a2a0b08f8 100644 --- a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift +++ b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift @@ -1,5 +1,4 @@ -import SwiftFormatCore -import SwiftFormatRules +import SwiftFormat import SwiftParser import XCTest import _SwiftFormatTestSupport diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index 60bdaa293..4d27911b8 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -1,5 +1,5 @@ +import SwiftFormat import SwiftFormatConfiguration -import SwiftFormatCore import SwiftOperators import SwiftParser import SwiftSyntax diff --git a/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift b/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift index 076ea6610..a9a8005e2 100644 --- a/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NeverForceUnwrapTests: LintOrFormatRuleTestCase { func testUnsafeUnwrap() { diff --git a/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift index 646182a95..97b0fbaf8 100644 --- a/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NeverUseForceTryTests: LintOrFormatRuleTestCase { func testInvalidTryExpression() { diff --git a/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift index 369f1c8fe..4a7002080 100644 --- a/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NeverUseImplicitlyUnwrappedOptionalsTests: LintOrFormatRuleTestCase { func testInvalidVariableUnwrapping() { diff --git a/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift b/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift index 03a782d1e..dd8e49129 100644 --- a/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { func testExtensionDeclarationAccessLevel() { diff --git a/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift b/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift index 6ba554a0f..30556d0cd 100644 --- a/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { func testAssignmentInExpressionContextIsDiagnosed() { diff --git a/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift b/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift index f00f8fd5f..39831e668 100644 --- a/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NoBlockCommentsTests: LintOrFormatRuleTestCase { func testDiagnoseBlockComments() { diff --git a/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift b/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift index eb9653968..1d69a5320 100644 --- a/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { func testFallthroughCases() { diff --git a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift index c4aef650a..fee720585 100644 --- a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NoEmptyTrailingClosureParenthesesTests: LintOrFormatRuleTestCase { func testInvalidEmptyParenTrailingClosure() { diff --git a/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift b/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift index f756513d4..aa689d901 100644 --- a/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NoLabelsInCasePatternsTests: LintOrFormatRuleTestCase { func testRedundantCaseLabels() { diff --git a/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift b/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift index 3df6f4f43..b29179a66 100644 --- a/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NoLeadingUnderscoresTests: LintOrFormatRuleTestCase { func testVars() { diff --git a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift index 9dbe9d244..c524c6a74 100644 --- a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { func testParensAroundConditions() { diff --git a/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift b/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift index 953b5b5e2..b95d82764 100644 --- a/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class NoVoidReturnOnFunctionSignatureTests: LintOrFormatRuleTestCase { func testVoidReturns() { diff --git a/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift b/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift index d89cd6c72..4e4da7098 100644 --- a/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift +++ b/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class OneCasePerLineTests: LintOrFormatRuleTestCase { diff --git a/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift b/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift index d9dc6a518..99d836900 100644 --- a/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift +++ b/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { func testMultipleVariableBindings() { diff --git a/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift b/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift index 812dfef9f..4fd5e444a 100644 --- a/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift +++ b/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class OnlyOneTrailingClosureArgumentTests: LintOrFormatRuleTestCase { func testInvalidTrailingClosureCall() { diff --git a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift index b3d2f2527..5b5bd7a75 100644 --- a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class OrderedImportsTests: LintOrFormatRuleTestCase { func testInvalidImportsOrder() { @@ -12,7 +12,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { // Comment with new lines import UIKit - @testable import SwiftFormatRules + @testable import SwiftFormat import enum Darwin.D.isatty // Starts Test @testable import MyModuleUnderTest @@ -38,7 +38,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { // Starts Test @testable import MyModuleUnderTest - @testable import SwiftFormatRules + @testable import SwiftFormat let a = 3 """ @@ -66,7 +66,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { func testImportsOrderWithoutModuleType() { let input = """ - @testable import SwiftFormatRules + @testable import SwiftFormat import func Darwin.D.isatty @testable import MyModuleUnderTest import func Darwin.C.isatty @@ -80,7 +80,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { import func Darwin.D.isatty @testable import MyModuleUnderTest - @testable import SwiftFormatRules + @testable import SwiftFormat let a = 3 """ diff --git a/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift b/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift index 1bd020d5d..a35805185 100644 --- a/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift +++ b/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class ReturnVoidInsteadOfEmptyTupleTests: LintOrFormatRuleTestCase { func testBasic() { diff --git a/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift b/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift index 8d0341571..255bdd3f3 100644 --- a/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift +++ b/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { func testConstruction() { diff --git a/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift b/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift index 9f7f443ae..e988701cf 100644 --- a/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class UseEarlyExitsTests: LintOrFormatRuleTestCase { func testBasicIfElse() { diff --git a/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift b/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift index b491d4e69..998fc06da 100644 --- a/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class UseLetInEveryBoundCaseVariableTests: LintOrFormatRuleTestCase { override func setUp() { diff --git a/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift b/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift index 388c4386d..29e35dd89 100644 --- a/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { func testNamesInTypeContextsAreShortened() { diff --git a/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift b/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift index e20a093fb..cf4f286d2 100644 --- a/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class UseSingleLinePropertyGetterTests: LintOrFormatRuleTestCase { func testMultiLinePropertyGetter() { diff --git a/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift b/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift index f10a72e7c..548fd93ce 100644 --- a/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { override func setUp() { diff --git a/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift index b7e4894c4..4e817ef72 100644 --- a/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCase { func testRemoveDocBlockComments() { diff --git a/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift b/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift index ac60a3c81..34e271456 100644 --- a/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class UseWhereClausesInForLoopsTests: LintOrFormatRuleTestCase { func testForLoopWhereClauses() { diff --git a/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift index 5df864395..ca0807e58 100644 --- a/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatRules +import SwiftFormat final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { override func setUp() { From 615c2bcc9ccc8a0c4e515a61f2059ca6b3822bb9 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 15 Aug 2023 14:36:11 -0400 Subject: [PATCH 092/332] Update `generate-pipeline` for the new module organization. --- Sources/generate-pipeline/PipelineGenerator.swift | 2 -- Sources/generate-pipeline/RuleCollector.swift | 2 +- Sources/generate-pipeline/main.swift | 8 ++++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Sources/generate-pipeline/PipelineGenerator.swift b/Sources/generate-pipeline/PipelineGenerator.swift index 8a92036ce..4b0054ccb 100644 --- a/Sources/generate-pipeline/PipelineGenerator.swift +++ b/Sources/generate-pipeline/PipelineGenerator.swift @@ -40,8 +40,6 @@ final class PipelineGenerator: FileGenerator { // This file is automatically generated with generate-pipeline. Do Not Edit! - import SwiftFormatCore - import SwiftFormatRules import SwiftSyntax /// A syntax visitor that delegates to individual rules for linting. diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index a9ef1dc43..165a58d55 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -135,7 +135,7 @@ final class RuleCollector { /// Ignore it if it doesn't have any; there's no point in putting no-op rules in the pipeline. /// Otherwise, return it (we don't need to look at the rest of the inheritances). guard !visitedNodes.isEmpty else { return nil } - guard let ruleType = _typeByName("SwiftFormatRules.\(typeName)") as? Rule.Type else { + guard let ruleType = _typeByName("SwiftFormat.\(typeName)") as? Rule.Type else { preconditionFailure("Failed to find type for rule named \(typeName)") } return DetectedRule( diff --git a/Sources/generate-pipeline/main.swift b/Sources/generate-pipeline/main.swift index afc469ad9..fdf932031 100644 --- a/Sources/generate-pipeline/main.swift +++ b/Sources/generate-pipeline/main.swift @@ -16,16 +16,20 @@ import SwiftSyntax let sourcesDirectory = URL(fileURLWithPath: #file) .deletingLastPathComponent() .deletingLastPathComponent() -let rulesDirectory = sourcesDirectory.appendingPathComponent("SwiftFormatRules") +let rulesDirectory = sourcesDirectory + .appendingPathComponent("SwiftFormat") + .appendingPathComponent("Rules") let pipelineFile = sourcesDirectory .appendingPathComponent("SwiftFormat") + .appendingPathComponent("Core") .appendingPathComponent("Pipelines+Generated.swift") let ruleRegistryFile = sourcesDirectory .appendingPathComponent("SwiftFormatConfiguration") .appendingPathComponent("RuleRegistry+Generated.swift") let ruleNameCacheFile = sourcesDirectory - .appendingPathComponent("SwiftFormatRules") + .appendingPathComponent("SwiftFormat") + .appendingPathComponent("Core") .appendingPathComponent("RuleNameCache+Generated.swift") var ruleCollector = RuleCollector() From 0ddb4c0ba3cb3d2d43b44bdc3bc67be40c13f4c5 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 15 Aug 2023 14:55:21 -0400 Subject: [PATCH 093/332] Move syntax tree lint/format rules into `@_spi(Rules)`. --- .../Rules/AllPublicDeclarationsHaveDocumentation.swift | 2 ++ Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift | 2 ++ .../Rules/AmbiguousTrailingClosureOverload.swift | 3 +++ .../BeginDocumentationCommentWithOneLineSummary.swift | 3 +++ Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift | 3 +++ .../Rules/DontRepeatTypeInStaticProperties.swift | 2 ++ .../SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift | 3 +++ Sources/SwiftFormat/Rules/FullyIndirectEnum.swift | 2 ++ Sources/SwiftFormat/Rules/GroupNumericLiterals.swift | 2 ++ Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift | 2 ++ Sources/SwiftFormat/Rules/NeverForceUnwrap.swift | 3 +++ Sources/SwiftFormat/Rules/NeverUseForceTry.swift | 2 ++ .../Rules/NeverUseImplicitlyUnwrappedOptionals.swift | 2 ++ .../Rules/NoAccessLevelOnExtensionDeclaration.swift | 3 +++ Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift | 2 ++ Sources/SwiftFormat/Rules/NoBlockComments.swift | 2 ++ .../SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift | 2 ++ .../Rules/NoEmptyTrailingClosureParentheses.swift | 2 ++ Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift | 2 ++ Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift | 2 ++ Sources/SwiftFormat/Rules/NoParensAroundConditions.swift | 2 ++ Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift | 1 + .../Rules/NoVoidReturnOnFunctionSignature.swift | 2 ++ Sources/SwiftFormat/Rules/OneCasePerLine.swift | 2 ++ .../SwiftFormat/Rules/OneVariableDeclarationPerLine.swift | 2 ++ .../Rules/OnlyOneTrailingClosureArgument.swift | 2 ++ Sources/SwiftFormat/Rules/OrderedImports.swift | 5 +++++ .../SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift | 2 ++ .../SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift | 2 ++ Sources/SwiftFormat/Rules/UseEarlyExits.swift | 2 ++ .../Rules/UseLetInEveryBoundCaseVariable.swift | 2 ++ Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift | 2 ++ .../SwiftFormat/Rules/UseSingleLinePropertyGetter.swift | 2 ++ Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift | 2 ++ .../Rules/UseTripleSlashForDocumentationComments.swift | 2 ++ Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift | 3 +++ .../SwiftFormat/Rules/ValidateDocumentationComments.swift | 8 ++++++++ .../AllPublicDeclarationsHaveDocumentationTests.swift | 2 +- .../Rules/AlwaysUseLowerCamelCaseTests.swift | 2 +- .../Rules/AmbiguousTrailingClosureOverloadTests.swift | 2 +- ...BeginDocumentationCommentWithOneLineSummaryTests.swift | 2 +- .../SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift | 2 +- .../Rules/DontRepeatTypeInStaticPropertiesTests.swift | 2 +- .../Rules/FileScopedDeclarationPrivacyTests.swift | 3 ++- Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift | 2 +- .../Rules/GroupNumericLiteralsTests.swift | 2 +- .../Rules/IdentifiersMustBeASCIITests.swift | 2 +- .../Rules/ImportsXCTestVisitorTests.swift | 3 ++- Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift | 2 +- Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift | 2 +- .../Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift | 2 +- .../Rules/NoAccessLevelOnExtensionDeclarationTests.swift | 2 +- .../Rules/NoAssignmentInExpressionsTests.swift | 2 +- Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift | 2 +- .../Rules/NoCasesWithOnlyFallthroughTests.swift | 2 +- .../Rules/NoEmptyTrailingClosureParenthesesTests.swift | 2 +- .../Rules/NoLabelsInCasePatternsTests.swift | 2 +- .../Rules/NoLeadingUnderscoresTests.swift | 2 +- .../Rules/NoParensAroundConditionsTests.swift | 2 +- .../Rules/NoVoidReturnOnFunctionSignatureTests.swift | 2 +- Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift | 2 +- .../Rules/OneVariableDeclarationPerLineTests.swift | 2 +- .../Rules/OnlyOneTrailingClosureArgumentTests.swift | 2 +- Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift | 2 +- .../Rules/ReturnVoidInsteadOfEmptyTupleTests.swift | 2 +- .../Rules/TypeNamesShouldBeCapitalizedTests.swift | 2 +- Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift | 2 +- .../Rules/UseLetInEveryBoundCaseVariableTests.swift | 2 +- .../Rules/UseShorthandTypeNamesTests.swift | 2 +- .../Rules/UseSingleLinePropertyGetterTests.swift | 2 +- .../Rules/UseSynthesizedInitializerTests.swift | 2 +- .../UseTripleSlashForDocumentationCommentsTests.swift | 2 +- .../Rules/UseWhereClausesInForLoopsTests.swift | 2 +- .../Rules/ValidateDocumentationCommentsTests.swift | 2 +- 74 files changed, 128 insertions(+), 37 deletions(-) diff --git a/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift index 7bef875af..ebd25bd48 100644 --- a/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift +++ b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift @@ -15,6 +15,7 @@ import SwiftSyntax /// All public or open declarations must have a top-level documentation comment. /// /// Lint: If a public declaration is missing a documentation comment, a lint error is raised. +@_spi(Rules) public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { /// Identifies this rule as being opt-in. While docs on most public declarations are beneficial, @@ -86,6 +87,7 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static func declRequiresComment(_ name: String) -> Finding.Message { "add a documentation comment for '\(name)'" } diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift index 652ee8b75..1d7493f26 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift @@ -20,6 +20,7 @@ import SwiftSyntax /// /// Lint: If an identifier contains underscores or begins with a capital letter, a lint error is /// raised. +@_spi(Rules) public final class AlwaysUseLowerCamelCase: SyntaxLintRule { /// Stores function decls that are test cases. private var testCaseFuncs = Set() @@ -213,6 +214,7 @@ extension ReturnClauseSyntax { } extension Finding.Message { + @_spi(Rules) public static func nameMustBeLowerCamelCase( _ name: String, description: String ) -> Finding.Message { diff --git a/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift b/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift index 1e448f8dc..d46a2ef36 100644 --- a/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift +++ b/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift @@ -16,6 +16,7 @@ import SwiftSyntax /// /// Lint: If two overloaded functions with one closure parameter appear in the same scope, a lint /// error is raised. +@_spi(Rules) public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { private func diagnoseBadOverloads(_ overloads: [String: [FunctionDeclSyntax]]) { @@ -72,10 +73,12 @@ public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static func ambiguousTrailingClosureOverload(_ decl: String) -> Finding.Message { "rename '\(decl)' so it is no longer ambiguous when called with a trailing closure" } + @_spi(Rules) public static func otherAmbiguousOverloadHere(_ decl: String) -> Finding.Message { "ambiguous overload '\(decl)' is here" } diff --git a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift index 07fb24602..4a7e3acf5 100644 --- a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift +++ b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift @@ -16,6 +16,7 @@ import SwiftSyntax /// All documentation comments must begin with a one-line summary of the declaration. /// /// Lint: If a comment does not begin with a single-line summary, a lint error is raised. +@_spi(Rules) public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { /// Unit tests can testably import this module and set this to true in order to force the rule @@ -194,12 +195,14 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static func terminateSentenceWithPeriod(_ text: Sentence) -> Finding.Message { "terminate this sentence with a period: \"\(text)\"" } + @_spi(Rules) public static func addBlankLineAfterFirstSentence(_ text: Sentence) -> Finding.Message { diff --git a/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift index dc8e6f737..4bef9c4c2 100644 --- a/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift @@ -17,6 +17,7 @@ import SwiftSyntax /// Lint: If a semicolon appears anywhere, a lint error is raised. /// /// Format: All semicolons will be replaced with line breaks. +@_spi(Rules) public final class DoNotUseSemicolons: SyntaxFormatRule { /// Creates a new version of the given node which doesn't contain any semicolons. The node's @@ -102,8 +103,10 @@ public final class DoNotUseSemicolons: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static let removeSemicolon: Finding.Message = "remove ';'" + @_spi(Rules) public static let removeSemicolonAndMove: Finding.Message = "remove ';' and move the next statement to a new line" } diff --git a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift index fc99c0aae..eca677c55 100644 --- a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift @@ -19,6 +19,7 @@ import SwiftSyntax /// `public class var redColor: UIColor` would trigger this rule. /// /// Lint: Static properties of a type that return that type will yield a lint error. +@_spi(Rules) public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { @@ -101,6 +102,7 @@ public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static func removeTypeFromName(name: String, type: Substring) -> Finding.Message { "remove the suffix '\(type)' from the name of the variable '\(name)'" } diff --git a/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift b/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift index 95bed05a8..7f2de8ae8 100644 --- a/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift +++ b/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift @@ -20,6 +20,7 @@ import SwiftSyntax /// /// Format: File-scoped declarations that have formal access opposite to the desired access level in /// the formatter's configuration will have their access level changed. +@_spi(Rules) public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { public override func visit(_ node: SourceFileSyntax) -> SourceFileSyntax { let newStatements = rewrittenCodeBlockItems(node.statements) @@ -169,9 +170,11 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static let replacePrivateWithFileprivate: Finding.Message = "replace 'private' with 'fileprivate' on file-scoped declarations" + @_spi(Rules) public static let replaceFileprivateWithPrivate: Finding.Message = "replace 'fileprivate' with 'private' on file-scoped declarations" } diff --git a/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift index 7361b7f9a..3e793e2a8 100644 --- a/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift @@ -19,6 +19,7 @@ import SwiftSyntax /// /// Format: Enums where all cases are `indirect` will be rewritten such that the enum is marked /// `indirect`, and each case is not. +@_spi(Rules) public final class FullyIndirectEnum: SyntaxFormatRule { public override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { @@ -109,6 +110,7 @@ public final class FullyIndirectEnum: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static func moveIndirectKeywordToEnumDecl(name: String) -> Finding.Message { "move 'indirect' before the enum declaration '\(name)' when all cases are indirect" } diff --git a/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift b/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift index 0c4a41bf8..555b3c75a 100644 --- a/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift +++ b/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift @@ -24,6 +24,7 @@ import SwiftSyntax /// TODO: Minimum numeric literal length bounds and numeric groupings have been selected arbitrarily; /// these could be reevaluated. /// TODO: Handle floating point literals. +@_spi(Rules) public final class GroupNumericLiterals: SyntaxFormatRule { public override func visit(_ node: IntegerLiteralExprSyntax) -> ExprSyntax { var originalDigits = node.literal.text @@ -82,6 +83,7 @@ public final class GroupNumericLiterals: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static func groupNumericLiteral(every stride: Int) -> Finding.Message { let ending = stride == 3 ? "rd" : "th" return "group numeric literal using '_' every \(stride)\(ending) number" diff --git a/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift b/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift index be87ee8a6..fb2222617 100644 --- a/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift +++ b/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift @@ -15,6 +15,7 @@ import SwiftSyntax /// All identifiers must be ASCII. /// /// Lint: If an identifier contains non-ASCII characters, a lint error is raised. +@_spi(Rules) public final class IdentifiersMustBeASCII: SyntaxLintRule { public override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { @@ -30,6 +31,7 @@ public final class IdentifiersMustBeASCII: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static func nonASCIICharsNotAllowed( _ invalidCharacters: [String], _ identifierName: String ) -> Finding.Message { diff --git a/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift index 770ade535..22e8a3513 100644 --- a/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift +++ b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift @@ -18,6 +18,7 @@ import SwiftSyntax /// * Contains the line `import XCTest` /// /// Lint: If a force unwrap is used, a lint warning is raised. +@_spi(Rules) public final class NeverForceUnwrap: SyntaxLintRule { /// Identifies this rule as being opt-in. While force unwrap is an unsafe pattern (i.e. it can @@ -49,10 +50,12 @@ public final class NeverForceUnwrap: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static func doNotForceUnwrap(name: String) -> Finding.Message { "do not force unwrap '\(name)'" } + @_spi(Rules) public static func doNotForceCast(name: String) -> Finding.Message { "do not force cast to '\(name)'" } diff --git a/Sources/SwiftFormat/Rules/NeverUseForceTry.swift b/Sources/SwiftFormat/Rules/NeverUseForceTry.swift index b33deb969..db15979c6 100644 --- a/Sources/SwiftFormat/Rules/NeverUseForceTry.swift +++ b/Sources/SwiftFormat/Rules/NeverUseForceTry.swift @@ -20,6 +20,7 @@ import SwiftSyntax /// Lint: Using `try!` results in a lint error. /// /// TODO: Create exception for NSRegularExpression +@_spi(Rules) public final class NeverUseForceTry: SyntaxLintRule { /// Identifies this rule as being opt-in. While force try is an unsafe pattern (i.e. it can @@ -43,5 +44,6 @@ public final class NeverUseForceTry: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static let doNotForceTry: Finding.Message = "do not use force try" } diff --git a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift index 266b208a5..6977be832 100644 --- a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -22,6 +22,7 @@ import SwiftSyntax /// TODO: Create exceptions for other UI elements (ex: viewDidLoad) /// /// Lint: Declaring a property with an implicitly unwrapped type yields a lint error. +@_spi(Rules) public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule { /// Identifies this rule as being opt-in. While accessing implicitly unwrapped optionals is an @@ -61,6 +62,7 @@ public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static func doNotUseImplicitUnwrapping(identifier: String) -> Finding.Message { "use '\(identifier)' or '\(identifier)?' instead of '\(identifier)!'" } diff --git a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift index 998da9cfc..b86124d0e 100644 --- a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift @@ -21,6 +21,7 @@ import SwiftSyntax /// `internal`, as that is the default access level) have the explicit access level removed. /// /// TODO: Find a better way to access modifiers and keyword tokens besides casting each declaration +@_spi(Rules) public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { public override func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax { @@ -95,10 +96,12 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static func removeRedundantAccessKeyword(name: String) -> Finding.Message { "remove redundant 'internal' access keyword from '\(name)'" } + @_spi(Rules) public static func moveAccessKeyword(keyword: String) -> Finding.Message { "move the '\(keyword)' access keyword to precede each member inside the extension" } diff --git a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift index 89306cf57..af760cfdb 100644 --- a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift @@ -23,6 +23,7 @@ import SwiftSyntax /// /// Format: A `return` statement containing an assignment expression is expanded into two separate /// statements. +@_spi(Rules) public final class NoAssignmentInExpressions: SyntaxFormatRule { public override func visit(_ node: InfixOperatorExprSyntax) -> ExprSyntax { // Diagnose any assignment that isn't directly a child of a `CodeBlockItem` (which would be the @@ -161,6 +162,7 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static let moveAssignmentToOwnStatement: Finding.Message = "move this assignment expression into its own statement" } diff --git a/Sources/SwiftFormat/Rules/NoBlockComments.swift b/Sources/SwiftFormat/Rules/NoBlockComments.swift index a22150e77..f4f00a4ce 100644 --- a/Sources/SwiftFormat/Rules/NoBlockComments.swift +++ b/Sources/SwiftFormat/Rules/NoBlockComments.swift @@ -15,6 +15,7 @@ import SwiftSyntax /// Block comments should be avoided in favor of line comments. /// /// Lint: If a block comment appears, a lint error is raised. +@_spi(Rules) public final class NoBlockComments: SyntaxLintRule { public override func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind { for triviaIndex in token.leadingTrivia.indices { @@ -28,6 +29,7 @@ public final class NoBlockComments: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static let avoidBlockComment: Finding.Message = "replace this block comment with line comments" } diff --git a/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift index b152e629d..7eb6a9b00 100644 --- a/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift +++ b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift @@ -18,6 +18,7 @@ import SwiftSyntax /// /// Format: The fallthrough `case` is added as a prefix to the next case unless the next case is /// `default`; in that case, the fallthrough `case` is deleted. +@_spi(Rules) public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { public override func visit(_ node: SwitchCaseListSyntax) -> SwitchCaseListSyntax { @@ -218,6 +219,7 @@ extension TriviaPiece { } extension Finding.Message { + @_spi(Rules) public static var collapseCase: Finding.Message { "combine this fallthrough-only 'case' and the following 'case' into a single 'case'" } diff --git a/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift b/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift index 96c6a7e80..0f6a0cbc0 100644 --- a/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift +++ b/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift @@ -18,6 +18,7 @@ import SwiftSyntax /// a lint error is raised. /// /// Format: Empty parentheses in function calls with trailing closures will be removed. +@_spi(Rules) public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { public override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax { @@ -51,6 +52,7 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static func removeEmptyTrailingParentheses(name: String) -> Finding.Message { "remove the empty parentheses following '\(name)'" } diff --git a/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift b/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift index fa05ce5c2..1bc5428a6 100644 --- a/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift +++ b/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift @@ -21,6 +21,7 @@ import SwiftSyntax /// binding identifier. /// /// Format: Redundant labels in case patterns are removed. +@_spi(Rules) public final class NoLabelsInCasePatterns: SyntaxFormatRule { public override func visit(_ node: SwitchCaseLabelSyntax) -> SwitchCaseLabelSyntax { var newCaseItems: [SwitchCaseItemSyntax] = [] @@ -70,6 +71,7 @@ public final class NoLabelsInCasePatterns: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static func removeRedundantLabel(name: String) -> Finding.Message { "remove the label '\(name)' from this 'case' pattern" } diff --git a/Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift b/Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift index 840b86204..e194d3faa 100644 --- a/Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift +++ b/Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift @@ -22,6 +22,7 @@ import SwiftSyntax /// sites. /// /// Lint: Declaring an identifier with a leading underscore yields a lint error. +@_spi(Rules) public final class NoLeadingUnderscores: SyntaxLintRule { /// Identifies this rule as being opt-in. While leading underscores aren't meant to be used in @@ -124,6 +125,7 @@ public final class NoLeadingUnderscores: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static func doNotStartWithUnderscore(identifier: String) -> Finding.Message { "remove the leading '_' from the name '\(identifier)'" } diff --git a/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift index 721dc3308..cd1906e76 100644 --- a/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift @@ -24,6 +24,7 @@ import SwiftSyntax /// Format: Parentheses around such expressions are removed, if they do not cause a parse ambiguity. /// Specifically, parentheses are allowed if and only if the expression contains a function /// call with a trailing closure. +@_spi(Rules) public final class NoParensAroundConditions: SyntaxFormatRule { private func extractExpr(_ tuple: TupleExprSyntax) -> ExprSyntax { assert(tuple.elements.count == 1) @@ -99,6 +100,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static let removeParensAroundExpression: Finding.Message = "remove the parentheses around this expression" } diff --git a/Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift b/Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift index 96ac6d3ab..63bf0a4ca 100644 --- a/Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift +++ b/Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift @@ -25,6 +25,7 @@ import SwiftSyntax /// `#colorLiteral(...)` becomes `UIColor(...)` /// /// Configuration: resolveAmbiguousColor +@_spi(Rules) public final class NoPlaygroundLiterals: SyntaxFormatRule { } diff --git a/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift b/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift index c6cc65f80..cda2ef613 100644 --- a/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift +++ b/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift @@ -18,6 +18,7 @@ import SwiftSyntax /// /// Format: Function declarations with explicit returns of `()` or `Void` will have their return /// signature stripped. +@_spi(Rules) public final class NoVoidReturnOnFunctionSignature: SyntaxFormatRule { /// Remove the `-> Void` return type for function signatures. Do not remove /// it for closure signatures, because that may introduce an ambiguity when closure signatures @@ -36,6 +37,7 @@ public final class NoVoidReturnOnFunctionSignature: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static func removeRedundantReturn(_ type: String) -> Finding.Message { "remove the explicit return type '\(type)' from this function" } diff --git a/Sources/SwiftFormat/Rules/OneCasePerLine.swift b/Sources/SwiftFormat/Rules/OneCasePerLine.swift index 2605d44e9..f67193cee 100644 --- a/Sources/SwiftFormat/Rules/OneCasePerLine.swift +++ b/Sources/SwiftFormat/Rules/OneCasePerLine.swift @@ -19,6 +19,7 @@ import SwiftSyntax /// /// Format: All case declarations with associated values or raw values will be moved to their own /// case declarations. +@_spi(Rules) public final class OneCasePerLine: SyntaxFormatRule { /// A state machine that collects case elements encountered during visitation and allows new case @@ -128,6 +129,7 @@ public final class OneCasePerLine: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static func moveAssociatedOrRawValueCase(name: String) -> Finding.Message { "move '\(name)' to its own 'case' declaration" } diff --git a/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift index e7843cbd7..002f2ab54 100644 --- a/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift +++ b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift @@ -21,6 +21,7 @@ import SwiftSyntax /// Format: If a variable declaration declares multiple variables, it will be /// split into multiple declarations, each declaring one of the variables, as /// long as the result would still be syntactically valid. +@_spi(Rules) public final class OneVariableDeclarationPerLine: SyntaxFormatRule { public override func visit(_ node: CodeBlockItemListSyntax) -> CodeBlockItemListSyntax { guard node.contains(where: codeBlockItemHasMultipleVariableBindings) else { @@ -73,6 +74,7 @@ public final class OneVariableDeclarationPerLine: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static func onlyOneVariableDeclaration(specifier: String) -> Finding.Message { "split this variable declaration to introduce only one variable per '\(specifier)'" } diff --git a/Sources/SwiftFormat/Rules/OnlyOneTrailingClosureArgument.swift b/Sources/SwiftFormat/Rules/OnlyOneTrailingClosureArgument.swift index 9d2440251..551ea81d6 100644 --- a/Sources/SwiftFormat/Rules/OnlyOneTrailingClosureArgument.swift +++ b/Sources/SwiftFormat/Rules/OnlyOneTrailingClosureArgument.swift @@ -16,6 +16,7 @@ import SwiftSyntax /// /// Lint: If a function call with a trailing closure also contains a non-trailing closure argument, /// a lint error is raised. +@_spi(Rules) public final class OnlyOneTrailingClosureArgument: SyntaxLintRule { public override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { @@ -29,6 +30,7 @@ public final class OnlyOneTrailingClosureArgument: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static let removeTrailingClosure: Finding.Message = "revise this function call to avoid using both closure arguments and a trailing closure" } diff --git a/Sources/SwiftFormat/Rules/OrderedImports.swift b/Sources/SwiftFormat/Rules/OrderedImports.swift index dbd60c3f7..55d1ffed8 100644 --- a/Sources/SwiftFormat/Rules/OrderedImports.swift +++ b/Sources/SwiftFormat/Rules/OrderedImports.swift @@ -22,6 +22,7 @@ import SwiftSyntax /// raised. /// /// Format: Imports will be reordered and grouped at the top of the file. +@_spi(Rules) public final class OrderedImports: SyntaxFormatRule { public override func visit(_ node: SourceFileSyntax) -> SourceFileSyntax { @@ -569,13 +570,17 @@ extension Line: CustomDebugStringConvertible { } extension Finding.Message { + @_spi(Rules) public static let placeAtTopOfFile: Finding.Message = "place imports at the top of the file" + @_spi(Rules) public static func groupImports(before: LineType, after: LineType) -> Finding.Message { "place \(before) imports before \(after) imports" } + @_spi(Rules) public static let removeDuplicateImport: Finding.Message = "remove this duplicate import" + @_spi(Rules) public static let sortImports: Finding.Message = "sort import statements lexicographically" } diff --git a/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift index 8253a6af9..eed4060c6 100644 --- a/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift @@ -20,6 +20,7 @@ import SwiftSyntax /// Lint: Returning `()` in a signature yields a lint error. /// /// Format: `-> ()` is replaced with `-> Void` +@_spi(Rules) public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { public override func visit(_ node: FunctionTypeSyntax) -> TypeSyntax { guard let returnType = node.returnClause.type.as(TupleTypeSyntax.self), @@ -110,5 +111,6 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static let returnVoid: Finding.Message = "replace '()' with 'Void'" } diff --git a/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift b/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift index 2d84fadcc..21c441993 100644 --- a/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift +++ b/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift @@ -15,6 +15,7 @@ import SwiftSyntax /// `struct`, `class`, `enum` and `protocol` declarations should have a capitalized name. /// /// Lint: Types with un-capitalized names will yield a lint error. +@_spi(Rules) public final class TypeNamesShouldBeCapitalized : SyntaxLintRule { public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseNameConventionMismatch(node, name: node.name) @@ -61,6 +62,7 @@ public final class TypeNamesShouldBeCapitalized : SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static func capitalizeTypeName(name: String) -> Finding.Message { var capitalized = name let leadingUnderscores = capitalized.prefix { $0 == "_" } diff --git a/Sources/SwiftFormat/Rules/UseEarlyExits.swift b/Sources/SwiftFormat/Rules/UseEarlyExits.swift index 838bf80f3..9f4d15334 100644 --- a/Sources/SwiftFormat/Rules/UseEarlyExits.swift +++ b/Sources/SwiftFormat/Rules/UseEarlyExits.swift @@ -41,6 +41,7 @@ import SwiftSyntax /// /// Format: `if ... else { return/throw/break/continue }` constructs will be replaced with /// equivalent `guard ... else { return/throw/break/continue }` constructs. +@_spi(Rules) public final class UseEarlyExits: SyntaxFormatRule { /// Identifies this rule as being opt-in. This rule is experimental and not yet stable enough to @@ -106,6 +107,7 @@ public final class UseEarlyExits: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static let useGuardStatement: Finding.Message = "replace the 'if/else' block with a 'guard' statement containing the early exit" } diff --git a/Sources/SwiftFormat/Rules/UseLetInEveryBoundCaseVariable.swift b/Sources/SwiftFormat/Rules/UseLetInEveryBoundCaseVariable.swift index c45c96878..442768cd0 100644 --- a/Sources/SwiftFormat/Rules/UseLetInEveryBoundCaseVariable.swift +++ b/Sources/SwiftFormat/Rules/UseLetInEveryBoundCaseVariable.swift @@ -18,6 +18,7 @@ import SwiftSyntax /// `case .identifier(let x, let y)` instead. /// /// Lint: `case let .identifier(...)` will yield a lint error. +@_spi(Rules) public final class UseLetInEveryBoundCaseVariable: SyntaxLintRule { public override func visit(_ node: ValueBindingPatternSyntax) -> SyntaxVisitorContinueKind { @@ -65,6 +66,7 @@ public final class UseLetInEveryBoundCaseVariable: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static let useLetInBoundCaseVariables: Finding.Message = "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" } diff --git a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift index 55ea1289b..4cf30d840 100644 --- a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift @@ -19,6 +19,7 @@ import SwiftSyntax /// /// Format: Where possible, shorthand types replace long form types; e.g. `Array` is /// converted to `[Element]`. +@_spi(Rules) public final class UseShorthandTypeNames: SyntaxFormatRule { public override func visit(_ node: IdentifierTypeSyntax) -> TypeSyntax { @@ -577,6 +578,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static func useTypeShorthand(type: String) -> Finding.Message { "use shorthand syntax for this '\(type)' type" } diff --git a/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift index 0b7b6e632..bdae9ffa6 100644 --- a/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift @@ -17,6 +17,7 @@ import SwiftSyntax /// Lint: Read-only computed properties with explicit `get` blocks yield a lint error. /// /// Format: Explicit `get` blocks are rendered implicit by removing the `get`. +@_spi(Rules) public final class UseSingleLinePropertyGetter: SyntaxFormatRule { public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax { @@ -40,6 +41,7 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static let removeExtraneousGetBlock: Finding.Message = "remove 'get {...}' around the accessor and move its body directly into the computed property" } diff --git a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift index 501192d6b..5175b6cf7 100644 --- a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift @@ -20,6 +20,7 @@ import SwiftSyntax /// /// Lint: (Non-public) memberwise initializers with the same structure as the synthesized /// initializer will yield a lint error. +@_spi(Rules) public final class UseSynthesizedInitializer: SyntaxLintRule { public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { @@ -184,6 +185,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { } extension Finding.Message { + @_spi(Rules) public static let removeRedundantInitializer: Finding.Message = "remove this explicit initializer, which is identical to the compiler-synthesized initializer" } diff --git a/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift b/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift index 5b07de095..9fb325503 100644 --- a/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift +++ b/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift @@ -22,6 +22,7 @@ import SwiftSyntax /// Format: If a doc block comment appears on its own on a line, or if a doc block comment spans /// multiple lines without appearing on the same line as code, it will be replaced with /// multiple doc line comments. +@_spi(Rules) public final class UseTripleSlashForDocumentationComments: SyntaxFormatRule { public override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax { return convertDocBlockCommentToDocLineComment(DeclSyntax(node)) @@ -106,6 +107,7 @@ public final class UseTripleSlashForDocumentationComments: SyntaxFormatRule { } extension Finding.Message { + @_spi(Rules) public static let avoidDocBlockComment: Finding.Message = "replace documentation block comments with documentation line comments" } diff --git a/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift index 26c30554f..a326c8bb6 100644 --- a/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift @@ -18,6 +18,7 @@ import SwiftSyntax /// /// Format: `for` loops that consist of a single `if` statement have the conditional of that /// statement factored out to a `where` clause. +@_spi(Rules) public final class UseWhereClausesInForLoops: SyntaxFormatRule { /// Identifies this rule as being opt-in. This rule is experimental and not yet stable enough to @@ -119,9 +120,11 @@ fileprivate func updateWithWhereCondition( } extension Finding.Message { + @_spi(Rules) public static let useWhereInsteadOfIf: Finding.Message = "replace this 'if' statement with a 'where' clause" + @_spi(Rules) public static let useWhereInsteadOfGuard: Finding.Message = "replace this 'guard' statement with a 'where' clause" } diff --git a/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift index 5886f3cdc..9f8883762 100644 --- a/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift @@ -20,6 +20,7 @@ import SwiftSyntax /// /// Lint: Documentation comments that are incomplete (e.g. missing parameter documentation) or /// invalid (uses `Parameters` when there is only one parameter) will yield a lint error. +@_spi(Rules) public final class ValidateDocumentationComments: SyntaxLintRule { /// Identifies this rule as being opt-in. Accurate and complete documentation comments are /// important, but this rule isn't able to handle situations where portions of documentation are @@ -166,31 +167,38 @@ fileprivate func parametersAreEqual( } extension Finding.Message { + @_spi(Rules) public static func documentReturnValue(funcName: String) -> Finding.Message { "add a 'Returns:' section to document the return value of '\(funcName)'" } + @_spi(Rules) public static func removeReturnComment(funcName: String) -> Finding.Message { "remove the 'Returns:' section of '\(funcName)'; it does not return a value" } + @_spi(Rules) public static func parametersDontMatch(funcName: String) -> Finding.Message { "change the parameters of the documentation of '\(funcName)' to match its parameters" } + @_spi(Rules) public static let useSingularParameter: Finding.Message = "replace the plural 'Parameters:' section with a singular inline 'Parameter' section" + @_spi(Rules) public static let usePluralParameters: Finding.Message = """ replace the singular inline 'Parameter' section with a plural 'Parameters:' section \ that has the parameters nested inside it """ + @_spi(Rules) public static func removeThrowsComment(funcName: String) -> Finding.Message { "remove the 'Throws:' sections of '\(funcName)'; it does not throw any errors" } + @_spi(Rules) public static func documentErrorsThrown(funcName: String) -> Finding.Message { "add a 'Throws:' section to document the errors thrown by '\(funcName)'" } diff --git a/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift b/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift index b7faddd74..db9317a2c 100644 --- a/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift +++ b/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCase { func testPublicDeclsWithoutDocs() { diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift index 718800b3e..bde50ceeb 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class AlwaysUseLowerCamelCaseTests: LintOrFormatRuleTestCase { override func setUp() { diff --git a/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift b/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift index 169d5b916..1eb297688 100644 --- a/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift +++ b/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class AmbiguousTrailingClosureOverloadTests: LintOrFormatRuleTestCase { func testAmbiguousOverloads() { diff --git a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift index 7a31691d7..72ad56346 100644 --- a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift +++ b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTestCase { override func setUp() { diff --git a/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift b/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift index 554a0592d..7b19094c0 100644 --- a/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift +++ b/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { func testSemicolonUse() { diff --git a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift index c4ba2bf81..d39a0b148 100644 --- a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift +++ b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase { func testRepetitiveProperties() { diff --git a/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift b/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift index 42859d8a4..7be246ede 100644 --- a/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift +++ b/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift @@ -1,7 +1,8 @@ -import SwiftFormat import SwiftFormatConfiguration import SwiftSyntax +@_spi(Rules) import SwiftFormat + private typealias TestConfiguration = ( original: String, desired: FileScopedDeclarationPrivacyConfiguration.AccessLevel, diff --git a/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift b/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift index 5f2424b38..a7d4881a7 100644 --- a/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift +++ b/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat class FullyIndirectEnumTests: LintOrFormatRuleTestCase { func testAllIndirectCases() { diff --git a/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift b/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift index 088da634f..ce2109708 100644 --- a/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift +++ b/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class GroupNumericLiteralsTests: LintOrFormatRuleTestCase { func testNumericGrouping() { diff --git a/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift b/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift index 390896f92..546b6bfe9 100644 --- a/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift +++ b/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class IdentifiersMustBeASCIITests: LintOrFormatRuleTestCase { func testInvalidIdentifiers() { diff --git a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift index a2a0b08f8..4ad1aa6e2 100644 --- a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift +++ b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift @@ -1,8 +1,9 @@ -import SwiftFormat import SwiftParser import XCTest import _SwiftFormatTestSupport +@_spi(Rules) import SwiftFormat + class ImportsXCTestVisitorTests: DiagnosingTestCase { func testDoesNotImportXCTest() throws { XCTAssertEqual( diff --git a/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift b/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift index a9a8005e2..2a4e13652 100644 --- a/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NeverForceUnwrapTests: LintOrFormatRuleTestCase { func testUnsafeUnwrap() { diff --git a/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift index 97b0fbaf8..a4bf0e128 100644 --- a/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NeverUseForceTryTests: LintOrFormatRuleTestCase { func testInvalidTryExpression() { diff --git a/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift index 4a7002080..15e4c1136 100644 --- a/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NeverUseImplicitlyUnwrappedOptionalsTests: LintOrFormatRuleTestCase { func testInvalidVariableUnwrapping() { diff --git a/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift b/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift index dd8e49129..8f1b49175 100644 --- a/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { func testExtensionDeclarationAccessLevel() { diff --git a/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift b/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift index 30556d0cd..e635a0fbc 100644 --- a/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { func testAssignmentInExpressionContextIsDiagnosed() { diff --git a/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift b/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift index 39831e668..7ec6dd39d 100644 --- a/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NoBlockCommentsTests: LintOrFormatRuleTestCase { func testDiagnoseBlockComments() { diff --git a/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift b/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift index 1d69a5320..b745a3d66 100644 --- a/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { func testFallthroughCases() { diff --git a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift index fee720585..625e8a418 100644 --- a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NoEmptyTrailingClosureParenthesesTests: LintOrFormatRuleTestCase { func testInvalidEmptyParenTrailingClosure() { diff --git a/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift b/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift index aa689d901..f22682ce1 100644 --- a/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NoLabelsInCasePatternsTests: LintOrFormatRuleTestCase { func testRedundantCaseLabels() { diff --git a/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift b/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift index b29179a66..286375600 100644 --- a/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NoLeadingUnderscoresTests: LintOrFormatRuleTestCase { func testVars() { diff --git a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift index c524c6a74..62fec7da1 100644 --- a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { func testParensAroundConditions() { diff --git a/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift b/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift index b95d82764..daff3b2dd 100644 --- a/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class NoVoidReturnOnFunctionSignatureTests: LintOrFormatRuleTestCase { func testVoidReturns() { diff --git a/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift b/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift index 4e4da7098..93397bf53 100644 --- a/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift +++ b/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class OneCasePerLineTests: LintOrFormatRuleTestCase { diff --git a/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift b/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift index 99d836900..d9086df45 100644 --- a/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift +++ b/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { func testMultipleVariableBindings() { diff --git a/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift b/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift index 4fd5e444a..976687220 100644 --- a/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift +++ b/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class OnlyOneTrailingClosureArgumentTests: LintOrFormatRuleTestCase { func testInvalidTrailingClosureCall() { diff --git a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift index 5b5bd7a75..cc0af6a80 100644 --- a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class OrderedImportsTests: LintOrFormatRuleTestCase { func testInvalidImportsOrder() { diff --git a/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift b/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift index a35805185..27fc788fd 100644 --- a/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift +++ b/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class ReturnVoidInsteadOfEmptyTupleTests: LintOrFormatRuleTestCase { func testBasic() { diff --git a/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift b/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift index 255bdd3f3..a28e40ddc 100644 --- a/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift +++ b/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { func testConstruction() { diff --git a/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift b/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift index e988701cf..279dddf2b 100644 --- a/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class UseEarlyExitsTests: LintOrFormatRuleTestCase { func testBasicIfElse() { diff --git a/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift b/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift index 998fc06da..b1ad1df87 100644 --- a/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class UseLetInEveryBoundCaseVariableTests: LintOrFormatRuleTestCase { override func setUp() { diff --git a/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift b/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift index 29e35dd89..bc856172d 100644 --- a/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { func testNamesInTypeContextsAreShortened() { diff --git a/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift b/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift index cf4f286d2..58ddf252c 100644 --- a/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class UseSingleLinePropertyGetterTests: LintOrFormatRuleTestCase { func testMultiLinePropertyGetter() { diff --git a/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift b/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift index 548fd93ce..d137b1d97 100644 --- a/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { override func setUp() { diff --git a/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift index 4e817ef72..7bc8b60f6 100644 --- a/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCase { func testRemoveDocBlockComments() { diff --git a/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift b/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift index 34e271456..d88153267 100644 --- a/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class UseWhereClausesInForLoopsTests: LintOrFormatRuleTestCase { func testForLoopWhereClauses() { diff --git a/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift index ca0807e58..659809473 100644 --- a/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift @@ -1,4 +1,4 @@ -import SwiftFormat +@_spi(Rules) import SwiftFormat final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { override func setUp() { From 50eecb7d5f47e13e8d8036e46bed1be3d47186ce Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 15 Aug 2023 15:12:55 -0400 Subject: [PATCH 094/332] Reduce visibility of some things to `internal`. These changes are only the ones that don't cause compilation errors. --- Sources/SwiftFormat/Core/Context.swift | 16 +++++------ .../Core/Finding+Convenience.swift | 2 +- Sources/SwiftFormat/Core/FindingEmitter.swift | 2 +- .../Core/SyntaxProtocol+Convenience.swift | 6 ++-- .../SwiftFormat/Core/Trivia+Convenience.swift | 28 +++++++++---------- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Sources/SwiftFormat/Core/Context.swift b/Sources/SwiftFormat/Core/Context.swift index 0d9e6d1dd..e740c8b7e 100644 --- a/Sources/SwiftFormat/Core/Context.swift +++ b/Sources/SwiftFormat/Core/Context.swift @@ -37,28 +37,28 @@ public final class Context { } /// The configuration for this run of the pipeline, provided by a configuration JSON file. - public let configuration: Configuration + let configuration: Configuration /// Defines the operators and their precedence relationships that were used during parsing. - public let operatorTable: OperatorTable + let operatorTable: OperatorTable /// Emits findings to the finding consumer. - public let findingEmitter: FindingEmitter + let findingEmitter: FindingEmitter /// The URL of the file being linted or formatted. - public let fileURL: URL + let fileURL: URL /// Indicates whether the file is known to import XCTest. public var importsXCTest: XCTestImportState /// An object that converts `AbsolutePosition` values to `SourceLocation` values. - public let sourceLocationConverter: SourceLocationConverter + let sourceLocationConverter: SourceLocationConverter /// Contains the rules have been disabled by comments for certain line numbers. - public let ruleMask: RuleMask + let ruleMask: RuleMask /// Contains all the available rules' names associated to their types' object identifiers. - public let ruleNameCache: [ObjectIdentifier: String] + let ruleNameCache: [ObjectIdentifier: String] /// Creates a new Context with the provided configuration, diagnostic engine, and file URL. public init( @@ -87,7 +87,7 @@ public final class Context { /// Given a rule's name and the node it is examining, determine if the rule is disabled at this /// location or not. - public func isRuleEnabled(_ rule: R.Type, node: Syntax) -> Bool { + func isRuleEnabled(_ rule: R.Type, node: Syntax) -> Bool { let loc = node.startLocation(converter: self.sourceLocationConverter) assert( diff --git a/Sources/SwiftFormat/Core/Finding+Convenience.swift b/Sources/SwiftFormat/Core/Finding+Convenience.swift index 3962c7265..b532e815d 100644 --- a/Sources/SwiftFormat/Core/Finding+Convenience.swift +++ b/Sources/SwiftFormat/Core/Finding+Convenience.swift @@ -14,7 +14,7 @@ import SwiftSyntax extension Finding.Location { /// Creates a new `Finding.Location` by converting the given `SourceLocation` from `SwiftSyntax`. - public init(_ sourceLocation: SourceLocation) { + init(_ sourceLocation: SourceLocation) { self.init(file: sourceLocation.file, line: sourceLocation.line, column: sourceLocation.column) } } diff --git a/Sources/SwiftFormat/Core/FindingEmitter.swift b/Sources/SwiftFormat/Core/FindingEmitter.swift index e425448fd..06264c6c9 100644 --- a/Sources/SwiftFormat/Core/FindingEmitter.swift +++ b/Sources/SwiftFormat/Core/FindingEmitter.swift @@ -19,7 +19,7 @@ /// If the consumer function is nil, then the `emit` function is a no-op. This allows callers, such /// as lint/format rules and the pretty-printer, to emit findings unconditionally, without wrapping /// each call in a check about whether the client is interested in receiving those findings or not. -public final class FindingEmitter { +final class FindingEmitter { /// An optional function that will be called and passed a finding each time one is emitted. private let consumer: ((Finding) -> Void)? diff --git a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift index 415c8d7f6..8d30d38d1 100644 --- a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift +++ b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift @@ -24,7 +24,7 @@ extension SyntaxProtocol { /// - Parameter index: The index of the trivia piece in the leading trivia whose position should /// be returned. /// - Returns: The absolute position of the trivia piece. - public func position(ofLeadingTriviaAt index: Trivia.Index) -> AbsolutePosition { + func position(ofLeadingTriviaAt index: Trivia.Index) -> AbsolutePosition { guard leadingTrivia.indices.contains(index) else { preconditionFailure("Index was out of bounds in the node's leading trivia.") } @@ -50,7 +50,7 @@ extension SyntaxProtocol { /// - converter: The `SourceLocationConverter` that was previously initialized using the root /// tree of this node. /// - Returns: The source location of the trivia piece. - public func startLocation( + func startLocation( ofLeadingTriviaAt index: Trivia.Index, converter: SourceLocationConverter ) -> SourceLocation { @@ -60,7 +60,7 @@ extension SyntaxProtocol { extension SyntaxCollection { /// The first element in the syntax collection if it is the *only* element, or nil otherwise. - public var firstAndOnly: Element? { + var firstAndOnly: Element? { var iterator = makeIterator() guard let first = iterator.next() else { return nil } guard iterator.next() == nil else { return nil } diff --git a/Sources/SwiftFormat/Core/Trivia+Convenience.swift b/Sources/SwiftFormat/Core/Trivia+Convenience.swift index ab56031a2..36c5851b2 100644 --- a/Sources/SwiftFormat/Core/Trivia+Convenience.swift +++ b/Sources/SwiftFormat/Core/Trivia+Convenience.swift @@ -13,7 +13,7 @@ import SwiftSyntax extension Trivia { - public var numberOfComments: Int { + var numberOfComments: Int { var count = 0 for piece in self { switch piece { @@ -27,7 +27,7 @@ extension Trivia { } /// Returns whether the trivia contains at least 1 `lineComment`. - public var hasLineComment: Bool { + var hasLineComment: Bool { return self.contains { if case .lineComment = $0 { return true } return false @@ -35,7 +35,7 @@ extension Trivia { } /// Returns this set of trivia, without any whitespace characters. - public func withoutSpaces() -> Trivia { + func withoutSpaces() -> Trivia { return Trivia( pieces: filter { if case .spaces = $0 { return false } @@ -45,7 +45,7 @@ extension Trivia { } /// Returns this set of trivia, without any leading spaces. - public func withoutLeadingSpaces() -> Trivia { + func withoutLeadingSpaces() -> Trivia { return Trivia( pieces: Array(drop { if case .spaces = $0 { return false } @@ -55,7 +55,7 @@ extension Trivia { } /// Returns this set of trivia, without any newlines. - public func withoutNewlines() -> Trivia { + func withoutNewlines() -> Trivia { return Trivia( pieces: filter { if case .newlines = $0 { return false } @@ -66,7 +66,7 @@ extension Trivia { /// Returns this trivia, excluding the last newline and anything following it. /// /// If there is no newline in the trivia, it is returned unmodified. - public func withoutLastLine() -> Trivia { + func withoutLastLine() -> Trivia { var maybeLastNewlineOffset: Int? = nil for (offset, piece) in self.enumerated() { switch piece { @@ -82,30 +82,30 @@ extension Trivia { /// Returns this set of trivia, with all spaces removed except for one at the /// end. - public func withOneTrailingSpace() -> Trivia { + func withOneTrailingSpace() -> Trivia { return withoutSpaces() + .spaces(1) } /// Returns this set of trivia, with all spaces removed except for one at the /// beginning. - public func withOneLeadingSpace() -> Trivia { + func withOneLeadingSpace() -> Trivia { return .spaces(1) + withoutSpaces() } /// Returns this set of trivia, with all newlines removed except for one. - public func withOneLeadingNewline() -> Trivia { + func withOneLeadingNewline() -> Trivia { return .newlines(1) + withoutNewlines() } /// Returns this set of trivia, with all newlines removed except for one. - public func withOneTrailingNewline() -> Trivia { + func withOneTrailingNewline() -> Trivia { return withoutNewlines() + .newlines(1) } /// Walks through trivia looking for multiple separate trivia entities with /// the same base kind, and condenses them. /// `[.spaces(1), .spaces(2)]` becomes `[.spaces(3)]`. - public func condensed() -> Trivia { + func condensed() -> Trivia { guard var prev = first else { return self } var pieces = [TriviaPiece]() for piece in dropFirst() { @@ -136,7 +136,7 @@ extension Trivia { } /// Returns `true` if this trivia contains any newlines. - public var containsNewlines: Bool { + var containsNewlines: Bool { return contains( where: { if case .newlines = $0 { return true } @@ -145,7 +145,7 @@ extension Trivia { } /// Returns `true` if this trivia contains any spaces. - public var containsSpaces: Bool { + var containsSpaces: Bool { return contains( where: { if case .spaces = $0 { return true } @@ -156,7 +156,7 @@ extension Trivia { /// Returns `true` if this trivia contains any backslahes (used for multiline string newline /// suppression). - public var containsBackslashes: Bool { + var containsBackslashes: Bool { return contains( where: { if case .backslashes = $0 { return true } From ca113c0541046537b120083f0723d10fc131afa8 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 15 Aug 2023 15:15:08 -0400 Subject: [PATCH 095/332] Move `Finding` and `FindingCategorizing` to the API folder. These are part of the public API; they were originally in `SwiftFormatCore` but reexported by `SwiftFormat`. --- Sources/SwiftFormat/{Core => API}/Finding.swift | 0 Sources/SwiftFormat/{Core => API}/FindingCategorizing.swift | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Sources/SwiftFormat/{Core => API}/Finding.swift (100%) rename Sources/SwiftFormat/{Core => API}/FindingCategorizing.swift (100%) diff --git a/Sources/SwiftFormat/Core/Finding.swift b/Sources/SwiftFormat/API/Finding.swift similarity index 100% rename from Sources/SwiftFormat/Core/Finding.swift rename to Sources/SwiftFormat/API/Finding.swift diff --git a/Sources/SwiftFormat/Core/FindingCategorizing.swift b/Sources/SwiftFormat/API/FindingCategorizing.swift similarity index 100% rename from Sources/SwiftFormat/Core/FindingCategorizing.swift rename to Sources/SwiftFormat/API/FindingCategorizing.swift From 69d408498acddc55a876edbfcb4c7c8409e96a14 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 15 Aug 2023 16:36:14 -0400 Subject: [PATCH 096/332] Finish adding SPIs and lowering access levels. --- Sources/SwiftFormat/Core/Context.swift | 1 + Sources/SwiftFormat/Core/DocumentationComment.swift | 1 + Sources/SwiftFormat/Core/DocumentationCommentText.swift | 1 + Sources/SwiftFormat/Core/ImportsXCTestVisitor.swift | 1 + Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift | 1 + Sources/SwiftFormat/Core/Rule.swift | 1 + Sources/SwiftFormat/Core/RuleMask.swift | 1 + Sources/SwiftFormat/Core/RuleNameCache+Generated.swift | 1 + Sources/SwiftFormat/Core/RuleState.swift | 1 + Sources/SwiftFormat/Core/SyntaxFormatRule.swift | 7 ++++--- Sources/SwiftFormat/Core/SyntaxLintRule.swift | 5 +++-- Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift | 1 + Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift | 1 + Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift | 4 +++- Sources/generate-pipeline/RuleCollector.swift | 3 ++- Sources/generate-pipeline/RuleNameCacheGenerator.swift | 1 + .../WhitespaceLinterPerformanceTests.swift | 5 +++-- .../SwiftFormatTests/Core/DocumentationCommentTests.swift | 3 ++- .../Core/DocumentationCommentTextTests.swift | 3 ++- Tests/SwiftFormatTests/Core/RuleMaskTests.swift | 3 ++- .../SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift | 5 +++-- .../SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift | 5 +++-- .../SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift | 4 ++-- .../SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift | 5 +++-- 24 files changed, 44 insertions(+), 20 deletions(-) diff --git a/Sources/SwiftFormat/Core/Context.swift b/Sources/SwiftFormat/Core/Context.swift index e740c8b7e..e0b15ab9b 100644 --- a/Sources/SwiftFormat/Core/Context.swift +++ b/Sources/SwiftFormat/Core/Context.swift @@ -20,6 +20,7 @@ import SwiftParser /// /// Specifically, it is the container for the shared configuration, diagnostic consumer, and URL of /// the current file. +@_spi(Rules) public final class Context { /// Tracks whether `XCTest` has been imported so that certain logic can be modified for files that diff --git a/Sources/SwiftFormat/Core/DocumentationComment.swift b/Sources/SwiftFormat/Core/DocumentationComment.swift index f4f13f3f3..4aa0d601b 100644 --- a/Sources/SwiftFormat/Core/DocumentationComment.swift +++ b/Sources/SwiftFormat/Core/DocumentationComment.swift @@ -19,6 +19,7 @@ import SwiftSyntax /// also the nested information that can be provided on a parameter. For example, when a parameter /// is a function type, it can provide not only a brief summary but also its own parameter and /// return value descriptions. +@_spi(Testing) public struct DocumentationComment { /// A description of a parameter in a documentation comment. public struct Parameter { diff --git a/Sources/SwiftFormat/Core/DocumentationCommentText.swift b/Sources/SwiftFormat/Core/DocumentationCommentText.swift index ca69dc416..335bbbab8 100644 --- a/Sources/SwiftFormat/Core/DocumentationCommentText.swift +++ b/Sources/SwiftFormat/Core/DocumentationCommentText.swift @@ -17,6 +17,7 @@ import SwiftSyntax /// This type should be used when only the text of the comment is important, not the Markdown /// structural organization. It automatically handles trimming leading indentation from comments as /// well as "ASCII art" in block comments (i.e., leading asterisks on each line). +@_spi(Testing) public struct DocumentationCommentText { /// Denotes the kind of punctuation used to introduce the comment. public enum Introducer { diff --git a/Sources/SwiftFormat/Core/ImportsXCTestVisitor.swift b/Sources/SwiftFormat/Core/ImportsXCTestVisitor.swift index e5d4189aa..ed9e9750f 100644 --- a/Sources/SwiftFormat/Core/ImportsXCTestVisitor.swift +++ b/Sources/SwiftFormat/Core/ImportsXCTestVisitor.swift @@ -52,6 +52,7 @@ private class ImportsXCTestVisitor: SyntaxVisitor { /// - Parameters: /// - context: The context information of the target source file. /// - sourceFile: The file to be visited. +@_spi(Testing) public func setImportsXCTest(context: Context, sourceFile: SourceFileSyntax) { guard context.importsXCTest == .notDetermined else { return } let visitor = ImportsXCTestVisitor(context: context) diff --git a/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift b/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift index 6fc9365c3..83b5e5ca7 100644 --- a/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift +++ b/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift @@ -5,6 +5,7 @@ import SwiftSyntax /// /// Eventually we should get rid of this and update the core formatting code to adjust to the new /// behavior, but this workaround lets us keep the current implementation without larger changes. +@_spi(Testing) public func restoringLegacyTriviaBehavior(_ sourceFile: SourceFileSyntax) -> SourceFileSyntax { return LegacyTriviaBehaviorRewriter().visit(sourceFile) } diff --git a/Sources/SwiftFormat/Core/Rule.swift b/Sources/SwiftFormat/Core/Rule.swift index aa596616a..8e7d3e3b1 100644 --- a/Sources/SwiftFormat/Core/Rule.swift +++ b/Sources/SwiftFormat/Core/Rule.swift @@ -14,6 +14,7 @@ import Foundation import SwiftSyntax /// A Rule is a linting or formatting pass that executes in a given context. +@_spi(Rules) public protocol Rule { /// The context in which the rule is executed. var context: Context { get } diff --git a/Sources/SwiftFormat/Core/RuleMask.swift b/Sources/SwiftFormat/Core/RuleMask.swift index b18239b02..b08a6942b 100644 --- a/Sources/SwiftFormat/Core/RuleMask.swift +++ b/Sources/SwiftFormat/Core/RuleMask.swift @@ -37,6 +37,7 @@ import SwiftSyntax /// /// The rules themselves reference RuleMask to see if it is disabled for the line it is currently /// examining. +@_spi(Testing) public class RuleMask { /// Stores the source ranges in which all rules are ignored. private var allRulesIgnoredRanges: [SourceRange] = [] diff --git a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift index a5adbd68f..2e8f3825a 100644 --- a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift @@ -13,6 +13,7 @@ // This file is automatically generated with generate-pipeline. Do Not Edit! /// By default, the `Rule.ruleName` should be the name of the implementing rule type. +@_spi(Testing) public let ruleNameCache: [ObjectIdentifier: String] = [ ObjectIdentifier(AllPublicDeclarationsHaveDocumentation.self): "AllPublicDeclarationsHaveDocumentation", ObjectIdentifier(AlwaysUseLowerCamelCase.self): "AlwaysUseLowerCamelCase", diff --git a/Sources/SwiftFormat/Core/RuleState.swift b/Sources/SwiftFormat/Core/RuleState.swift index 97b49f280..13be8e4da 100644 --- a/Sources/SwiftFormat/Core/RuleState.swift +++ b/Sources/SwiftFormat/Core/RuleState.swift @@ -12,6 +12,7 @@ /// The enablement of a lint/format rule based on the presence or absence of comment directives in /// the source file. +@_spi(Testing) public enum RuleState { /// There is no explicit information in the source file about whether the rule should be enabled diff --git a/Sources/SwiftFormat/Core/SyntaxFormatRule.swift b/Sources/SwiftFormat/Core/SyntaxFormatRule.swift index 4ceb5ce61..767e59fcf 100644 --- a/Sources/SwiftFormat/Core/SyntaxFormatRule.swift +++ b/Sources/SwiftFormat/Core/SyntaxFormatRule.swift @@ -13,10 +13,11 @@ import SwiftSyntax /// A rule that both formats and lints a given file. -open class SyntaxFormatRule: SyntaxRewriter, Rule { +@_spi(Rules) +public class SyntaxFormatRule: SyntaxRewriter, Rule { /// Whether this rule is opt-in, meaning it's disabled by default. Rules are opt-out unless they /// override this property. - open class var isOptIn: Bool { + public class var isOptIn: Bool { return false } @@ -28,7 +29,7 @@ open class SyntaxFormatRule: SyntaxRewriter, Rule { self.context = context } - open override func visitAny(_ node: Syntax) -> Syntax? { + public override func visitAny(_ node: Syntax) -> Syntax? { // If the rule is not enabled, then return the node unmodified; otherwise, returning nil tells // SwiftSyntax to continue with the standard dispatch. guard context.isRuleEnabled(type(of: self), node: node) else { return node } diff --git a/Sources/SwiftFormat/Core/SyntaxLintRule.swift b/Sources/SwiftFormat/Core/SyntaxLintRule.swift index 3888f5d7a..696cfcb8a 100644 --- a/Sources/SwiftFormat/Core/SyntaxLintRule.swift +++ b/Sources/SwiftFormat/Core/SyntaxLintRule.swift @@ -14,10 +14,11 @@ import Foundation import SwiftSyntax /// A rule that lints a given file. -open class SyntaxLintRule: SyntaxVisitor, Rule { +@_spi(Rules) +public class SyntaxLintRule: SyntaxVisitor, Rule { /// Whether this rule is opt-in, meaning it's disabled by default. Rules are opt-out unless they /// override this property. - open class var isOptIn: Bool { + public class var isOptIn: Bool { return false } diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 5099cba6b..6e91d821d 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -15,6 +15,7 @@ import SwiftSyntax /// PrettyPrinter takes a Syntax node and outputs a well-formatted, re-indented reproduction of the /// code as a String. +@_spi(Testing) public class PrettyPrinter { /// Information about an open break that has not yet been closed during the printing stage. diff --git a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift index 528d526e0..2c5dcb30a 100644 --- a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift +++ b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift @@ -18,6 +18,7 @@ private let utf8Tab = UTF8.CodeUnit(ascii: "\t") /// Emits linter errors for whitespace style violations by comparing the raw text of the input Swift /// code with formatted text. +@_spi(Testing) public class WhitespaceLinter { /// The text of the input source code to be linted. diff --git a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift index b011b0925..ef6ea552b 100644 --- a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift +++ b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift @@ -1,8 +1,9 @@ -import SwiftFormat import SwiftFormatConfiguration import SwiftSyntax import XCTest +@_spi(Rules) @_spi(Testing) import SwiftFormat + /// DiagnosingTestCase is an XCTestCase subclass meant to inject diagnostic-specific testing /// routines into specific formatting test cases. open class DiagnosingTestCase: XCTestCase { @@ -32,6 +33,7 @@ open class DiagnosingTestCase: XCTestCase { /// The returned context is configured with a diagnostic consumer that records diagnostics emitted /// during the tests, which can then be asserted using the `XCTAssertDiagnosed` and /// `XCTAssertNotDiagnosed` methods. + @_spi(Testing) public func makeContext(sourceFileSyntax: SourceFileSyntax, configuration: Configuration? = nil) -> Context { diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index 165a58d55..186757f32 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -11,10 +11,11 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormat import SwiftSyntax import SwiftParser +@_spi(Rules) import SwiftFormat + /// Collects information about rules in the formatter code base. final class RuleCollector { /// Information about a detected rule. diff --git a/Sources/generate-pipeline/RuleNameCacheGenerator.swift b/Sources/generate-pipeline/RuleNameCacheGenerator.swift index 6c7e3a729..06c2978e6 100644 --- a/Sources/generate-pipeline/RuleNameCacheGenerator.swift +++ b/Sources/generate-pipeline/RuleNameCacheGenerator.swift @@ -41,6 +41,7 @@ final class RuleNameCacheGenerator: FileGenerator { // This file is automatically generated with generate-pipeline. Do Not Edit! /// By default, the `Rule.ruleName` should be the name of the implementing rule type. + @_spi(Testing) public let ruleNameCache: [ObjectIdentifier: String] = [ """ diff --git a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift index 9e522c105..a679189aa 100644 --- a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift +++ b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift @@ -1,8 +1,9 @@ -import SwiftFormat import SwiftSyntax import SwiftParser import XCTest -import _SwiftFormatTestSupport + +@_spi(Testing) import SwiftFormat +@_spi(Testing) import _SwiftFormatTestSupport final class WhitespaceLinterPerformanceTests: DiagnosingTestCase { func testWhitespaceLinterPerformance() { diff --git a/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift b/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift index f376f42a4..7094e5b1d 100644 --- a/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift +++ b/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift @@ -1,9 +1,10 @@ import Markdown -import SwiftFormat import SwiftSyntax import SwiftSyntaxBuilder import XCTest +@_spi(Testing) import SwiftFormat + final class DocumentationCommentTests: XCTestCase { func testBriefSummaryOnly() throws { let decl: DeclSyntax = """ diff --git a/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift b/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift index 28580a95f..adda6e4f1 100644 --- a/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift +++ b/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift @@ -1,8 +1,9 @@ -import SwiftFormat import SwiftSyntax import SwiftSyntaxBuilder import XCTest +@_spi(Testing) import SwiftFormat + final class DocumentationCommentTextTests: XCTestCase { func testSimpleDocLineComment() throws { let decl: DeclSyntax = """ diff --git a/Tests/SwiftFormatTests/Core/RuleMaskTests.swift b/Tests/SwiftFormatTests/Core/RuleMaskTests.swift index ee753a7b0..0984b7f8a 100644 --- a/Tests/SwiftFormatTests/Core/RuleMaskTests.swift +++ b/Tests/SwiftFormatTests/Core/RuleMaskTests.swift @@ -1,8 +1,9 @@ -import SwiftFormat import SwiftSyntax import SwiftParser import XCTest +@_spi(Testing) import SwiftFormat + final class RuleMaskTests: XCTestCase { /// The source converter for the text in the current test. This is implicitly unwrapped because /// each test case must prepare some source text before performing any assertions, otherwise diff --git a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift index 5818a02a2..f7e782b86 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift @@ -1,10 +1,11 @@ -import SwiftFormat import SwiftFormatConfiguration import SwiftOperators import SwiftSyntax import SwiftParser import XCTest -import _SwiftFormatTestSupport + +@_spi(Testing) import SwiftFormat +@_spi(Testing) import _SwiftFormatTestSupport class PrettyPrintTestCase: DiagnosingTestCase { /// Asserts that the input string, when pretty printed, is equal to the expected string. diff --git a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift index e2bd4fb55..e6a992a1c 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift @@ -1,9 +1,10 @@ -import SwiftFormat import SwiftFormatConfiguration import SwiftSyntax import SwiftParser import XCTest -import _SwiftFormatTestSupport + +@_spi(Testing) import SwiftFormat +@_spi(Testing) import _SwiftFormatTestSupport class WhitespaceTestCase: DiagnosingTestCase { override func setUp() { diff --git a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift index 4ad1aa6e2..a13e74221 100644 --- a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift +++ b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift @@ -1,8 +1,8 @@ import SwiftParser import XCTest -import _SwiftFormatTestSupport -@_spi(Rules) import SwiftFormat +@_spi(Rules) @_spi(Testing) import SwiftFormat +@_spi(Testing) import _SwiftFormatTestSupport class ImportsXCTestVisitorTests: DiagnosingTestCase { func testDoesNotImportXCTest() throws { diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index 4d27911b8..a57c9782e 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -1,10 +1,11 @@ -import SwiftFormat import SwiftFormatConfiguration import SwiftOperators import SwiftParser import SwiftSyntax import XCTest -import _SwiftFormatTestSupport + +@_spi(Rules) @_spi(Testing) import SwiftFormat +@_spi(Testing) import _SwiftFormatTestSupport class LintOrFormatRuleTestCase: DiagnosingTestCase { /// Performs a lint using the provided linter rule on the provided input. From 474e12ea6b716a63f0286fd4e6a19f59134e8945 Mon Sep 17 00:00:00 2001 From: woxtu Date: Fri, 18 Aug 2023 00:18:40 +0900 Subject: [PATCH 097/332] Fix links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 349e20703..1a0a73ded 100644 --- a/README.md +++ b/README.md @@ -230,8 +230,8 @@ creates. Instead, it can pass the in-memory syntax tree to the `SwiftFormat` API and receive perfectly formatted code as output. Please see the documentation in the -[`SwiftFormatter`](Sources/SwiftFormat/SwiftFormatter.swift) and -[`SwiftLinter`](Sources/SwiftFormat/SwiftLinter.swift) classes for more +[`SwiftFormatter`](Sources/SwiftFormat/API/SwiftFormatter.swift) and +[`SwiftLinter`](Sources/SwiftFormat/API/SwiftLinter.swift) classes for more information about their usage. ### Checking Out the Source Code for Development From 3fd572a86e4744acd21677b281c1522692ea6138 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Fri, 18 Aug 2023 11:01:00 -0400 Subject: [PATCH 098/332] Refactor tests. This PR significantly improves the testing infrastructure by adopting the emoji markers used by SwiftSyntax's parser tests. This makes it easier to read and write tests, because the markers indicate in the string where the finding should be emitted and we don't have to compute line/column numbers manually. Additional testing improvements include: - Tests are no longer stateful w.r.t. collecting findings, so a single test can safely call its `assert*` helpers multiple times. - Tests verify the actual text of the findings, not just their symbolic values/constructors. Previously, we weren't actually verifying that the *messages* were what we wanted them to be, and this has revealed a couple minor issues. - I've dropped the `XCT*` prefix on our own custom assertions, because we shouldn't be using their namespace. I've added a handful of FIXME comments for things I caught during the migration that need to be addressed. --- Sources/SwiftFormat/Core/Context.swift | 2 +- .../DiagnosingTestCase.swift | 225 ++-- .../_SwiftFormatTestSupport/FindingSpec.swift | 45 + .../_SwiftFormatTestSupport/MarkedText.swift | 80 ++ .../TestingFindingConsumer.swift | 97 -- .../WhitespaceLinterPerformanceTests.swift | 2 +- .../PrettyPrint/ArrayDeclTests.swift | 78 +- .../PrettyPrint/DictionaryDeclTests.swift | 76 +- .../PrettyPrint/PrettyPrintTestCase.swift | 69 +- .../PrettyPrint/WhitespaceLintTests.swift | 468 ++++--- .../PrettyPrint/WhitespaceTestCase.swift | 40 +- ...icDeclarationsHaveDocumentationTests.swift | 21 +- .../Rules/AlwaysUseLowerCamelCaseTests.swift | 324 +++-- ...mbiguousTrailingClosureOverloadTests.swift | 91 +- ...tationCommentWithOneLineSummaryTests.swift | 148 ++- .../Rules/DoNotUseSemicolonsTests.swift | 212 ++-- ...ontRepeatTypeInStaticPropertiesTests.swift | 82 +- .../FileScopedDeclarationPrivacyTests.swift | 188 ++- .../Rules/FullyIndirectEnumTests.swift | 31 +- .../Rules/GroupNumericLiteralsTests.swift | 101 +- .../Rules/IdentifiersMustBeASCIITests.swift | 29 +- .../Rules/ImportsXCTestVisitorTests.swift | 12 +- .../Rules/LintOrFormatRuleTestCase.swift | 73 +- .../Rules/NeverForceUnwrapTests.swift | 58 +- .../Rules/NeverUseForceTryTests.swift | 37 +- ...UseImplicitlyUnwrappedOptionalsTests.swift | 31 +- ...cessLevelOnExtensionDeclarationTests.swift | 217 ++-- .../NoAssignmentInExpressionsTests.swift | 104 +- .../Rules/NoBlockCommentsTests.swift | 33 +- .../NoCasesWithOnlyFallthroughTests.swift | 191 +-- ...EmptyTrailingClosureParenthesesTests.swift | 157 ++- .../Rules/NoLabelsInCasePatternsTests.swift | 50 +- .../Rules/NoLeadingUnderscoresTests.swift | 220 ++-- .../Rules/NoParensAroundConditionsTests.swift | 349 +++--- ...NoVoidReturnOnFunctionSignatureTests.swift | 45 +- .../Rules/OneCasePerLineTests.swift | 87 +- .../OneVariableDeclarationPerLineTests.swift | 159 ++- .../OnlyOneTrailingClosureArgumentTests.swift | 17 +- .../Rules/OrderedImportsTests.swift | 1097 ++++++++--------- .../ReturnVoidInsteadOfEmptyTupleTests.swift | 163 ++- .../TypeNamesShouldBeCapitalizedTests.swift | 131 +- .../Rules/UseEarlyExitsTests.swift | 42 +- .../UseLetInEveryBoundCaseVariableTests.swift | 157 +-- .../Rules/UseShorthandTypeNamesTests.swift | 704 +++++++---- .../UseSingleLinePropertyGetterTests.swift | 120 +- .../UseSynthesizedInitializerTests.swift | 298 ++--- ...leSlashForDocumentationCommentsTests.swift | 233 ++-- .../UseWhereClausesInForLoopsTests.swift | 147 +-- .../ValidateDocumentationCommentsTests.swift | 473 ++++--- 49 files changed, 4199 insertions(+), 3615 deletions(-) create mode 100644 Sources/_SwiftFormatTestSupport/FindingSpec.swift create mode 100644 Sources/_SwiftFormatTestSupport/MarkedText.swift delete mode 100644 Sources/_SwiftFormatTestSupport/TestingFindingConsumer.swift diff --git a/Sources/SwiftFormat/Core/Context.swift b/Sources/SwiftFormat/Core/Context.swift index e0b15ab9b..94cfc5a4b 100644 --- a/Sources/SwiftFormat/Core/Context.swift +++ b/Sources/SwiftFormat/Core/Context.swift @@ -53,7 +53,7 @@ public final class Context { public var importsXCTest: XCTestImportState /// An object that converts `AbsolutePosition` values to `SourceLocation` values. - let sourceLocationConverter: SourceLocationConverter + public let sourceLocationConverter: SourceLocationConverter /// Contains the rules have been disabled by comments for certain line numbers. let ruleMask: RuleMask diff --git a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift index ef6ea552b..567f5dccf 100644 --- a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift +++ b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift @@ -7,100 +7,183 @@ import XCTest /// DiagnosingTestCase is an XCTestCase subclass meant to inject diagnostic-specific testing /// routines into specific formatting test cases. open class DiagnosingTestCase: XCTestCase { - /// Set during lint tests to indicate that we should check for unasserted diagnostics when the - /// test is torn down and fail if there were any. - public var shouldCheckForUnassertedDiagnostics = false - - /// A helper that will keep track of the findings that were emitted. - private var consumer = TestingFindingConsumer() - - override open func setUp() { - shouldCheckForUnassertedDiagnostics = false - } - - override open func tearDown() { - guard shouldCheckForUnassertedDiagnostics else { return } - - // This will emit a test failure if a diagnostic is thrown but we don't explicitly call - // XCTAssertDiagnosed for it. - for finding in consumer.emittedFindings { - XCTFail("unexpected finding '\(finding)' emitted") - } - } - /// Creates and returns a new `Context` from the given syntax tree and configuration. /// - /// The returned context is configured with a diagnostic consumer that records diagnostics emitted - /// during the tests, which can then be asserted using the `XCTAssertDiagnosed` and - /// `XCTAssertNotDiagnosed` methods. + /// The returned context is configured with the given finding consumer to record findings emitted + /// during the tests, so that they can be asserted later using the `assertFindings` method. @_spi(Testing) - public func makeContext(sourceFileSyntax: SourceFileSyntax, configuration: Configuration? = nil) - -> Context - { - consumer = TestingFindingConsumer() + public func makeContext( + sourceFileSyntax: SourceFileSyntax, + configuration: Configuration? = nil, + findingConsumer: @escaping (Finding) -> Void + ) -> Context { let context = Context( configuration: configuration ?? Configuration(), operatorTable: .standardOperators, - findingConsumer: consumer.consume, + findingConsumer: findingConsumer, fileURL: URL(fileURLWithPath: "/tmp/test.swift"), sourceFileSyntax: sourceFileSyntax, ruleNameCache: ruleNameCache) return context } - /// Stops tracking diagnostics emitted during formatting/linting. - /// - /// This used by the pretty-printer tests to suppress any diagnostics that might be emitted during - /// the second format pass (which checks for idempotence). - public func stopTrackingDiagnostics() { - consumer.stopTrackingFindings() + /// Asserts that the given list of findings matches a set of specs. + @_spi(Testing) + public final func assertFindings( + expected specs: [FindingSpec], + markerLocations: [String: Int], + emittedFindings: [Finding], + context: Context, + file: StaticString = #file, + line: UInt = #line + ) { + var emittedFindings = emittedFindings + + // Check for a finding that matches each spec, removing it from the array if found. + for spec in specs { + assertAndRemoveFinding( + findingSpec: spec, + markerLocations: markerLocations, + emittedFindings: &emittedFindings, + context: context, + file: file, + line: line) + } + + // Emit test failures for any findings that did not have matches. + for finding in emittedFindings { + let locationString: String + if let location = finding.location { + locationString = "line:col \(location.line):\(location.column)" + } else { + locationString = "no location provided" + } + XCTFail( + "Unexpected finding '\(finding.message)' was emitted (\(locationString))", + file: file, + line: line) + } } - /// Asserts that a specific diagnostic message was emitted. - /// - /// - Parameters: - /// - message: The diagnostic message expected to be emitted. - /// - file: The file in which failure occurred. Defaults to the file name of the test case in - /// which this function was called. - /// - line: The line number on which failure occurred. Defaults to the line number on which this - /// function was called. - public final func XCTAssertDiagnosed( - _ message: Finding.Message, - line diagnosticLine: Int? = nil, - column diagnosticColumn: Int? = nil, + private func assertAndRemoveFinding( + findingSpec: FindingSpec, + markerLocations: [String: Int], + emittedFindings: inout [Finding], + context: Context, file: StaticString = #file, line: UInt = #line ) { - let wasEmitted: Bool - if let diagnosticLine = diagnosticLine, let diagnosticColumn = diagnosticColumn { - wasEmitted = consumer.popFinding( - containing: message.text, atLine: diagnosticLine, column: diagnosticColumn) - } else { - wasEmitted = consumer.popFinding(containing: message.text) + guard let utf8Offset = markerLocations[findingSpec.marker] else { + XCTFail("Marker '\(findingSpec.marker)' was not found in the input", file: file, line: line) + return + } + + let markerLocation = + context.sourceLocationConverter.location(for: AbsolutePosition(utf8Offset: utf8Offset)) + + // Find a finding that has the expected line/column location, ignoring the text. + // FIXME: We do this to provide a better error message if the finding is in the right place but + // doesn't have the right message, but this also introduces an order-sensitivity among the + // specs. Fix this if it becomes an issue. + let maybeIndex = emittedFindings.firstIndex { + markerLocation.line == $0.location?.line && markerLocation.column == $0.location?.column + } + guard let index = maybeIndex else { + XCTFail( + """ + Finding '\(findingSpec.message)' was not emitted at marker '\(findingSpec.marker)' \ + (line:col \(markerLocation.line):\(markerLocation.column), offset \(utf8Offset)) + """, + file: file, + line: line) + return } - if !wasEmitted { - XCTFail("diagnostic '\(message.text)' not emitted", file: file, line: line) + + // Verify that the finding text also matches what we expect. + let matchedFinding = emittedFindings.remove(at: index) + XCTAssertEqual( + matchedFinding.message.text, + findingSpec.message, + """ + Finding emitted at marker '\(findingSpec.marker)' \ + (line:col \(markerLocation.line):\(markerLocation.column), offset \(utf8Offset)) \ + had the wrong message + """, + file: file, + line: line) + + // Assert that a note exists for each of the expected nodes in the finding. + var emittedNotes = matchedFinding.notes + for noteSpec in findingSpec.notes { + assertAndRemoveNote( + noteSpec: noteSpec, + markerLocations: markerLocations, + emittedNotes: &emittedNotes, + context: context, + file: file, + line: line) + } + + // Emit test failures for any notes that weren't specified. + for note in emittedNotes { + let locationString: String + if let location = note.location { + locationString = "line:col \(location.line):\(location.column)" + } else { + locationString = "no location provided" + } + XCTFail( + "Unexpected note '\(note.message)' was emitted (\(locationString))", + file: file, + line: line) } } - /// Asserts that a specific diagnostic message was not emitted. - /// - /// - Parameters: - /// - message: The diagnostic message expected to not be emitted. - /// - file: The file in which failure occurred. Defaults to the file name of the test case in - /// which this function was called. - /// - line: The line number on which failure occurred. Defaults to the line number on which this - /// function was called. - public final func XCTAssertNotDiagnosed( - _ message: Finding.Message, + private func assertAndRemoveNote( + noteSpec: NoteSpec, + markerLocations: [String: Int], + emittedNotes: inout [Finding.Note], + context: Context, file: StaticString = #file, line: UInt = #line ) { - let wasEmitted = consumer.popFinding(containing: message.text) - XCTAssertFalse( - wasEmitted, - "diagnostic '\(message.text)' should not have been emitted", - file: file, line: line) + guard let utf8Offset = markerLocations[noteSpec.marker] else { + XCTFail("Marker '\(noteSpec.marker)' was not found in the input", file: file, line: line) + return + } + + let markerLocation = + context.sourceLocationConverter.location(for: AbsolutePosition(utf8Offset: utf8Offset)) + + // FIXME: We do this to provide a better error message if the note is in the right place but + // doesn't have the right message, but this also introduces an order-sensitivity among the + // specs. Fix this if it becomes an issue. + let maybeIndex = emittedNotes.firstIndex { + markerLocation.line == $0.location?.line && markerLocation.column == $0.location?.column + } + guard let index = maybeIndex else { + XCTFail( + """ + Note '\(noteSpec.message)' was not emitted at marker '\(noteSpec.marker)' \ + (line:col \(markerLocation.line):\(markerLocation.column), offset \(utf8Offset)) + """, + file: file, + line: line) + return + } + + // Verify that the note text also matches what we expect. + let matchedNote = emittedNotes.remove(at: index) + XCTAssertEqual( + matchedNote.message.text, + noteSpec.message, + """ + Note emitted at marker '\(noteSpec.marker)' \ + (line:col \(markerLocation.line):\(markerLocation.column), offset \(utf8Offset)) \ + had the wrong message + """, + file: file, + line: line) } /// Asserts that the two strings are equal, providing Unix `diff`-style output if they are not. @@ -113,7 +196,7 @@ open class DiagnosingTestCase: XCTestCase { /// which this function was called. /// - line: The line number on which failure occurred. Defaults to the line number on which this /// function was called. - public final func XCTAssertStringsEqualWithDiff( + public final func assertStringsEqualWithDiff( _ actual: String, _ expected: String, _ message: String = "", diff --git a/Sources/_SwiftFormatTestSupport/FindingSpec.swift b/Sources/_SwiftFormatTestSupport/FindingSpec.swift new file mode 100644 index 000000000..e9751ede4 --- /dev/null +++ b/Sources/_SwiftFormatTestSupport/FindingSpec.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// A description of a `Finding` that can be asserted during tests. +public struct FindingSpec { + /// The marker that identifies the finding. + public var marker: String + + /// The message text associated with the finding. + public var message: String + + /// A description of a `Note` that should be associated with this finding. + public var notes: [NoteSpec] + + /// Creates a new `FindingSpec` with the given values. + public init(_ marker: String = "1️⃣", message: String, notes: [NoteSpec] = []) { + self.marker = marker + self.message = message + self.notes = notes + } +} + +/// A description of a `Note` that can be asserted during tests. +public struct NoteSpec { + /// The marker that identifies the note. + public var marker: String + + /// The message text associated with the note. + public var message: String + + /// Creates a new `NoteSpec` with the given values. + public init(_ marker: String, message: String) { + self.marker = marker + self.message = message + } +} diff --git a/Sources/_SwiftFormatTestSupport/MarkedText.swift b/Sources/_SwiftFormatTestSupport/MarkedText.swift new file mode 100644 index 000000000..e43c8ccf8 --- /dev/null +++ b/Sources/_SwiftFormatTestSupport/MarkedText.swift @@ -0,0 +1,80 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// Encapsulates the locations of emoji markers extracted from source text. +public struct MarkedText { + /// A mapping from marker names to the UTF-8 offset where the marker was found in the string. + public let markers: [String: Int] + + /// The text with all markers removed. + public let textWithoutMarkers: String + + /// Creates a new `MarkedText` value by extracting emoji markers from the given text. + public init(textWithMarkers markedText: String) { + var text = "" + var markers = [String: Int]() + var lastIndex = markedText.startIndex + for marker in findMarkedRanges(in: markedText) { + text += markedText[lastIndex.. +} + +private func findMarkedRanges(in text: String) -> [Marker] { + var markers = [Marker]() + while let marker = nextMarkedRange(in: text, from: markers.last?.range.upperBound ?? text.startIndex) { + markers.append(marker) + } + return markers +} + +private func nextMarkedRange(in text: String, from index: String.Index) -> Marker? { + guard let start = text[index...].firstIndex(where: { $0.isMarkerEmoji }) else { + return nil + } + + let end = text.index(after: start) + let markerRange = start.. Bool { - let maybeIndex = emittedFindings.firstIndex { - $0.message.contains(text) && line == $0.line && column == $0.column - } - guard let index = maybeIndex else { return false } - - emittedFindings.remove(at: index) - return true - } - - /// Pops the first finding that contains the given text (regardless of location) from the - /// collection of emitted findings, if possible. - /// - /// - Parameter text: The message text to match. - /// - Returns: True if a finding was found and popped, or false otherwise. - func popFinding(containing text: String) -> Bool { - let maybeIndex = emittedFindings.firstIndex { $0.message.contains(text) } - guard let index = maybeIndex else { return false } - - emittedFindings.remove(at: index) - return true - } - - /// Stops tracking findings. - func stopTrackingFindings() { - isTracking = false - } -} diff --git a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift index a679189aa..938fdad49 100644 --- a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift +++ b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift @@ -58,7 +58,7 @@ final class WhitespaceLinterPerformanceTests: DiagnosingTestCase { /// - expected: The formatted text. private func performWhitespaceLint(input: String, expected: String) { let sourceFileSyntax = Parser.parse(source: input) - let context = makeContext(sourceFileSyntax: sourceFileSyntax) + let context = makeContext(sourceFileSyntax: sourceFileSyntax, findingConsumer: { _ in }) let linter = WhitespaceLinter(user: input, formatted: expected, context: context) linter.lint() } diff --git a/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift index f7382c5cc..c65b3b7fe 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift @@ -1,5 +1,6 @@ import SwiftFormat import SwiftSyntax +import _SwiftFormatTestSupport final class ArrayDeclTests: PrettyPrintTestCase { func testBasicArrays() { @@ -154,37 +155,62 @@ final class ArrayDeclTests: PrettyPrintTestCase { } func testWhitespaceOnlyDoesNotChangeTrailingComma() { - let input = - """ - let a = [ - "String", - ] - let a = [1, 2, 3,] - let a: [String] = [ - "One", "Two", "Three", "Four", "Five", - "Six", "Seven", "Eight" - ] - """ - assertPrettyPrintEqual( - input: input, expected: input + "\n", linelength: 45, whitespaceOnly: true) + input: """ + let a = [ + "String"1️⃣, + ] + let a = [1, 2, 32️⃣,] + let a: [String] = [ + "One", "Two", "Three", "Four", "Five", + "Six", "Seven", "Eight"3️⃣ + ] + """, + expected: """ + let a = [ + "String", + ] + let a = [1, 2, 3,] + let a: [String] = [ + "One", "Two", "Three", "Four", "Five", + "Six", "Seven", "Eight" + ] + + """, + linelength: 45, + whitespaceOnly: true, + findings: [ + FindingSpec("1️⃣", message: "remove trailing comma from the last element in single line collection literal"), + FindingSpec("2️⃣", message: "remove trailing comma from the last element in single line collection literal"), + FindingSpec("3️⃣", message: "add trailing comma to the last element in multiline collection literal"), + ] + ) } func testTrailingCommaDiagnostics() { - let input = - """ - let a = [1, 2, 3,] - let a: [String] = [ - "One", "Two", "Three", "Four", "Five", - "Six", "Seven", "Eight" - ] - """ - assertPrettyPrintEqual( - input: input, expected: input + "\n", linelength: 45, whitespaceOnly: true) - - XCTAssertDiagnosed(.removeTrailingComma, line: 1, column: 17) - XCTAssertDiagnosed(.addTrailingComma, line: 4, column: 26) + input: """ + let a = [1, 2, 31️⃣,] + let a: [String] = [ + "One", "Two", "Three", "Four", "Five", + "Six", "Seven", "Eight"2️⃣ + ] + """, + expected: """ + let a = [1, 2, 3,] + let a: [String] = [ + "One", "Two", "Three", "Four", "Five", + "Six", "Seven", "Eight" + ] + + """, + linelength: 45, + whitespaceOnly: true, + findings: [ + FindingSpec("1️⃣", message: "remove trailing comma from the last element in single line collection literal"), + FindingSpec("2️⃣", message: "add trailing comma to the last element in multiline collection literal"), + ] + ) } func testGroupsTrailingComma() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift index 58a400ce5..d0df7747a 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift @@ -1,5 +1,6 @@ import SwiftFormat import SwiftSyntax +import _SwiftFormatTestSupport final class DictionaryDeclTests: PrettyPrintTestCase { func testBasicDictionaries() { @@ -90,37 +91,62 @@ final class DictionaryDeclTests: PrettyPrintTestCase { } func testWhitespaceOnlyDoesNotChangeTrailingComma() { - let input = - """ - let a = [ - 1: "a", - ] - let a = [1: "a", 2: "b", 3: "c",] - let a: [Int: String] = [ - 1: "a", 2: "b", 3: "c", 4: "d", 5: "e", 6: "f", - 7: "g", 8: "i" - ] - """ - assertPrettyPrintEqual( - input: input, expected: input + "\n", linelength: 50, whitespaceOnly: true) - } + input: """ + let a = [ + 1: "a"1️⃣, + ] + let a = [1: "a", 2: "b", 3: "c"2️⃣,] + let a: [Int: String] = [ + 1: "a", 2: "b", 3: "c", 4: "d", 5: "e", 6: "f", + 7: "g", 8: "i"3️⃣ + ] + """, + expected: """ + let a = [ + 1: "a", + ] + let a = [1: "a", 2: "b", 3: "c",] + let a: [Int: String] = [ + 1: "a", 2: "b", 3: "c", 4: "d", 5: "e", 6: "f", + 7: "g", 8: "i" + ] - func testTrailingCommaDiagnostics() { - let input = - """ - let a = [1: "a", 2: "b", 3: "c",] - let a: [Int: String] = [ - 1: "a", 2: "b", 3: "c", 4: "d", 5: "e", 6: "f", - 7: "g", 8: "i" + """, + linelength: 50, + whitespaceOnly: true, + findings: [ + FindingSpec("1️⃣", message: "remove trailing comma from the last element in single line collection literal"), + FindingSpec("2️⃣", message: "remove trailing comma from the last element in single line collection literal"), + FindingSpec("3️⃣", message: "add trailing comma to the last element in multiline collection literal"), ] - """ + ) + } + func testTrailingCommaDiagnostics() { assertPrettyPrintEqual( - input: input, expected: input + "\n", linelength: 50, whitespaceOnly: true) + input: """ + let a = [1: "a", 2: "b", 3: "c"1️⃣,] + let a: [Int: String] = [ + 1: "a", 2: "b", 3: "c", 4: "d", 5: "e", 6: "f", + 7: "g", 8: "i"2️⃣ + ] + """, + expected: """ + let a = [1: "a", 2: "b", 3: "c",] + let a: [Int: String] = [ + 1: "a", 2: "b", 3: "c", 4: "d", 5: "e", 6: "f", + 7: "g", 8: "i" + ] - XCTAssertDiagnosed(.removeTrailingComma, line: 1, column: 32) - XCTAssertDiagnosed(.addTrailingComma, line: 4, column: 17) + """, + linelength: 50, + whitespaceOnly: true, + findings: [ + FindingSpec("1️⃣", message: "remove trailing comma from the last element in single line collection literal"), + FindingSpec("2️⃣", message: "add trailing comma to the last element in multiline collection literal"), + ] + ) } func testDiscretionaryNewlineAfterColon() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift index f7e782b86..4bc4e1142 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift @@ -4,7 +4,7 @@ import SwiftSyntax import SwiftParser import XCTest -@_spi(Testing) import SwiftFormat +@_spi(Rules) @_spi(Testing) import SwiftFormat @_spi(Testing) import _SwiftFormatTestSupport class PrettyPrintTestCase: DiagnosingTestCase { @@ -17,6 +17,8 @@ class PrettyPrintTestCase: DiagnosingTestCase { /// - configuration: The formatter configuration. /// - whitespaceOnly: If true, the pretty printer should only apply whitespace changes and omit /// changes that insert or remove non-whitespace characters (like trailing commas). + /// - findings: A list of `FindingSpec` values that describe the findings that are expected to + /// be emitted. These are currently only checked if `whitespaceOnly` is true. /// - file: The file in which failure occurred. Defaults to the file name of the test case in /// which this function was called. /// - line: The line number on which failure occurred. Defaults to the line number on which this @@ -27,31 +29,49 @@ class PrettyPrintTestCase: DiagnosingTestCase { linelength: Int, configuration: Configuration = Configuration.forTesting, whitespaceOnly: Bool = false, + findings: [FindingSpec] = [], file: StaticString = #file, line: UInt = #line ) { var configuration = configuration configuration.lineLength = linelength + let markedInput = MarkedText(textWithMarkers: input) + var emittedFindings = [Finding]() + // Assert that the input, when formatted, is what we expected. - if let formatted = prettyPrintedSource( - input, configuration: configuration, whitespaceOnly: whitespaceOnly) - { - XCTAssertStringsEqualWithDiff( - formatted, expected, - "Pretty-printed result was not what was expected", - file: file, line: line) + let (formatted, context) = prettyPrintedSource( + markedInput.textWithoutMarkers, + configuration: configuration, + whitespaceOnly: whitespaceOnly, + findingConsumer: { emittedFindings.append($0) }) + assertStringsEqualWithDiff( + formatted, expected, + "Pretty-printed result was not what was expected", + file: file, line: line) - // Idempotency check: Running the formatter multiple times should not change the outcome. - // Assert that running the formatter again on the previous result keeps it the same. - stopTrackingDiagnostics() - if let reformatted = prettyPrintedSource( - formatted, configuration: configuration, whitespaceOnly: whitespaceOnly) - { - XCTAssertStringsEqualWithDiff( - reformatted, formatted, "Pretty printer is not idempotent", file: file, line: line) - } + // FIXME: It would be nice to check findings when whitespaceOnly == false, but their locations + // are wrong. + if whitespaceOnly { + assertFindings( + expected: findings, + markerLocations: markedInput.markers, + emittedFindings: emittedFindings, + context: context, + file: file, + line: line) } + + // Idempotency check: Running the formatter multiple times should not change the outcome. + // Assert that running the formatter again on the previous result keeps it the same. + let (reformatted, _) = prettyPrintedSource( + formatted, + configuration: configuration, + whitespaceOnly: whitespaceOnly, + findingConsumer: { _ in } // Ignore findings during the idempotence check. + ) + assertStringsEqualWithDiff( + reformatted, formatted, "Pretty printer is not idempotent", file: file, line: line) } /// Returns the given source code reformatted with the pretty printer. @@ -61,21 +81,28 @@ class PrettyPrintTestCase: DiagnosingTestCase { /// - configuration: The formatter configuration. /// - whitespaceOnly: If true, the pretty printer should only apply whitespace changes and omit /// changes that insert or remove non-whitespace characters (like trailing commas). + /// - findingConsumer: A function called for each finding that is emitted by the pretty printer. /// - Returns: The pretty-printed text, or nil if an error occurred and a test failure was logged. private func prettyPrintedSource( - _ source: String, configuration: Configuration, whitespaceOnly: Bool - ) -> String? { + _ source: String, + configuration: Configuration, + whitespaceOnly: Bool, + findingConsumer: @escaping (Finding) -> Void + ) -> (String, Context) { // Ignore folding errors for unrecognized operators so that we fallback to a reasonable default. let sourceFileSyntax = restoringLegacyTriviaBehavior( OperatorTable.standardOperators.foldAll(Parser.parse(source: source)) { _ in } .as(SourceFileSyntax.self)!) - let context = makeContext(sourceFileSyntax: sourceFileSyntax, configuration: configuration) + let context = makeContext( + sourceFileSyntax: sourceFileSyntax, + configuration: configuration, + findingConsumer: findingConsumer) let printer = PrettyPrinter( context: context, node: Syntax(sourceFileSyntax), printTokenStream: false, whitespaceOnly: whitespaceOnly) - return printer.prettyPrint() + return (printer.prettyPrint(), context) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift index 7b7d01bcd..51088ceb3 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift @@ -1,288 +1,248 @@ import SwiftFormat import SwiftFormatConfiguration +import _SwiftFormatTestSupport +// A note about these tests: `WhitespaceLinter` *only* emits findings; it does not do any +// reformatting. Therefore, in these tests the "expected" source code is the desired string that the +// linter is diffing against. final class WhitespaceLintTests: WhitespaceTestCase { func testSpacing() { - let input = - """ - let a : Int = 123 - let b =456 - - """ - - let expected = - """ - let a: Int = 123 - let b = 456 - - """ - - performWhitespaceLint(input: input, expected: expected) - XCTAssertDiagnosed(.spacingError(-1), line: 1, column: 6) - XCTAssertDiagnosed(.spacingError(1), line: 2, column: 8) + assertWhitespaceLint( + input: """ + let a1️⃣ : Int = 123 + let b =2️⃣456 + + """, + expected: """ + let a: Int = 123 + let b = 456 + + """, + findings: [ + FindingSpec("1️⃣", message: "remove 1 space"), + FindingSpec("2️⃣", message: "add 1 space"), + ] + ) } func testTabSpacing() { - let input = - """ - let a\t: Int = 123 - - """ - - let expected = - """ - let a: Int = 123 - - """ - - performWhitespaceLint(input: input, expected: expected) - XCTAssertDiagnosed(.spacingCharError, line: 1, column: 6) + assertWhitespaceLint( + input: """ + let a1️⃣\t: Int = 123 + + """, + expected: """ + let a: Int = 123 + + """, + findings: [ + FindingSpec("1️⃣", message: "use spaces for spacing"), + ] + ) } func testSpaceIndentation() { - let input = - """ + assertWhitespaceLint( + input: """ + 1️⃣ let a = 123 + 2️⃣let b = 456 + 3️⃣ let c = "abc" + 4️⃣\tlet d = 111 + + """, + expected: """ let a = 123 - let b = 456 - let c = "abc" - \tlet d = 111 - - """ - - let expected = - """ - let a = 123 - let b = 456 - let c = "abc" - let d = 111 - - """ - - performWhitespaceLint(input: input, expected: expected) - XCTAssertDiagnosed( - .indentationError(expected: .none, actual: .homogeneous(.spaces(2))), line: 1, column: 1) - XCTAssertDiagnosed( - .indentationError(expected: .homogeneous(.spaces(4)), actual: .none), line: 2, column: 1) - XCTAssertDiagnosed( - .indentationError(expected: .none, actual: .homogeneous(.spaces(1))), line: 3, column: 1) - XCTAssertDiagnosed( - .indentationError(expected: .homogeneous(.spaces(2)), actual: .homogeneous(.tabs(1))), - line: 4, - column: 1) + let b = 456 + let c = "abc" + let d = 111 + + """, + findings: [ + FindingSpec("1️⃣", message: "remove all leading whitespace"), + FindingSpec("2️⃣", message: "replace leading whitespace with 4 spaces"), + FindingSpec("3️⃣", message: "remove all leading whitespace"), + FindingSpec("4️⃣", message: "replace leading whitespace with 2 spaces"), + ] + ) } func testTabIndentation() { - let input = - """ - \t\tlet a = 123 - let b = 456 - let c = "abc" - let d = 111 - - """ - - let expected = - """ - let a = 123 - \tlet b = 456 - let c = "abc" - \t\tlet d = 111 - - """ - - performWhitespaceLint(input: input, expected: expected) - XCTAssertDiagnosed( - .indentationError(expected: .none, actual: .homogeneous(.tabs(2))), line: 1, column: 1) - XCTAssertDiagnosed( - .indentationError(expected: .homogeneous(.tabs(1)), actual: .none), line: 2, column: 1) - XCTAssertDiagnosed( - .indentationError(expected: .none, actual: .homogeneous(.spaces(2))), line: 3, column: 1) - XCTAssertDiagnosed( - .indentationError(expected: .homogeneous(.tabs(2)), actual: .homogeneous(.spaces(1))), - line: 4, - column: 1) - } + assertWhitespaceLint( + input: """ + 1️⃣\t\tlet a = 123 + 2️⃣let b = 456 + 3️⃣ let c = "abc" + 4️⃣ let d = 111 + + """, + expected: """ + let a = 123 + \tlet b = 456 + let c = "abc" + \t\tlet d = 111 + + """, + findings: [ + FindingSpec("1️⃣", message: "remove all leading whitespace"), + FindingSpec("2️⃣", message: "replace leading whitespace with 1 tab"), + FindingSpec("3️⃣", message: "remove all leading whitespace"), + FindingSpec("4️⃣", message: "replace leading whitespace with 2 tabs"), + ] + ) + } func testHeterogeneousIndentation() { - let input = - """ - \t\t \t let a = 123 - let b = 456 - let c = "abc" - \tlet d = 111 - \t let e = 111 - - """ - - let expected = - """ - let a = 123 - \t \t let b = 456 - let c = "abc" - let d = 111 - \tlet e = 111 - - """ - - performWhitespaceLint(input: input, expected: expected) - XCTAssertDiagnosed( - .indentationError( - expected: .homogeneous(.spaces(2)), - actual: .heterogeneous([.tabs(2), .spaces(2), .tabs(1), .spaces(1)])), - line: 1, - column: 1) - XCTAssertDiagnosed( - .indentationError( - expected: .heterogeneous([.tabs(1), .spaces(2), .tabs(1), .spaces(1)]), - actual: .none), - line: 2, - column: 1) - XCTAssertDiagnosed( - .indentationError( - expected: .none, - actual: .homogeneous(.spaces(2))), - line: 3, - column: 1) - XCTAssertDiagnosed( - .indentationError( - expected: .homogeneous(.spaces(2)), - actual: .homogeneous(.tabs(1))), - line: 4, - column: 1) - XCTAssertDiagnosed( - .indentationError( - expected: .heterogeneous([.spaces(1), .tabs(1)]), - actual: .heterogeneous([.tabs(1), .spaces(1)])), - line: 5, - column: 1) - } + assertWhitespaceLint( + input: """ + 1️⃣\t\t \t let a = 123 + 2️⃣let b = 456 + 3️⃣ let c = "abc" + 4️⃣ \tlet d = 111 + 5️⃣\t let e = 111 + + """, + expected: """ + let a = 123 + \t \t let b = 456 + let c = "abc" + let d = 111 + \tlet e = 111 + + """, + findings: [ + FindingSpec("1️⃣", message: "replace leading whitespace with 2 spaces"), + FindingSpec("2️⃣", message: "replace leading whitespace with 1 tab, 2 spaces, 1 tab, 1 space"), + FindingSpec("3️⃣", message: "remove all leading whitespace"), + FindingSpec("4️⃣", message: "replace leading whitespace with 2 spaces"), + FindingSpec("5️⃣", message: "replace leading whitespace with 1 space, 1 tab"), + ] + ) + } func testTrailingWhitespace() { - let input = - """ - let a = 123\u{20}\u{20} - let b = "abc"\u{20} - let c = "def" - \u{20}\u{20} - let d = 456\u{20}\u{20}\u{20} - - """ - - let expected = - """ - let a = 123 - let b = "abc" - let c = "def" - - let d = 456 - - """ - - performWhitespaceLint(input: input, expected: expected) - XCTAssertDiagnosed(.trailingWhitespaceError, line: 1, column: 12) - XCTAssertDiagnosed(.trailingWhitespaceError, line: 2, column: 14) - XCTAssertDiagnosed(.trailingWhitespaceError, line: 4, column: 1) - XCTAssertDiagnosed(.trailingWhitespaceError, line: 5, column: 12) + assertWhitespaceLint( + input: """ + let a = 1231️⃣\u{20}\u{20} + let b = "abc"2️⃣\u{20} + let c = "def" + 3️⃣\u{20}\u{20} + let d = 4564️⃣\u{20}\u{20}\u{20} + + """, + expected: """ + let a = 123 + let b = "abc" + let c = "def" + + let d = 456 + + """, + findings: [ + FindingSpec("1️⃣", message: "remove trailing whitespace"), + FindingSpec("2️⃣", message: "remove trailing whitespace"), + FindingSpec("3️⃣", message: "remove trailing whitespace"), + FindingSpec("4️⃣", message: "remove trailing whitespace"), + ] + ) } func testAddLines() { - let input = - """ - let a = 123 - let b = "abc" - func myfun() { return } - - """ - - let expected = - """ - let a = 123 - - let b = "abc" - func myfun() { - return - } - - """ + assertWhitespaceLint( + input: """ + let a = 1231️⃣ + let b = "abc" + func myfun() {2️⃣ return3️⃣ } + + """, + expected: """ + let a = 123 - performWhitespaceLint(input: input, expected: expected) - XCTAssertDiagnosed(.addLinesError(1), line: 1, column: 12) - XCTAssertDiagnosed(.addLinesError(1), line: 3, column: 15) - XCTAssertDiagnosed(.addLinesError(1), line: 3, column: 22) + let b = "abc" + func myfun() { + return + } + + """, + findings: [ + // FIXME: These should be singular. + FindingSpec("1️⃣", message: "add 1 line breaks"), + FindingSpec("2️⃣", message: "add 1 line breaks"), + FindingSpec("3️⃣", message: "add 1 line breaks"), + ] + ) } func testRemoveLines() { - let input = - """ - let a = 123 + assertWhitespaceLint( + input: """ + let a = 1231️⃣ - let b = "abc" + let b = "abc"2️⃣ + 3️⃣ + let c = 456 + func myFun() {4️⃣ + return someValue5️⃣ + } - let c = 456 - func myFun() { - return someValue - } - - """ - - let expected = - """ - let a = 123 - let b = "abc" - let c = 456 - func myFun() { return someValue } - - """ - - performWhitespaceLint(input: input, expected: expected) - XCTAssertDiagnosed(.removeLineError, line: 1, column: 12) - XCTAssertDiagnosed(.removeLineError, line: 3, column: 14) - XCTAssertDiagnosed(.removeLineError, line: 4, column: 1) - XCTAssertDiagnosed(.removeLineError, line: 7, column: 15) - XCTAssertDiagnosed(.removeLineError, line: 8, column: 19) + """, + expected: """ + let a = 123 + let b = "abc" + let c = 456 + func myFun() { return someValue } + + """, + findings: [ + FindingSpec("1️⃣", message: "remove line break"), + FindingSpec("2️⃣", message: "remove line break"), + FindingSpec("3️⃣", message: "remove line break"), + FindingSpec("4️⃣", message: "remove line break"), + FindingSpec("5️⃣", message: "remove line break"), + ] + ) } func testLineLength() { - let input = - """ - func myFunc(longVar1: Bool, longVar2: Bool, longVar3: Bool, longVar4: Bool) { - // do stuff - } - - func myFunc(longVar1: Bool, longVar2: Bool, - longVar3: Bool, - longVar4: Bool) { - // do stuff - } - - """ - - let expected = - """ - func myFunc( - longVar1: Bool, - longVar2: Bool, - longVar3: Bool, - longVar4: Bool - ) { - // do stuff - } - - func myFunc( - longVar1: Bool, - longVar2: Bool, - longVar3: Bool, - longVar4: Bool - ) { - // do stuff - } - - """ - - performWhitespaceLint(input: input, expected: expected, linelength: 30) - XCTAssertDiagnosed(.lineLengthError, line: 1, column: 1) - XCTAssertDiagnosed(.lineLengthError, line: 5, column: 1) - XCTAssertDiagnosed(.addLinesError(1), line: 7, column: 17) + assertWhitespaceLint( + input: """ + 1️⃣func myFunc(longVar1: Bool, longVar2: Bool, longVar3: Bool, longVar4: Bool) { + // do stuff + } + + 2️⃣func myFunc(longVar1: Bool, longVar2: Bool, + longVar3: Bool, + longVar4: Bool3️⃣) { + // do stuff + } + + """, + expected: """ + func myFunc( + longVar1: Bool, + longVar2: Bool, + longVar3: Bool, + longVar4: Bool + ) { + // do stuff + } + + func myFunc( + longVar1: Bool, + longVar2: Bool, + longVar3: Bool, + longVar4: Bool + ) { + // do stuff + } + + """, + linelength: 30, + findings: [ + FindingSpec("1️⃣", message: "line is too long"), + FindingSpec("2️⃣", message: "line is too long"), + FindingSpec("3️⃣", message: "add 1 line breaks"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift index e6a992a1c..cd0aeed90 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift @@ -7,11 +7,6 @@ import XCTest @_spi(Testing) import _SwiftFormatTestSupport class WhitespaceTestCase: DiagnosingTestCase { - override func setUp() { - super.setUp() - shouldCheckForUnassertedDiagnostics = true - } - /// Perform whitespace linting by comparing the input text from the user with the expected /// formatted text. /// @@ -19,15 +14,42 @@ class WhitespaceTestCase: DiagnosingTestCase { /// - input: The user's input text. /// - expected: The formatted text. /// - linelength: The maximum allowed line length of the output. - final func performWhitespaceLint(input: String, expected: String, linelength: Int? = nil) { - let sourceFileSyntax = Parser.parse(source: input) + /// - findings: A list of `FindingSpec` values that describe the findings that are expected to + /// be emitted. + /// - file: The file the test resides in (defaults to the current caller's file). + /// - line: The line the test resides in (defaults to the current caller's line). + final func assertWhitespaceLint( + input: String, + expected: String, + linelength: Int? = nil, + findings: [FindingSpec], + file: StaticString = #file, + line: UInt = #line + ) { + let markedText = MarkedText(textWithMarkers: input) + + let sourceFileSyntax = Parser.parse(source: markedText.textWithoutMarkers) var configuration = Configuration.forTesting if let linelength = linelength { configuration.lineLength = linelength } - let context = makeContext(sourceFileSyntax: sourceFileSyntax, configuration: configuration) - let linter = WhitespaceLinter(user: input, formatted: expected, context: context) + var emittedFindings = [Finding]() + + let context = makeContext( + sourceFileSyntax: sourceFileSyntax, + configuration: configuration, + findingConsumer: { emittedFindings.append($0) }) + let linter = WhitespaceLinter( + user: markedText.textWithoutMarkers, formatted: expected, context: context) linter.lint() + + assertFindings( + expected: findings, + markerLocations: markedText.markers, + emittedFindings: emittedFindings, + context: context, + file: file, + line: line) } } diff --git a/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift b/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift index db9317a2c..b32fdb5ff 100644 --- a/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift +++ b/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift @@ -1,13 +1,16 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCase { func testPublicDeclsWithoutDocs() { - let input = + assertLint( + AllPublicDeclarationsHaveDocumentation.self, """ - public func lightswitchRave() { + 1️⃣public func lightswitchRave() { } - public var isSblounskched: Int { + 2️⃣public var isSblounskched: Int { return 0 } @@ -21,11 +24,11 @@ final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCas public var isDelorted: Bool { return false } - """ - performLint(AllPublicDeclarationsHaveDocumentation.self, input: input) - XCTAssertDiagnosed(.declRequiresComment("lightswitchRave()")) - XCTAssertDiagnosed(.declRequiresComment("isSblounskched")) - XCTAssertNotDiagnosed(.declRequiresComment("fhqwhgads()")) - XCTAssertNotDiagnosed(.declRequiresComment("isDelorted")) + """, + findings: [ + FindingSpec("1️⃣", message: "add a documentation comment for 'lightswitchRave()'"), + FindingSpec("2️⃣", message: "add a documentation comment for 'isSblounskched'"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift index bde50ceeb..59201e42e 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift @@ -1,261 +1,213 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class AlwaysUseLowerCamelCaseTests: LintOrFormatRuleTestCase { - override func setUp() { - super.setUp() - shouldCheckForUnassertedDiagnostics = true - } - func testInvalidVariableCasing() { - let input = + assertLint( + AlwaysUseLowerCamelCase.self, """ - let Test = 1 + let 1️⃣Test = 1 var foo = 2 - var bad_name = 20 + var 2️⃣bad_name = 20 var _okayName = 20 + if let 3️⃣Baz = foo { } + """, + findings: [ + FindingSpec("1️⃣", message: "rename the constant 'Test' using lowerCamelCase"), + FindingSpec("2️⃣", message: "rename the variable 'bad_name' using lowerCamelCase"), + FindingSpec("3️⃣", message: "rename the constant 'Baz' using lowerCamelCase"), + ] + ) + } + + func testInvalidFunctionCasing() { + assertLint( + AlwaysUseLowerCamelCase.self, + """ struct Foo { - func FooFunc() {} + func 1️⃣FooFunc() {} } class UnitTests: XCTestCase { - func test_HappyPath_Through_GoodCode() {} + // This is flagged because XCTest is not imported. + func 2️⃣test_HappyPath_Through_GoodCode() {} } + func wellNamedFunc(_ 3️⃣BadFuncArg1: Int, 4️⃣BadFuncArgLabel goodFuncArg: String) { + var 5️⃣PoorlyNamedVar = 0 + } + """, + findings: [ + FindingSpec("1️⃣", message: "rename the function 'FooFunc' using lowerCamelCase"), + FindingSpec("2️⃣", message: "rename the function 'test_HappyPath_Through_GoodCode' using lowerCamelCase"), + FindingSpec("3️⃣", message: "rename the function parameter 'BadFuncArg1' using lowerCamelCase"), + FindingSpec("4️⃣", message: "rename the argument label 'BadFuncArgLabel' using lowerCamelCase"), + FindingSpec("5️⃣", message: "rename the variable 'PoorlyNamedVar' using lowerCamelCase"), + ] + ) + + } + + func testInvalidEnumCaseCasing() { + assertLint( + AlwaysUseLowerCamelCase.self, + """ enum FooBarCases { - case UpperCamelCase + case 1️⃣UpperCamelCase case lowerCamelCase } - if let Baz = foo { } - guard let foo = [1, 2, 3, 4].first(where: { BadName -> Bool in - let TerribleName = BadName - return TerribleName != 0 - }) else { return } - var fooVar = [1, 2, 3, 4].first(where: { BadNameInFooVar -> Bool in - let TerribleNameInFooVar = BadName + """, + findings: [ + FindingSpec("1️⃣", message: "rename the enum case 'UpperCamelCase' using lowerCamelCase"), + ] + ) + + } + + func testInvalidClosureCasing() { + assertLint( + AlwaysUseLowerCamelCase.self, + """ + var fooVar = [1, 2, 3, 4].first(where: { 1️⃣BadNameInFooVar -> Bool in + let 2️⃣TerribleNameInFooVar = BadName return TerribleName != 0 }) - var abc = array.first(where: { (CParam1, _ CParam2: Type, cparam3) -> Bool in return true }) - func wellNamedFunc(_ BadFuncArg1: Int, BadFuncArgLabel goodFuncArg: String) { - var PoorlyNamedVar = 0 - } - """ - performLint(AlwaysUseLowerCamelCase.self, input: input) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("Test", description: "constant"), line: 1, column: 5) - XCTAssertNotDiagnosed(.nameMustBeLowerCamelCase("foo", description: "variable")) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("bad_name", description: "variable"), line: 3, column: 5) - XCTAssertNotDiagnosed(.nameMustBeLowerCamelCase("_okayName", description: "variable")) - XCTAssertNotDiagnosed(.nameMustBeLowerCamelCase("Foo", description: "struct")) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("FooFunc", description: "function"), line: 6, column: 8) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("test_HappyPath_Through_GoodCode", description: "function"), - line: 9, column: 8) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("UpperCamelCase", description: "enum case"), line: 12, column: 8) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("Baz", description: "constant"), line: 15, column: 8) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("BadName", description: "closure parameter"), line: 16, column: 45) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("TerribleName", description: "constant"), line: 17, column: 7) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("BadNameInFooVar", description: "closure parameter"), - line: 20, column: 42) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("TerribleNameInFooVar", description: "constant"), - line: 21, column: 7) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("CParam1", description: "closure parameter"), line: 24, column: 33) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("CParam2", description: "closure parameter"), line: 24, column: 44) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("BadFuncArg1", description: "function parameter"), - line: 25, column: 22) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("BadFuncArgLabel", description: "argument label"), - line: 25, column: 40) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("PoorlyNamedVar", description: "variable"), line: 26, column: 7) + var abc = array.first(where: { (3️⃣CParam1, _ 4️⃣CParam2: Type, cparam3) -> Bool in return true }) + guard let foo = [1, 2, 3, 4].first(where: { 5️⃣BadName -> Bool in + let 6️⃣TerribleName = BadName + return TerribleName != 0 + }) else { return } + """, + findings: [ + FindingSpec("1️⃣", message: "rename the closure parameter 'BadNameInFooVar' using lowerCamelCase"), + FindingSpec("2️⃣", message: "rename the constant 'TerribleNameInFooVar' using lowerCamelCase"), + FindingSpec("3️⃣", message: "rename the closure parameter 'CParam1' using lowerCamelCase"), + FindingSpec("4️⃣", message: "rename the closure parameter 'CParam2' using lowerCamelCase"), + FindingSpec("5️⃣", message: "rename the closure parameter 'BadName' using lowerCamelCase"), + FindingSpec("6️⃣", message: "rename the constant 'TerribleName' using lowerCamelCase"), + ] + ) } func testIgnoresUnderscoresInTestNames() { - let input = + assertLint( + AlwaysUseLowerCamelCase.self, """ import XCTest - let Test = 1 + let 1️⃣Test = 1 class UnitTests: XCTestCase { - static let My_Constant_Value = 0 + static let 2️⃣My_Constant_Value = 0 func test_HappyPath_Through_GoodCode() {} - private func FooFunc() {} - private func helperFunc_For_HappyPath_Setup() {} - private func testLikeMethod_With_Underscores(_ arg1: ParamType) {} - private func testLikeMethod_With_Underscores2() -> ReturnType {} + private func 3️⃣FooFunc() {} + private func 4️⃣helperFunc_For_HappyPath_Setup() {} + private func 5️⃣testLikeMethod_With_Underscores(_ arg1: ParamType) {} + private func 6️⃣testLikeMethod_With_Underscores2() -> ReturnType {} func test_HappyPath_Through_GoodCode_ReturnsVoid() -> Void {} func test_HappyPath_Through_GoodCode_ReturnsShortVoid() -> () {} func test_HappyPath_Through_GoodCode_Throws() throws {} } - """ - performLint(AlwaysUseLowerCamelCase.self, input: input) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("Test", description: "constant"), line: 3, column: 5) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("My_Constant_Value", description: "constant"), line: 5, column: 14) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase("test_HappyPath_Through_GoodCode", description: "function")) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("FooFunc", description: "function"), line: 7, column: 16) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("helperFunc_For_HappyPath_Setup", description: "function"), - line: 8, column: 16) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("testLikeMethod_With_Underscores", description: "function"), - line: 9, column: 16) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("testLikeMethod_With_Underscores2", description: "function"), - line: 10, column: 16) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase( - "test_HappyPath_Through_GoodCode_ReturnsVoid", description: "function")) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase( - "test_HappyPath_Through_GoodCode_ReturnsShortVoid", description: "function")) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase("test_HappyPath_Through_GoodCode_Throws", description: "function")) + """, + findings: [ + FindingSpec("1️⃣", message: "rename the constant 'Test' using lowerCamelCase"), + FindingSpec("2️⃣", message: "rename the constant 'My_Constant_Value' using lowerCamelCase"), + FindingSpec("3️⃣", message: "rename the function 'FooFunc' using lowerCamelCase"), + FindingSpec("4️⃣", message: "rename the function 'helperFunc_For_HappyPath_Setup' using lowerCamelCase"), + FindingSpec("5️⃣", message: "rename the function 'testLikeMethod_With_Underscores' using lowerCamelCase"), + FindingSpec("6️⃣", message: "rename the function 'testLikeMethod_With_Underscores2' using lowerCamelCase"), + ] + ) } func testIgnoresUnderscoresInTestNamesWhenImportedConditionally() { - let input = + assertLint( + AlwaysUseLowerCamelCase.self, """ #if SOME_FEATURE_FLAG import XCTest - let Test = 1 + let 1️⃣Test = 1 class UnitTests: XCTestCase { - static let My_Constant_Value = 0 + static let 2️⃣My_Constant_Value = 0 func test_HappyPath_Through_GoodCode() {} - private func FooFunc() {} - private func helperFunc_For_HappyPath_Setup() {} - private func testLikeMethod_With_Underscores(_ arg1: ParamType) {} - private func testLikeMethod_With_Underscores2() -> ReturnType {} + private func 3️⃣FooFunc() {} + private func 4️⃣helperFunc_For_HappyPath_Setup() {} + private func 5️⃣testLikeMethod_With_Underscores(_ arg1: ParamType) {} + private func 6️⃣testLikeMethod_With_Underscores2() -> ReturnType {} func test_HappyPath_Through_GoodCode_ReturnsVoid() -> Void {} func test_HappyPath_Through_GoodCode_ReturnsShortVoid() -> () {} func test_HappyPath_Through_GoodCode_Throws() throws {} } #endif - """ - performLint(AlwaysUseLowerCamelCase.self, input: input) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("Test", description: "constant"), line: 4, column: 7) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("My_Constant_Value", description: "constant"), line: 6, column: 16) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase("test_HappyPath_Through_GoodCode", description: "function")) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("FooFunc", description: "function"), line: 8, column: 18) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("helperFunc_For_HappyPath_Setup", description: "function"), - line: 9, column: 18) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("testLikeMethod_With_Underscores", description: "function"), - line: 10, column: 18) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("testLikeMethod_With_Underscores2", description: "function"), - line: 11, column: 18) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase( - "test_HappyPath_Through_GoodCode_ReturnsVoid", description: "function")) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase( - "test_HappyPath_Through_GoodCode_ReturnsShortVoid", description: "function")) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase("test_HappyPath_Through_GoodCode_Throws", description: "function")) + """, + findings: [ + FindingSpec("1️⃣", message: "rename the constant 'Test' using lowerCamelCase"), + FindingSpec("2️⃣", message: "rename the constant 'My_Constant_Value' using lowerCamelCase"), + FindingSpec("3️⃣", message: "rename the function 'FooFunc' using lowerCamelCase"), + FindingSpec("4️⃣", message: "rename the function 'helperFunc_For_HappyPath_Setup' using lowerCamelCase"), + FindingSpec("5️⃣", message: "rename the function 'testLikeMethod_With_Underscores' using lowerCamelCase"), + FindingSpec("6️⃣", message: "rename the function 'testLikeMethod_With_Underscores2' using lowerCamelCase"), + ] + ) } func testIgnoresUnderscoresInConditionalTestNames() { - let input = + assertLint( + AlwaysUseLowerCamelCase.self, """ import XCTest - let Test = 1 class UnitTests: XCTestCase { #if SOME_FEATURE_FLAG - static let My_Constant_Value = 0 + static let 1️⃣My_Constant_Value = 0 func test_HappyPath_Through_GoodCode() {} - private func FooFunc() {} - private func helperFunc_For_HappyPath_Setup() {} - private func testLikeMethod_With_Underscores(_ arg1: ParamType) {} - private func testLikeMethod_With_Underscores2() -> ReturnType {} + private func 2️⃣FooFunc() {} + private func 3️⃣helperFunc_For_HappyPath_Setup() {} + private func 4️⃣testLikeMethod_With_Underscores(_ arg1: ParamType) {} + private func 5️⃣testLikeMethod_With_Underscores2() -> ReturnType {} func test_HappyPath_Through_GoodCode_ReturnsVoid() -> Void {} func test_HappyPath_Through_GoodCode_ReturnsShortVoid() -> () {} func test_HappyPath_Through_GoodCode_Throws() throws {} #else - func testBadMethod_HasNonVoidReturn() -> ReturnType {} + func 6️⃣testBadMethod_HasNonVoidReturn() -> ReturnType {} func testGoodMethod_HasVoidReturn() {} #if SOME_OTHER_FEATURE_FLAG - func testBadMethod_HasNonVoidReturn2() -> ReturnType {} + func 7️⃣testBadMethod_HasNonVoidReturn2() -> ReturnType {} func testGoodMethod_HasVoidReturn2() {} #endif #endif } #endif - """ - performLint(AlwaysUseLowerCamelCase.self, input: input) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("Test", description: "constant"), line: 3, column: 5) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("My_Constant_Value", description: "constant"), line: 6, column: 16) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase("test_HappyPath_Through_GoodCode", description: "function")) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("FooFunc", description: "function"), line: 8, column: 18) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("helperFunc_For_HappyPath_Setup", description: "function"), - line: 9, column: 18) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("testLikeMethod_With_Underscores", description: "function"), - line: 10, column: 18) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("testLikeMethod_With_Underscores2", description: "function"), - line: 11, column: 18) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase( - "test_HappyPath_Through_GoodCode_ReturnsVoid", description: "function")) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase( - "test_HappyPath_Through_GoodCode_ReturnsShortVoid", description: "function")) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase("test_HappyPath_Through_GoodCode_Throws", description: "function")) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("testBadMethod_HasNonVoidReturn", description: "function"), - line: 16, column: 10) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase("testGoodMethod_HasVoidReturn", description: "function")) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("testBadMethod_HasNonVoidReturn2", description: "function"), - line: 19, column: 12) - XCTAssertNotDiagnosed( - .nameMustBeLowerCamelCase("testGoodMethod_HasVoidReturn2", description: "function")) + """, + findings: [ + FindingSpec("1️⃣", message: "rename the constant 'My_Constant_Value' using lowerCamelCase"), + FindingSpec("2️⃣", message: "rename the function 'FooFunc' using lowerCamelCase"), + FindingSpec("3️⃣", message: "rename the function 'helperFunc_For_HappyPath_Setup' using lowerCamelCase"), + FindingSpec("4️⃣", message: "rename the function 'testLikeMethod_With_Underscores' using lowerCamelCase"), + FindingSpec("5️⃣", message: "rename the function 'testLikeMethod_With_Underscores2' using lowerCamelCase"), + FindingSpec("6️⃣", message: "rename the function 'testBadMethod_HasNonVoidReturn' using lowerCamelCase"), + FindingSpec("7️⃣", message: "rename the function 'testBadMethod_HasNonVoidReturn2' using lowerCamelCase"), + ] + ) } func testIgnoresFunctionOverrides() { - let input = + assertLint( + AlwaysUseLowerCamelCase.self, """ class ParentClass { - var poorly_named_variable: Int = 5 - func poorly_named_method() {} + var 1️⃣poorly_named_variable: Int = 5 + func 2️⃣poorly_named_method() {} } class ChildClass: ParentClass { override var poorly_named_variable: Int = 5 override func poorly_named_method() {} } - """ - - performLint(AlwaysUseLowerCamelCase.self, input: input) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("poorly_named_variable", description: "variable"), line: 2, column: 7) - XCTAssertDiagnosed( - .nameMustBeLowerCamelCase("poorly_named_method", description: "function"), line: 3, column: 8) + """, + findings: [ + FindingSpec("1️⃣", message: "rename the variable 'poorly_named_variable' using lowerCamelCase"), + FindingSpec("2️⃣", message: "rename the function 'poorly_named_method' using lowerCamelCase"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift b/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift index 1eb297688..b50bb8411 100644 --- a/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift +++ b/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift @@ -1,47 +1,60 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class AmbiguousTrailingClosureOverloadTests: LintOrFormatRuleTestCase { func testAmbiguousOverloads() { - performLint( + assertLint( AmbiguousTrailingClosureOverload.self, - input: """ - func strong(mad: () -> Int) {} - func strong(bad: (Bool) -> Bool) {} - func strong(sad: (String) -> Bool) {} - - class A { - static func the(cheat: (Int) -> Void) {} - class func the(sneak: (Int) -> Void) {} - func the(kingOfTown: () -> Void) {} - func the(cheatCommandos: (Bool) -> Void) {} - func the(brothersStrong: (String) -> Void) {} - } - - struct B { - func hom(estar: () -> Int) {} - func hom(sar: () -> Bool) {} - - static func baleeted(_ f: () -> Void) {} - func baleeted(_ f: () -> Void) {} - } - """ + """ + func 1️⃣strong(mad: () -> Int) {} + func 2️⃣strong(bad: (Bool) -> Bool) {} + func 3️⃣strong(sad: (String) -> Bool) {} + + class A { + static func 4️⃣the(cheat: (Int) -> Void) {} + class func 5️⃣the(sneak: (Int) -> Void) {} + func 6️⃣the(kingOfTown: () -> Void) {} + func 7️⃣the(cheatCommandos: (Bool) -> Void) {} + func 8️⃣the(brothersStrong: (String) -> Void) {} + } + + struct B { + func 9️⃣hom(estar: () -> Int) {} + func 🔟hom(sar: () -> Bool) {} + + static func baleeted(_ f: () -> Void) {} + func baleeted(_ f: () -> Void) {} + } + """, + findings: [ + FindingSpec( + "1️⃣", message: "rename 'strong(mad:)' so it is no longer ambiguous when called with a trailing closure", + notes: [ + NoteSpec("2️⃣", message: "ambiguous overload 'strong(bad:)' is here"), + NoteSpec("3️⃣", message: "ambiguous overload 'strong(sad:)' is here"), + ] + ), + FindingSpec( + "4️⃣", message: "rename 'the(cheat:)' so it is no longer ambiguous when called with a trailing closure", + notes: [ + NoteSpec("5️⃣", message: "ambiguous overload 'the(sneak:)' is here"), + ] + ), + FindingSpec( + "6️⃣", message: "rename 'the(kingOfTown:)' so it is no longer ambiguous when called with a trailing closure", + notes: [ + NoteSpec("7️⃣", message: "ambiguous overload 'the(cheatCommandos:)' is here"), + NoteSpec("8️⃣", message: "ambiguous overload 'the(brothersStrong:)' is here"), + ] + ), + FindingSpec( + "9️⃣", message: "rename 'hom(estar:)' so it is no longer ambiguous when called with a trailing closure", + notes: [ + NoteSpec("🔟", message: "ambiguous overload 'hom(sar:)' is here"), + ] + ), + ] ) - - XCTAssertDiagnosed(.ambiguousTrailingClosureOverload("strong(mad:)")) - XCTAssertDiagnosed(.otherAmbiguousOverloadHere("strong(bad:)")) - XCTAssertDiagnosed(.otherAmbiguousOverloadHere("strong(sad:)")) - - XCTAssertDiagnosed(.ambiguousTrailingClosureOverload("the(cheat:)")) - XCTAssertDiagnosed(.otherAmbiguousOverloadHere("the(sneak:)")) - - XCTAssertDiagnosed(.ambiguousTrailingClosureOverload("the(kingOfTown:)")) - XCTAssertDiagnosed(.otherAmbiguousOverloadHere("the(cheatCommandos:)")) - XCTAssertDiagnosed(.otherAmbiguousOverloadHere("the(brothersStrong:)")) - - XCTAssertDiagnosed(.ambiguousTrailingClosureOverload("hom(estar:)")) - XCTAssertDiagnosed(.otherAmbiguousOverloadHere("hom(sar:)")) - - XCTAssertNotDiagnosed(.ambiguousTrailingClosureOverload("baleeted(_:)")) - XCTAssertNotDiagnosed(.otherAmbiguousOverloadHere("baleeted(_:)")) } } diff --git a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift index 72ad56346..7f37461b0 100644 --- a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift +++ b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift @@ -1,5 +1,8 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: We should place the diagnostic somewhere in the comment, not on the declaration. final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTestCase { override func setUp() { // Reset this to false by default. Specific tests may override it. @@ -8,7 +11,8 @@ final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTe } func testDocLineCommentsWithoutOneSentenceSummary() { - let input = + assertLint( + BeginDocumentationCommentWithOneLineSummary.self, """ /// Returns a bottle of Dr Pepper from the vending machine. public func drPepper(from vendingMachine: VendingMachine) -> Soda {} @@ -30,30 +34,26 @@ final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTe /// This docline should not succeed. /// There are two sentences without a blank line between them. - struct Test {} + 1️⃣struct Test {} /// This docline should not succeed. There are two sentences. - public enum Token { case comma, semicolon, identifier } + 2️⃣public enum Token { case comma, semicolon, identifier } /// Should fail because it doesn't have a period - public class testNoPeriod {} - """ - performLint(BeginDocumentationCommentWithOneLineSummary.self, input: input) - XCTAssertDiagnosed(.addBlankLineAfterFirstSentence("This docline should not succeed.")) - XCTAssertDiagnosed(.addBlankLineAfterFirstSentence("This docline should not succeed.")) - XCTAssertDiagnosed(.terminateSentenceWithPeriod("Should fail because it doesn't have a period")) - - XCTAssertNotDiagnosed(.addBlankLineAfterFirstSentence( - "Returns a bottle of Dr Pepper from the vending machine.")) - XCTAssertNotDiagnosed(.addBlankLineAfterFirstSentence( - "Contains a comment as description that needs a sentence of two lines of code.")) - XCTAssertNotDiagnosed(.addBlankLineAfterFirstSentence("The background color of the view.")) - XCTAssertNotDiagnosed(.addBlankLineAfterFirstSentence("Returns the sum of the numbers.")) + 3️⃣public class testNoPeriod {} + """, + findings: [ + FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), + FindingSpec("2️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), + FindingSpec("3️⃣", message: #"terminate this sentence with a period: "Should fail because it doesn't have a period""#), + ] + ) } func testBlockLineCommentsWithoutOneSentenceSummary() { - let input = - """ + assertLint( + BeginDocumentationCommentWithOneLineSummary.self, + """ /** * Returns the numeric value. * @@ -73,76 +73,70 @@ final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTe * This block comment should not succeed, struct. * There are two sentences without a blank line between them. */ - struct TestStruct {} + 1️⃣struct TestStruct {} /** This block comment should not succeed, class. Add a blank comment after the first line. */ - public class TestClass {} + 2️⃣public class TestClass {} /** This block comment should not succeed, enum. There are two sentences. */ - public enum testEnum {} + 3️⃣public enum testEnum {} /** Should fail because it doesn't have a period */ - public class testNoPeriod {} - """ - performLint(BeginDocumentationCommentWithOneLineSummary.self, input: input) - XCTAssertDiagnosed(.addBlankLineAfterFirstSentence("This block comment should not succeed, struct.")) - XCTAssertDiagnosed(.addBlankLineAfterFirstSentence("This block comment should not succeed, class.")) - XCTAssertDiagnosed(.addBlankLineAfterFirstSentence("This block comment should not succeed, enum.")) - XCTAssertDiagnosed(.terminateSentenceWithPeriod("Should fail because it doesn't have a period")) - - XCTAssertNotDiagnosed(.addBlankLineAfterFirstSentence("Returns the numeric value.")) - XCTAssertNotDiagnosed(.addBlankLineAfterFirstSentence( - "This block comment contains a sentence summary of two lines of code.")) + 4️⃣public class testNoPeriod {} + """, + findings: [ + FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This block comment should not succeed, struct.""#), + FindingSpec("2️⃣", message: #"add a blank comment line after this sentence: "This block comment should not succeed, class.""#), + FindingSpec("3️⃣", message: #"add a blank comment line after this sentence: "This block comment should not succeed, enum.""#), + FindingSpec("4️⃣", message: #"terminate this sentence with a period: "Should fail because it doesn't have a period""#), + ] + ) } func testApproximationsOnMacOS() { #if os(macOS) - // Let macOS also verify that the fallback mode works, which gives us signal about whether it - // will also succeed on Linux (where the linguistic APIs are not currently available). - BeginDocumentationCommentWithOneLineSummary._forcesFallbackModeForTesting = true - - let input = - """ - /// Returns a bottle of Dr Pepper from the vending machine. - public func drPepper(from vendingMachine: VendingMachine) -> Soda {} - - /// Contains a comment as description that needs a sentence - /// of two lines of code. - public var twoLinesForOneSentence = "test" - - /// The background color of the view. - var backgroundColor: UIColor - - /// Returns the sum of the numbers. - /// - /// - Parameter numbers: The numbers to sum. - /// - Returns: The sum of the numbers. - func sum(_ numbers: [Int]) -> Int { - // ... - } - - /// This docline should not succeed. - /// There are two sentences without a blank line between them. - struct Test {} - - /// This docline should not succeed. There are two sentences. - public enum Token { case comma, semicolon, identifier } - - /// Should fail because it doesn't have a period - public class testNoPeriod {} - """ - performLint(BeginDocumentationCommentWithOneLineSummary.self, input: input) - XCTAssertDiagnosed(.addBlankLineAfterFirstSentence("This docline should not succeed.")) - XCTAssertDiagnosed(.addBlankLineAfterFirstSentence("This docline should not succeed.")) - XCTAssertDiagnosed(.terminateSentenceWithPeriod("Should fail because it doesn't have a period")) - - XCTAssertNotDiagnosed(.addBlankLineAfterFirstSentence( - "Returns a bottle of Dr Pepper from the vending machine.")) - XCTAssertNotDiagnosed(.addBlankLineAfterFirstSentence( - "Contains a comment as description that needs a sentence of two lines of code.")) - XCTAssertNotDiagnosed(.addBlankLineAfterFirstSentence("The background color of the view.")) - XCTAssertNotDiagnosed(.addBlankLineAfterFirstSentence("Returns the sum of the numbers.")) + // Let macOS also verify that the fallback mode works, which gives us signal about whether it + // will also succeed on Linux (where the linguistic APIs are not currently available). + BeginDocumentationCommentWithOneLineSummary._forcesFallbackModeForTesting = true + + assertLint( + BeginDocumentationCommentWithOneLineSummary.self, + """ + /// Returns a bottle of Dr Pepper from the vending machine. + public func drPepper(from vendingMachine: VendingMachine) -> Soda {} + + /// Contains a comment as description that needs a sentence + /// of two lines of code. + public var twoLinesForOneSentence = "test" + + /// The background color of the view. + var backgroundColor: UIColor + + /// Returns the sum of the numbers. + /// + /// - Parameter numbers: The numbers to sum. + /// - Returns: The sum of the numbers. + func sum(_ numbers: [Int]) -> Int { + // ... + } + + /// This docline should not succeed. + /// There are two sentences without a blank line between them. + 1️⃣struct Test {} + + /// This docline should not succeed. There are two sentences. + 2️⃣public enum Token { case comma, semicolon, identifier } + + /// Should fail because it doesn't have a period + 3️⃣public class testNoPeriod {} + """, + findings: [ + FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), + FindingSpec("2️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), + FindingSpec("3️⃣", message: #"terminate this sentence with a period: "Should fail because it doesn't have a period""#), + ] + ) #endif } } diff --git a/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift b/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift index 7b19094c0..e7f7316ba 100644 --- a/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift +++ b/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift @@ -1,129 +1,165 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: Some of the messages suggesting the next statement be moved to a new line are inaccurate. final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { func testSemicolonUse() { - XCTAssertFormatting( + assertFormatting( DoNotUseSemicolons.self, input: """ - print("hello"); print("goodbye"); - print("3") - """, + print("hello")1️⃣; print("goodbye")2️⃣; + print("3") + """, expected: """ - print("hello") - print("goodbye") - print("3") - """) + print("hello") + print("goodbye") + print("3") + """, + findings: [ + FindingSpec("1️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("2️⃣", message: "remove ';' and move the next statement to a new line"), + ] + ) } func testSemicolonsInNestedStatements() { - XCTAssertFormatting( + assertFormatting( DoNotUseSemicolons.self, input: """ - guard let someVar = Optional(items.filter ({ a in foo(a); return true; })) else { - items.forEach { a in foo(a); }; return; - } - """, + guard let someVar = Optional(items.filter ({ a in foo(a)1️⃣; return true2️⃣; })) else { + items.forEach { a in foo(a)3️⃣; }4️⃣; return5️⃣; + } + """, // The formatting in the expected output is unappealing, but that is fixed by the pretty // printer and isn't a concern for the format rule. expected: """ - guard let someVar = Optional(items.filter ({ a in foo(a) - return true})) else { - items.forEach { a in foo(a)} - return - } - """) + guard let someVar = Optional(items.filter ({ a in foo(a) + return true})) else { + items.forEach { a in foo(a)} + return + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("2️⃣", message: "remove ';'"), + FindingSpec("3️⃣", message: "remove ';'"), + FindingSpec("4️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("5️⃣", message: "remove ';'"), + ] + ) } func testSemicolonsInMemberLists() { - XCTAssertFormatting( + assertFormatting( DoNotUseSemicolons.self, input: """ - struct Foo { - func foo() { - code() - }; - - let someVar = 5;let someOtherVar = 6; - } - """, + struct Foo { + func foo() { + code() + }1️⃣; + + let someVar = 52️⃣;let someOtherVar = 63️⃣; + } + """, expected: """ - struct Foo { - func foo() { - code() - } - - let someVar = 5 - let someOtherVar = 6 - } - """) + struct Foo { + func foo() { + code() + } + + let someVar = 5 + let someOtherVar = 6 + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("2️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("3️⃣", message: "remove ';'"), + ] + ) } func testNewlinesAfterSemicolons() { - XCTAssertFormatting( + assertFormatting( DoNotUseSemicolons.self, input: """ - print("hello"); - /// This is a doc comment for printing "goodbye". - print("goodbye"); + print("hello")1️⃣; + /// This is a doc comment for printing "goodbye". + print("goodbye")2️⃣; - /// This is a doc comment for printing "3". - print("3"); + /// This is a doc comment for printing "3". + print("3")3️⃣; - print("4"); /** Inline comment. */ print("5"); + print("4")4️⃣; /** Inline comment. */ print("5")5️⃣; - print("6"); // This is an important statement. - print("7"); - """, + print("6")6️⃣; // This is an important statement. + print("7")7️⃣; + """, expected: """ - print("hello") - /// This is a doc comment for printing "goodbye". - print("goodbye") - - /// This is a doc comment for printing "3". - print("3") - - print("4") - /** Inline comment. */ print("5") - - print("6")// This is an important statement. - print("7") - """) + print("hello") + /// This is a doc comment for printing "goodbye". + print("goodbye") + + /// This is a doc comment for printing "3". + print("3") + + print("4") + /** Inline comment. */ print("5") + + print("6")// This is an important statement. + print("7") + """, + findings: [ + FindingSpec("1️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("2️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("3️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("4️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("5️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("6️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("7️⃣", message: "remove ';'"), + ] + ) } func testSemicolonsSeparatingDoWhile() { - XCTAssertFormatting( + assertFormatting( DoNotUseSemicolons.self, input: """ - do { f() }; - while someCondition { g() } + do { f() }; + while someCondition { g() } - do { - f() - }; + do { + f() + }; - // Comment and whitespace separating blocks. - while someCondition { - g() - } + // Comment and whitespace separating blocks. + while someCondition { + g() + } - do { f() }; - for _ in 0..<10 { g() } - """, + do { f() }1️⃣; + for _ in 0..<10 { g() } + """, expected: """ - do { f() }; - while someCondition { g() } - - do { - f() - }; - - // Comment and whitespace separating blocks. - while someCondition { - g() - } - - do { f() } - for _ in 0..<10 { g() } - """) + do { f() }; + while someCondition { g() } + + do { + f() + }; + + // Comment and whitespace separating blocks. + while someCondition { + g() + } + + do { f() } + for _ in 0..<10 { g() } + """, + findings: [ + FindingSpec("1️⃣", message: "remove ';' and move the next statement to a new line"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift index d39a0b148..c6ddba965 100644 --- a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift +++ b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift @@ -1,79 +1,75 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: These diagnostics should be on the variable name, not at the beginning of the declaration. final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase { func testRepetitiveProperties() { - let input = + assertLint( + DontRepeatTypeInStaticProperties.self, """ public class UIColor { - static let redColor: UIColor - public class var blueColor: UIColor + 1️⃣static let redColor: UIColor + 2️⃣public class var blueColor: UIColor var yellowColor: UIColor static let green: UIColor public class var purple: UIColor } enum Sandwich { - static let bolognaSandwich: Sandwich - static var hamSandwich: Sandwich + 3️⃣static let bolognaSandwich: Sandwich + 4️⃣static var hamSandwich: Sandwich static var turkey: Sandwich } protocol RANDPerson { var oldPerson: Person - static let youngPerson: Person + 5️⃣static let youngPerson: Person } struct TVGame { - static var basketballGame: TVGame - static var baseballGame: TVGame + 6️⃣static var basketballGame: TVGame + 7️⃣static var baseballGame: TVGame static let soccer: TVGame let hockey: TVGame } extension URLSession { - class var sharedSession: URLSession + 8️⃣class var sharedSession: URLSession } - """ - - performLint(DontRepeatTypeInStaticProperties.self, input: input) - XCTAssertDiagnosed(.removeTypeFromName(name: "redColor", type: "Color")) - XCTAssertDiagnosed(.removeTypeFromName(name: "blueColor", type: "Color")) - XCTAssertNotDiagnosed(.removeTypeFromName(name: "yellowColor", type: "Color")) - XCTAssertNotDiagnosed(.removeTypeFromName(name: "green", type: "Color")) - XCTAssertNotDiagnosed(.removeTypeFromName(name: "purple", type: "Color")) - - XCTAssertDiagnosed(.removeTypeFromName(name: "bolognaSandwich", type: "Sandwich")) - XCTAssertDiagnosed(.removeTypeFromName(name: "hamSandwich", type: "Sandwich")) - XCTAssertNotDiagnosed(.removeTypeFromName(name: "turkey", type: "Sandwich")) - - XCTAssertNotDiagnosed(.removeTypeFromName(name: "oldPerson", type: "Person")) - XCTAssertDiagnosed(.removeTypeFromName(name: "youngPerson", type: "Person")) - - XCTAssertDiagnosed(.removeTypeFromName(name: "basketballGame", type: "Game")) - XCTAssertDiagnosed(.removeTypeFromName(name: "baseballGame", type: "Game")) - XCTAssertNotDiagnosed(.removeTypeFromName(name: "soccer", type: "Game")) - XCTAssertNotDiagnosed(.removeTypeFromName(name: "hockey", type: "Game")) - - XCTAssertDiagnosed(.removeTypeFromName(name: "sharedSession", type: "Session")) + """, + findings: [ + FindingSpec("1️⃣", message: "remove the suffix 'Color' from the name of the variable 'redColor'"), + FindingSpec("2️⃣", message: "remove the suffix 'Color' from the name of the variable 'blueColor'"), + FindingSpec("3️⃣", message: "remove the suffix 'Sandwich' from the name of the variable 'bolognaSandwich'"), + FindingSpec("4️⃣", message: "remove the suffix 'Sandwich' from the name of the variable 'hamSandwich'"), + FindingSpec("5️⃣", message: "remove the suffix 'Person' from the name of the variable 'youngPerson'"), + FindingSpec("6️⃣", message: "remove the suffix 'Game' from the name of the variable 'basketballGame'"), + FindingSpec("7️⃣", message: "remove the suffix 'Game' from the name of the variable 'baseballGame'"), + FindingSpec("8️⃣", message: "remove the suffix 'Session' from the name of the variable 'sharedSession'"), + ] + ) } - func testSR11123() { - let input = + func testDoNotDiagnoseUnrelatedType() { + assertLint( + DontRepeatTypeInStaticProperties.self, """ extension A { static let b = C() } - """ - - performLint(DontRepeatTypeInStaticProperties.self, input: input) - XCTAssertNotDiagnosed(.removeTypeFromName(name: "b", type: "A")) + """, + findings: [] + ) } func testDottedExtendedType() { - let input = + assertLint( + DontRepeatTypeInStaticProperties.self, """ extension Dotted.Thing { - static let defaultThing: Dotted.Thing + 1️⃣static let defaultThing: Dotted.Thing } - """ - - performLint(DontRepeatTypeInStaticProperties.self, input: input) - XCTAssertDiagnosed(.removeTypeFromName(name: "defaultThing", type: "Thing")) + """, + findings: [ + FindingSpec("1️⃣", message: "remove the suffix 'Thing' from the name of the variable 'defaultThing'"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift b/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift index 7be246ede..576f88811 100644 --- a/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift +++ b/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift @@ -1,5 +1,6 @@ import SwiftFormatConfiguration import SwiftSyntax +import _SwiftFormatTestSupport @_spi(Rules) import SwiftFormat @@ -31,23 +32,25 @@ final class FileScopedDeclarationPrivacyTests: LintOrFormatRuleTestCase { func testFileScopeDecls() { runWithMultipleConfigurations( source: """ - $access$ class Foo {} - $access$ struct Foo {} - $access$ enum Foo {} - $access$ protocol Foo {} - $access$ typealias Foo = Bar - $access$ func foo() {} - $access$ var foo: Bar + 1️⃣$access$ class Foo {} + 2️⃣$access$ struct Foo {} + 3️⃣$access$ enum Foo {} + 4️⃣$access$ protocol Foo {} + 5️⃣$access$ typealias Foo = Bar + 6️⃣$access$ func foo() {} + 7️⃣$access$ var foo: Bar """, testConfigurations: changingTestConfigurations - ) { assertDiagnosticWasEmittedOrNot in - assertDiagnosticWasEmittedOrNot(1, 1) - assertDiagnosticWasEmittedOrNot(2, 1) - assertDiagnosticWasEmittedOrNot(3, 1) - assertDiagnosticWasEmittedOrNot(4, 1) - assertDiagnosticWasEmittedOrNot(5, 1) - assertDiagnosticWasEmittedOrNot(6, 1) - assertDiagnosticWasEmittedOrNot(7, 1) + ) { original, expected in + [ + FindingSpec("1️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("2️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("3️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("4️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("5️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("6️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("7️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + ] } } @@ -57,9 +60,7 @@ final class FileScopedDeclarationPrivacyTests: LintOrFormatRuleTestCase { $access$ extension Foo {} """, testConfigurations: unchangingTestConfigurations - ) { assertDiagnosticWasEmittedOrNot in - assertDiagnosticWasEmittedOrNot(1, 1) - } + ) { _, _ in [] } } func testNonFileScopeDeclsAreNotChanged() { @@ -75,69 +76,27 @@ final class FileScopedDeclarationPrivacyTests: LintOrFormatRuleTestCase { } """, testConfigurations: unchangingTestConfigurations - ) { assertDiagnosticWasEmittedOrNot in - assertDiagnosticWasEmittedOrNot(1, 1) - assertDiagnosticWasEmittedOrNot(2, 1) - assertDiagnosticWasEmittedOrNot(3, 1) - assertDiagnosticWasEmittedOrNot(4, 1) - assertDiagnosticWasEmittedOrNot(5, 1) - assertDiagnosticWasEmittedOrNot(6, 1) - assertDiagnosticWasEmittedOrNot(7, 1) - } + ) { _, _ in [] } } func testFileScopeDeclsInsideConditionals() { runWithMultipleConfigurations( source: """ #if FOO - $access$ class Foo {} - $access$ struct Foo {} - $access$ enum Foo {} - $access$ protocol Foo {} - $access$ typealias Foo = Bar - $access$ func foo() {} - $access$ var foo: Bar + 1️⃣$access$ class Foo {} #elseif BAR - $access$ class Foo {} - $access$ struct Foo {} - $access$ enum Foo {} - $access$ protocol Foo {} - $access$ typealias Foo = Bar - $access$ func foo() {} - $access$ var foo: Bar + 2️⃣$access$ class Foo {} #else - $access$ class Foo {} - $access$ struct Foo {} - $access$ enum Foo {} - $access$ protocol Foo {} - $access$ typealias Foo = Bar - $access$ func foo() {} - $access$ var foo: Bar + 3️⃣$access$ class Foo {} #endif """, testConfigurations: changingTestConfigurations - ) { assertDiagnosticWasEmittedOrNot in - assertDiagnosticWasEmittedOrNot(2, 3) - assertDiagnosticWasEmittedOrNot(3, 3) - assertDiagnosticWasEmittedOrNot(4, 3) - assertDiagnosticWasEmittedOrNot(5, 3) - assertDiagnosticWasEmittedOrNot(6, 3) - assertDiagnosticWasEmittedOrNot(7, 3) - assertDiagnosticWasEmittedOrNot(8, 3) - assertDiagnosticWasEmittedOrNot(10, 3) - assertDiagnosticWasEmittedOrNot(11, 3) - assertDiagnosticWasEmittedOrNot(12, 3) - assertDiagnosticWasEmittedOrNot(13, 3) - assertDiagnosticWasEmittedOrNot(14, 3) - assertDiagnosticWasEmittedOrNot(15, 3) - assertDiagnosticWasEmittedOrNot(16, 3) - assertDiagnosticWasEmittedOrNot(18, 3) - assertDiagnosticWasEmittedOrNot(19, 3) - assertDiagnosticWasEmittedOrNot(20, 3) - assertDiagnosticWasEmittedOrNot(21, 3) - assertDiagnosticWasEmittedOrNot(22, 3) - assertDiagnosticWasEmittedOrNot(23, 3) - assertDiagnosticWasEmittedOrNot(24, 3) + ) { original, expected in + [ + FindingSpec("1️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("2️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("3️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + ] } } @@ -146,25 +105,27 @@ final class FileScopedDeclarationPrivacyTests: LintOrFormatRuleTestCase { source: """ #if FOO #if BAR - $access$ class Foo {} - $access$ struct Foo {} - $access$ enum Foo {} - $access$ protocol Foo {} - $access$ typealias Foo = Bar - $access$ func foo() {} - $access$ var foo: Bar + 1️⃣$access$ class Foo {} + 2️⃣$access$ struct Foo {} + 3️⃣$access$ enum Foo {} + 4️⃣$access$ protocol Foo {} + 5️⃣$access$ typealias Foo = Bar + 6️⃣$access$ func foo() {} + 7️⃣$access$ var foo: Bar #endif #endif """, testConfigurations: changingTestConfigurations - ) { assertDiagnosticWasEmittedOrNot in - assertDiagnosticWasEmittedOrNot(3, 5) - assertDiagnosticWasEmittedOrNot(4, 5) - assertDiagnosticWasEmittedOrNot(5, 5) - assertDiagnosticWasEmittedOrNot(6, 5) - assertDiagnosticWasEmittedOrNot(7, 5) - assertDiagnosticWasEmittedOrNot(8, 5) - assertDiagnosticWasEmittedOrNot(9, 5) + ) { original, expected in + [ + FindingSpec("1️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("2️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("3️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("4️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("5️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("6️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("7️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + ] } } @@ -172,25 +133,29 @@ final class FileScopedDeclarationPrivacyTests: LintOrFormatRuleTestCase { runWithMultipleConfigurations( source: """ /// Some doc comment - $access$ class Foo {} + 1️⃣$access$ class Foo {} - @objc /* comment */ $access$ class Bar {} + @objc /* comment */ 2️⃣$access$ class Bar {} """, testConfigurations: changingTestConfigurations - ) { assertDiagnosticWasEmittedOrNot in - assertDiagnosticWasEmittedOrNot(2, 1) - assertDiagnosticWasEmittedOrNot(4, 21) + ) { original, expected in + [ + FindingSpec("1️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("2️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + ] } } func testModifierDetailIsPreserved() { runWithMultipleConfigurations( source: """ - public $access$(set) var foo: Int + public 1️⃣$access$(set) var foo: Int """, testConfigurations: changingTestConfigurations - ) { assertDiagnosticWasEmittedOrNot in - assertDiagnosticWasEmittedOrNot(1, 8) + ) { original, expected in + [ + FindingSpec("1️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + ] } } @@ -200,40 +165,35 @@ final class FileScopedDeclarationPrivacyTests: LintOrFormatRuleTestCase { testConfigurations: [TestConfiguration], file: StaticString = #file, line: UInt = #line, - completion: ((Int, Int) -> Void) -> Void + findingsProvider: (String, String) -> [FindingSpec] ) { for testConfig in testConfigurations { var configuration = Configuration.forTesting configuration.fileScopedDeclarationPrivacy.accessLevel = testConfig.desired let substitutedInput = source.replacingOccurrences(of: "$access$", with: testConfig.original) - let substitutedExpected = - source.replacingOccurrences(of: "$access$", with: testConfig.expected) - XCTAssertFormatting( + let markedSource = MarkedText(textWithMarkers: source) + let substitutedExpected = markedSource.textWithoutMarkers.replacingOccurrences( + of: "$access$", with: testConfig.expected) + + // Only use the findings if the output was expected to change. If it didn't change, then the + // rule wouldn't have emitted anything. + let findingSpecs: [FindingSpec] + if testConfig.original == testConfig.expected { + findingSpecs = [] + } else { + findingSpecs = findingsProvider(testConfig.original, testConfig.expected) + } + + assertFormatting( FileScopedDeclarationPrivacy.self, input: substitutedInput, expected: substitutedExpected, - checkForUnassertedDiagnostics: true, + findings: findingSpecs, configuration: configuration, file: file, line: line) - - let message: Finding.Message = - testConfig.desired == .private - ? .replaceFileprivateWithPrivate - : .replacePrivateWithFileprivate - - if testConfig.original == testConfig.expected { - completion { _, _ in - XCTAssertNotDiagnosed(message, file: file, line: line) - } - } else { - completion { diagnosticLine, diagnosticColumn in - XCTAssertDiagnosed( - message, line: diagnosticLine, column: diagnosticColumn, file: file, line: line) - } - } } } } diff --git a/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift b/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift index a7d4881a7..a28cb04ec 100644 --- a/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift +++ b/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift @@ -1,12 +1,17 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: Since we're putting the finding on the `enum` decl, we should have notes pointing to each +// `indirect` that should be removed from the cases. The finding should also probably be attached to +// the `enum` keyword, not the name, since the inserted keyword will be there. class FullyIndirectEnumTests: LintOrFormatRuleTestCase { func testAllIndirectCases() { - XCTAssertFormatting( + assertFormatting( FullyIndirectEnum.self, input: """ // Comment 1 - public enum DependencyGraphNode { + public enum 1️⃣DependencyGraphNode { internal indirect case userDefined(dependencies: [DependencyGraphNode]) // Comment 2 indirect case synthesized(dependencies: [DependencyGraphNode]) @@ -23,15 +28,19 @@ class FullyIndirectEnumTests: LintOrFormatRuleTestCase { case other(dependencies: [DependencyGraphNode]) var x: Int } - """) + """, + findings: [ + FindingSpec("1️⃣", message: "move 'indirect' before the enum declaration 'DependencyGraphNode' when all cases are indirect"), + ] + ) } func testAllIndirectCasesWithAttributes() { - XCTAssertFormatting( + assertFormatting( FullyIndirectEnum.self, input: """ // Comment 1 - public enum DependencyGraphNode { + public enum 1️⃣DependencyGraphNode { @someAttr internal indirect case userDefined(dependencies: [DependencyGraphNode]) // Comment 2 @someAttr indirect case synthesized(dependencies: [DependencyGraphNode]) @@ -48,7 +57,11 @@ class FullyIndirectEnumTests: LintOrFormatRuleTestCase { @someAttr case other(dependencies: [DependencyGraphNode]) var x: Int } - """) + """, + findings: [ + FindingSpec("1️⃣", message: "move 'indirect' before the enum declaration 'DependencyGraphNode' when all cases are indirect"), + ] + ) } func testNotAllIndirectCases() { @@ -60,7 +73,7 @@ class FullyIndirectEnumTests: LintOrFormatRuleTestCase { case west } """ - XCTAssertFormatting(FullyIndirectEnum.self, input: input, expected: input) + assertFormatting(FullyIndirectEnum.self, input: input, expected: input, findings: []) } func testAlreadyIndirectEnum() { @@ -72,7 +85,7 @@ class FullyIndirectEnumTests: LintOrFormatRuleTestCase { case west } """ - XCTAssertFormatting(FullyIndirectEnum.self, input: input, expected: input) + assertFormatting(FullyIndirectEnum.self, input: input, expected: input, findings: []) } func testCaselessEnum() { @@ -82,6 +95,6 @@ class FullyIndirectEnumTests: LintOrFormatRuleTestCase { public static let bar = "bar" } """ - XCTAssertFormatting(FullyIndirectEnum.self, input: input, expected: input) + assertFormatting(FullyIndirectEnum.self, input: input, expected: input, findings: []) } } diff --git a/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift b/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift index ce2109708..aa567341c 100644 --- a/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift +++ b/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift @@ -1,57 +1,62 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: The finding message should indicate what kind of literal it is (decimal, binary, etc.) and +// it should refer to "digits" instead of "numbers". final class GroupNumericLiteralsTests: LintOrFormatRuleTestCase { func testNumericGrouping() { - XCTAssertFormatting( + assertFormatting( GroupNumericLiterals.self, input: """ - let a = 9876543210 - let b = 1234 - let c = 0x34950309233 - let d = -0x34242 - let e = 0b10010010101 - let f = 0b101 - let g = 11_15_1999 - let h = 0o21743 - let i = -53096828347 - let j = 0000123 - let k = 0x00000012 - let l = 0x0000012 - let m = 0b00010010101 - let n = [ - 0xff00ff00, // comment - 0x00ff00ff, // comment - ] - """, + let a = 1️⃣9876543210 + let b = 1234 + let c = 2️⃣0x34950309233 + let d = -0x34242 + let e = 3️⃣0b10010010101 + let f = 0b101 + let g = 11_15_1999 + let h = 0o21743 + let i = -4️⃣53096828347 + let j = 5️⃣0000123 + let k = 6️⃣0x00000012 + let l = 0x0000012 + let m = 7️⃣0b00010010101 + let n = [ + 8️⃣0xff00ff00, // comment + 9️⃣0x00ff00ff, // comment + ] + """, expected: """ - let a = 9_876_543_210 - let b = 1234 - let c = 0x349_5030_9233 - let d = -0x34242 - let e = 0b100_10010101 - let f = 0b101 - let g = 11_15_1999 - let h = 0o21743 - let i = -53_096_828_347 - let j = 0_000_123 - let k = 0x0000_0012 - let l = 0x0000012 - let m = 0b000_10010101 - let n = [ - 0xff00_ff00, // comment - 0x00ff_00ff, // comment - ] - """) - XCTAssertDiagnosed(.groupNumericLiteral(every: 3)) - XCTAssertDiagnosed(.groupNumericLiteral(every: 3)) - XCTAssertDiagnosed(.groupNumericLiteral(every: 3)) - XCTAssertNotDiagnosed(.groupNumericLiteral(every: 3)) - XCTAssertDiagnosed(.groupNumericLiteral(every: 4)) - XCTAssertDiagnosed(.groupNumericLiteral(every: 4)) - XCTAssertDiagnosed(.groupNumericLiteral(every: 8)) - XCTAssertDiagnosed(.groupNumericLiteral(every: 8)) - XCTAssertNotDiagnosed(.groupNumericLiteral(every: 8)) - XCTAssertDiagnosed(.groupNumericLiteral(every: 4)) - XCTAssertDiagnosed(.groupNumericLiteral(every: 4)) + let a = 9_876_543_210 + let b = 1234 + let c = 0x349_5030_9233 + let d = -0x34242 + let e = 0b100_10010101 + let f = 0b101 + let g = 11_15_1999 + let h = 0o21743 + let i = -53_096_828_347 + let j = 0_000_123 + let k = 0x0000_0012 + let l = 0x0000012 + let m = 0b000_10010101 + let n = [ + 0xff00_ff00, // comment + 0x00ff_00ff, // comment + ] + """, + findings: [ + FindingSpec("1️⃣", message: "group numeric literal using '_' every 3rd number"), + FindingSpec("2️⃣", message: "group numeric literal using '_' every 4th number"), + FindingSpec("3️⃣", message: "group numeric literal using '_' every 8th number"), + FindingSpec("4️⃣", message: "group numeric literal using '_' every 3rd number"), + FindingSpec("5️⃣", message: "group numeric literal using '_' every 3rd number"), + FindingSpec("6️⃣", message: "group numeric literal using '_' every 4th number"), + FindingSpec("7️⃣", message: "group numeric literal using '_' every 8th number"), + FindingSpec("8️⃣", message: "group numeric literal using '_' every 4th number"), + FindingSpec("9️⃣", message: "group numeric literal using '_' every 4th number"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift b/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift index 546b6bfe9..902438c96 100644 --- a/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift +++ b/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift @@ -1,19 +1,24 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class IdentifiersMustBeASCIITests: LintOrFormatRuleTestCase { func testInvalidIdentifiers() { - let input = - """ - let Te$t = 1 - var fo😎o = 2 - let Δx = newX - previousX - var 🤩😆 = 20 + assertLint( + IdentifiersMustBeASCII.self, """ - performLint(IdentifiersMustBeASCII.self, input: input) - XCTAssertDiagnosed(.nonASCIICharsNotAllowed(["😎"],"fo😎o")) - // TODO: It would be nice to allow Δ (among other mathematically meaningful symbols) without - // a lot of special cases; investigate this. - XCTAssertDiagnosed(.nonASCIICharsNotAllowed(["Δ"],"Δx")) - XCTAssertDiagnosed(.nonASCIICharsNotAllowed(["🤩", "😆"], "🤩😆")) + let Te$t = 1 + var 1️⃣fo😎o = 2 + let 2️⃣Δx = newX - previousX + var 3️⃣🤩😆 = 20 + """, + findings: [ + FindingSpec("1️⃣", message: "remove non-ASCII characters from 'fo😎o': 😎"), + // TODO: It would be nice to allow Δ (among other mathematically meaningful symbols) without + // a lot of special cases; investigate this. + FindingSpec("2️⃣", message: "remove non-ASCII characters from 'Δx': Δ"), + FindingSpec("3️⃣", message: "remove non-ASCII characters from '🤩😆': 🤩, 😆"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift index a13e74221..1bef38333 100644 --- a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift +++ b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift @@ -1,10 +1,10 @@ +import SwiftFormatConfiguration import SwiftParser import XCTest @_spi(Rules) @_spi(Testing) import SwiftFormat -@_spi(Testing) import _SwiftFormatTestSupport -class ImportsXCTestVisitorTests: DiagnosingTestCase { +class ImportsXCTestVisitorTests: XCTestCase { func testDoesNotImportXCTest() throws { XCTAssertEqual( try makeContextAndSetImportsXCTest(source: """ @@ -50,7 +50,13 @@ class ImportsXCTestVisitorTests: DiagnosingTestCase { /// import state. private func makeContextAndSetImportsXCTest(source: String) throws -> Context.XCTestImportState { let sourceFile = Parser.parse(source: source) - let context = makeContext(sourceFileSyntax: sourceFile) + let context = Context( + configuration: Configuration(), + operatorTable: .standardOperators, + findingConsumer: { _ in }, + fileURL: URL(fileURLWithPath: "/tmp/test.swift"), + sourceFileSyntax: sourceFile, + ruleNameCache: ruleNameCache) setImportsXCTest(context: context, sourceFile: sourceFile) return context.importsXCTest } diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index a57c9782e..523769d26 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -8,34 +8,48 @@ import XCTest @_spi(Testing) import _SwiftFormatTestSupport class LintOrFormatRuleTestCase: DiagnosingTestCase { - /// Performs a lint using the provided linter rule on the provided input. + /// Performs a lint using the provided linter rule on the provided input and asserts that the + /// emitted findings are correct. /// /// - Parameters: /// - type: The metatype of the lint rule you wish to perform. - /// - input: The input code. - /// - file: The file the test resides in (defaults to the current caller's file) - /// - line: The line the test resides in (defaults to the current caller's line) - final func performLint( + /// - markedSource: The input source code, which may include emoji markers at the locations + /// where findings are expected to be emitted. + /// - findings: A list of `FindingSpec` values that describe the findings that are expected to + /// be emitted. + /// - file: The file the test resides in (defaults to the current caller's file). + /// - line: The line the test resides in (defaults to the current caller's line). + final func assertLint( _ type: LintRule.Type, - input: String, + _ markedSource: String, + findings: [FindingSpec] = [], file: StaticString = #file, line: UInt = #line ) { + let markedText = MarkedText(textWithMarkers: markedSource) + let tree = Parser.parse(source: markedText.textWithoutMarkers) let sourceFileSyntax = try! restoringLegacyTriviaBehavior( - OperatorTable.standardOperators.foldAll(Parser.parse(source: input)) - .as(SourceFileSyntax.self)!) + OperatorTable.standardOperators.foldAll(tree).as(SourceFileSyntax.self)!) + + var emittedFindings = [Finding]() // Force the rule to be enabled while we test it. var configuration = Configuration.forTesting configuration.rules[type.ruleName] = true - let context = makeContext(sourceFileSyntax: sourceFileSyntax, configuration: configuration) - - // If we're linting, then indicate that we want to fail for unasserted diagnostics when the test - // is torn down. - shouldCheckForUnassertedDiagnostics = true - + let context = makeContext( + sourceFileSyntax: sourceFileSyntax, + configuration: configuration, + findingConsumer: { emittedFindings.append($0) }) let linter = type.init(context: context) linter.walk(sourceFileSyntax) + + assertFindings( + expected: findings, + markerLocations: markedText.markers, + emittedFindings: emittedFindings, + context: context, + file: file, + line: line) } /// Asserts that the result of applying a formatter to the provided input code yields the output. @@ -46,32 +60,45 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { /// - formatType: The metatype of the format rule you wish to apply. /// - input: The unformatted input code. /// - expected: The expected result of formatting the input code. - /// - checkForUnassertedDiagnostics: Fail the test if there are any unasserted linter - /// diagnostics. + /// - findings: A list of `FindingSpec` values that describe the findings that are expected to + /// be emitted. /// - configuration: The configuration to use when formatting (or nil to use the default). /// - file: The file the test resides in (defaults to the current caller's file) /// - line: The line the test resides in (defaults to the current caller's line) - final func XCTAssertFormatting( + final func assertFormatting( _ formatType: SyntaxFormatRule.Type, input: String, expected: String, - checkForUnassertedDiagnostics: Bool = false, + findings: [FindingSpec] = [], configuration: Configuration? = nil, file: StaticString = #file, line: UInt = #line ) { + let markedInput = MarkedText(textWithMarkers: input) + let tree = Parser.parse(source: markedInput.textWithoutMarkers) let sourceFileSyntax = try! restoringLegacyTriviaBehavior( - OperatorTable.standardOperators.foldAll(Parser.parse(source: input)) - .as(SourceFileSyntax.self)!) + OperatorTable.standardOperators.foldAll(tree).as(SourceFileSyntax.self)!) + + var emittedFindings = [Finding]() // Force the rule to be enabled while we test it. var configuration = configuration ?? Configuration.forTesting configuration.rules[formatType.ruleName] = true - let context = makeContext(sourceFileSyntax: sourceFileSyntax, configuration: configuration) + let context = makeContext( + sourceFileSyntax: sourceFileSyntax, + configuration: configuration, + findingConsumer: { emittedFindings.append($0) }) - shouldCheckForUnassertedDiagnostics = checkForUnassertedDiagnostics let formatter = formatType.init(context: context) let actual = formatter.visit(sourceFileSyntax) - XCTAssertStringsEqualWithDiff(actual.description, expected, file: file, line: line) + assertStringsEqualWithDiff(actual.description, expected, file: file, line: line) + + assertFindings( + expected: findings, + markerLocations: markedInput.markers, + emittedFindings: emittedFindings, + context: context, + file: file, + line: line) } } diff --git a/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift b/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift index 2a4e13652..bf68c4589 100644 --- a/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift @@ -1,39 +1,43 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class NeverForceUnwrapTests: LintOrFormatRuleTestCase { func testUnsafeUnwrap() { - let input = - """ - func someFunc() -> Int { - var a = getInt() - var b = a as! Int - let c = (someValue())! - let d = String(a)! - let regex = try! NSRegularExpression(pattern: "a*b+c?") - let e = /*comment about stuff*/ [1: a, 2: b, 3: c][4]! - var f = a as! /*comment about this type*/ FooBarType - return a! - } - """ - performLint(NeverForceUnwrap.self, input: input) - XCTAssertDiagnosed(.doNotForceCast(name: "Int"), line: 3, column: 11) - XCTAssertDiagnosed(.doNotForceUnwrap(name: "(someValue())"), line: 4, column: 11) - XCTAssertDiagnosed(.doNotForceUnwrap(name: "String(a)"), line: 5, column: 11) - XCTAssertNotDiagnosed(.doNotForceCast(name: "try")) - XCTAssertNotDiagnosed(.doNotForceUnwrap(name: "try")) - XCTAssertDiagnosed(.doNotForceUnwrap(name: "[1: a, 2: b, 3: c][4]"), line: 7, column: 35) - XCTAssertDiagnosed(.doNotForceCast(name: "FooBarType"), line: 8, column: 11) - XCTAssertDiagnosed(.doNotForceUnwrap(name: "a"), line: 9, column: 10) + assertLint( + NeverForceUnwrap.self, + """ + func someFunc() -> Int { + var a = getInt() + var b = 1️⃣a as! Int + let c = 2️⃣(someValue())! + let d = 3️⃣String(a)! + let regex = try! NSRegularExpression(pattern: "a*b+c?") + let e = /*comment about stuff*/ 4️⃣[1: a, 2: b, 3: c][4]! + var f = 5️⃣a as! /*comment about this type*/ FooBarType + return 6️⃣a! + } + """, + findings: [ + FindingSpec("1️⃣", message: "do not force cast to 'Int'"), + FindingSpec("2️⃣", message: "do not force unwrap '(someValue())'"), + FindingSpec("3️⃣", message: "do not force unwrap 'String(a)'"), + FindingSpec("4️⃣", message: "do not force unwrap '[1: a, 2: b, 3: c][4]'"), + FindingSpec("5️⃣", message: "do not force cast to 'FooBarType'"), + FindingSpec("6️⃣", message: "do not force unwrap 'a'"), + ] + ) } func testIgnoreTestCode() { - let input = - """ + assertLint( + NeverForceUnwrap.self, + """ import XCTest var b = a as! Int - """ - performLint(NeverUseImplicitlyUnwrappedOptionals.self, input: input) - XCTAssertNotDiagnosed(.doNotForceCast(name: "Int")) + """, + findings: [] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift index a4bf0e128..2be59ac47 100644 --- a/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift @@ -1,36 +1,41 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class NeverUseForceTryTests: LintOrFormatRuleTestCase { func testInvalidTryExpression() { - let input = + assertLint( + NeverUseForceTry.self, """ - let document = try! Document(path: "important.data") + let document = 1️⃣try! Document(path: "important.data") let document = try Document(path: "important.data") - let x = try! someThrowingFunction() + let x = 2️⃣try! someThrowingFunction() let x = try? someThrowingFunction( - try! someThrowingFunction() + 3️⃣try! someThrowingFunction() ) let x = try someThrowingFunction( - try! someThrowingFunction() + 4️⃣try! someThrowingFunction() ) if let data = try? fetchDataFromDisk() { return data } - """ - performLint(NeverUseForceTry.self, input: input) - XCTAssertDiagnosed(.doNotForceTry) - XCTAssertDiagnosed(.doNotForceTry) - XCTAssertDiagnosed(.doNotForceTry) - XCTAssertDiagnosed(.doNotForceTry) - XCTAssertNotDiagnosed(.doNotForceTry) + """, + findings: [ + FindingSpec("1️⃣", message: "do not use force try"), + FindingSpec("2️⃣", message: "do not use force try"), + FindingSpec("3️⃣", message: "do not use force try"), + FindingSpec("4️⃣", message: "do not use force try"), + ] + ) } func testAllowForceTryInTestCode() { - let input = + assertLint( + NeverUseForceTry.self, """ import XCTest let document = try! Document(path: "important.data") - """ - performLint(NeverUseForceTry.self, input: input) - XCTAssertNotDiagnosed(.doNotForceTry) + """, + findings: [] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift index 15e4c1136..6a210b826 100644 --- a/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift @@ -1,35 +1,38 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class NeverUseImplicitlyUnwrappedOptionalsTests: LintOrFormatRuleTestCase { func testInvalidVariableUnwrapping() { - let input = + assertLint( + NeverUseImplicitlyUnwrappedOptionals.self, """ import Core import Foundation import SwiftSyntax var foo: Int? - var s: String! - var f: /*this is a Foo*/Foo! + var s: 1️⃣String! + var f: /*this is a Foo*/2️⃣Foo! var c, d, e: Float @IBOutlet var button: UIButton! - """ - performLint(NeverUseImplicitlyUnwrappedOptionals.self, input: input) - XCTAssertNotDiagnosed(.doNotUseImplicitUnwrapping(identifier: "Int")) - XCTAssertDiagnosed(.doNotUseImplicitUnwrapping(identifier: "String")) - XCTAssertDiagnosed(.doNotUseImplicitUnwrapping(identifier: "Foo")) - XCTAssertNotDiagnosed(.doNotUseImplicitUnwrapping(identifier: "Float")) - XCTAssertNotDiagnosed(.doNotUseImplicitUnwrapping(identifier: "UIButton")) + """, + findings: [ + FindingSpec("1️⃣", message: "use 'String' or 'String?' instead of 'String!'"), + FindingSpec("2️⃣", message: "use 'Foo' or 'Foo?' instead of 'Foo!'"), + ] + ) } func testIgnoreTestCode() { - let input = + assertLint( + NeverUseImplicitlyUnwrappedOptionals.self, """ import XCTest var s: String! - """ - performLint(NeverUseImplicitlyUnwrappedOptionals.self, input: input) - XCTAssertNotDiagnosed(.doNotUseImplicitUnwrapping(identifier: "String")) + """, + findings: [] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift b/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift index 8f1b49175..15853927d 100644 --- a/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift @@ -1,69 +1,78 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: We should have notes on each of the declarations inside the extension that we modify. +// Also fix the lack of trimming around the extension name, and we should say "extension X" instead +// of just "X". final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { func testExtensionDeclarationAccessLevel() { - XCTAssertFormatting( + assertFormatting( NoAccessLevelOnExtensionDeclaration.self, input: """ - public extension Foo { - var x: Bool - // Comment 1 - internal var y: Bool - // Comment 2 - static var z: Bool - // Comment 3 - static func someFunc() {} - init() {} - subscript(index: Int) -> Element {} - protocol SomeProtocol {} - class SomeClass {} - struct SomeStruct {} - enum SomeEnum {} - typealias Foo = Bar - } - internal extension Bar { - var a: Int - var b: Int - } - """, + 1️⃣public extension Foo { + var x: Bool + // Comment 1 + internal var y: Bool + // Comment 2 + static var z: Bool + // Comment 3 + static func someFunc() {} + init() {} + subscript(index: Int) -> Element {} + protocol SomeProtocol {} + class SomeClass {} + struct SomeStruct {} + enum SomeEnum {} + typealias Foo = Bar + } + 2️⃣internal extension Bar { + var a: Int + var b: Int + } + """, expected: """ - extension Foo { - public var x: Bool - // Comment 1 - internal var y: Bool - // Comment 2 - public static var z: Bool - // Comment 3 - public static func someFunc() {} - public init() {} - public subscript(index: Int) -> Element {} - public protocol SomeProtocol {} - public class SomeClass {} - public struct SomeStruct {} - public enum SomeEnum {} - public typealias Foo = Bar - } - extension Bar { - var a: Int - var b: Int - } - """ + extension Foo { + public var x: Bool + // Comment 1 + internal var y: Bool + // Comment 2 + public static var z: Bool + // Comment 3 + public static func someFunc() {} + public init() {} + public subscript(index: Int) -> Element {} + public protocol SomeProtocol {} + public class SomeClass {} + public struct SomeStruct {} + public enum SomeEnum {} + public typealias Foo = Bar + } + extension Bar { + var a: Int + var b: Int + } + """, + findings: [ + FindingSpec("1️⃣", message: "move the 'public' access keyword to precede each member inside the extension"), + FindingSpec("2️⃣", message: "remove redundant 'internal' access keyword from 'Bar '"), + ] ) } func testPreservesCommentOnRemovedModifier() { - XCTAssertFormatting( + assertFormatting( NoAccessLevelOnExtensionDeclaration.self, input: """ /// This doc comment should stick around. - public extension Foo { + 1️⃣public extension Foo { func f() {} // This should not change. func g() {} } /// So should this one. - internal extension Foo { + 2️⃣internal extension Foo { func f() {} // This should not change. func g() {} @@ -83,15 +92,19 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { // This should not change. func g() {} } - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move the 'public' access keyword to precede each member inside the extension"), + FindingSpec("2️⃣", message: "remove redundant 'internal' access keyword from 'Foo '"), + ] ) } func testPrivateIsEffectivelyFileprivate() { - XCTAssertFormatting( + assertFormatting( NoAccessLevelOnExtensionDeclaration.self, input: """ - private extension Foo { + 1️⃣private extension Foo { func f() {} } """, @@ -99,78 +112,85 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { extension Foo { fileprivate func f() {} } - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move the 'private' access keyword to precede each member inside the extension"), + ] ) } func testExtensionWithAnnotation() { - XCTAssertFormatting( + assertFormatting( NoAccessLevelOnExtensionDeclaration.self, - input: - """ + input: """ /// This extension has a comment. - @objc public extension Foo { + @objc 1️⃣public extension Foo { } """, - expected: - """ + expected: """ /// This extension has a comment. @objc extension Foo { } - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move the 'public' access keyword to precede each member inside the extension"), + ] ) } func testPreservesInlineAnnotationsBeforeAddedAccessLevelModifiers() { - XCTAssertFormatting( + assertFormatting( NoAccessLevelOnExtensionDeclaration.self, input: """ - /// This extension has a comment. - public extension Foo { - /// This property has a doc comment. - @objc var x: Bool { get { return true }} - // This property has a developer comment. - @objc static var z: Bool { get { return false }} - /// This static function has a doc comment. - @objc static func someStaticFunc() {} - @objc init(with foo: Foo) {} - @objc func someOtherFunc() {} - @objc protocol SomeProtocol {} - @objc class SomeClass : NSObject {} - @objc associatedtype SomeType - @objc enum SomeEnum : Int { - case SomeInt = 32 - } - } - """, + /// This extension has a comment. + 1️⃣public extension Foo { + /// This property has a doc comment. + @objc var x: Bool { get { return true }} + // This property has a developer comment. + @objc static var z: Bool { get { return false }} + /// This static function has a doc comment. + @objc static func someStaticFunc() {} + @objc init(with foo: Foo) {} + @objc func someOtherFunc() {} + @objc protocol SomeProtocol {} + @objc class SomeClass : NSObject {} + @objc associatedtype SomeType + @objc enum SomeEnum : Int { + case SomeInt = 32 + } + } + """, expected: """ - /// This extension has a comment. - extension Foo { - /// This property has a doc comment. - @objc public var x: Bool { get { return true }} - // This property has a developer comment. - @objc public static var z: Bool { get { return false }} - /// This static function has a doc comment. - @objc public static func someStaticFunc() {} - @objc public init(with foo: Foo) {} - @objc public func someOtherFunc() {} - @objc public protocol SomeProtocol {} - @objc public class SomeClass : NSObject {} - @objc public associatedtype SomeType - @objc public enum SomeEnum : Int { - case SomeInt = 32 - } - } - """ + /// This extension has a comment. + extension Foo { + /// This property has a doc comment. + @objc public var x: Bool { get { return true }} + // This property has a developer comment. + @objc public static var z: Bool { get { return false }} + /// This static function has a doc comment. + @objc public static func someStaticFunc() {} + @objc public init(with foo: Foo) {} + @objc public func someOtherFunc() {} + @objc public protocol SomeProtocol {} + @objc public class SomeClass : NSObject {} + @objc public associatedtype SomeType + @objc public enum SomeEnum : Int { + case SomeInt = 32 + } + } + """, + findings: [ + FindingSpec("1️⃣", message: "move the 'public' access keyword to precede each member inside the extension"), + ] ) } func testPreservesMultiLineAnnotationsBeforeAddedAccessLevelModifiers() { - XCTAssertFormatting( + assertFormatting( NoAccessLevelOnExtensionDeclaration.self, input: """ /// This extension has a comment. - public extension Foo { + 1️⃣public extension Foo { /// This property has a doc comment. @available(iOS 13, *) var x: Bool { get { return true }} @@ -239,7 +259,10 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { ) public func doSomething(_ foo : Foo, bar : Bar, baz : Baz) {} } - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move the 'public' access keyword to precede each member inside the extension"), + ] ) } } diff --git a/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift b/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift index e635a0fbc..83cb939c3 100644 --- a/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift @@ -1,23 +1,25 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { func testAssignmentInExpressionContextIsDiagnosed() { - XCTAssertFormatting( + assertFormatting( NoAssignmentInExpressions.self, input: """ - foo(bar, baz = quux, a + b) + foo(bar, 1️⃣baz = quux, a + b) """, expected: """ foo(bar, baz = quux, a + b) - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + ] ) - XCTAssertDiagnosed(.moveAssignmentToOwnStatement, line: 1, column: 10) - // Make sure no other expressions were diagnosed. - XCTAssertNotDiagnosed(.moveAssignmentToOwnStatement) } func testReturnStatementWithoutExpressionIsUnchanged() { - XCTAssertFormatting( + assertFormatting( NoAssignmentInExpressions.self, input: """ func foo() { @@ -28,13 +30,13 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { func foo() { return } - """ + """, + findings: [] ) - XCTAssertNotDiagnosed(.moveAssignmentToOwnStatement) } func testReturnStatementWithNonAssignmentExpressionIsUnchanged() { - XCTAssertFormatting( + assertFormatting( NoAssignmentInExpressions.self, input: """ func foo() { @@ -45,19 +47,19 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { func foo() { return a + b } - """ + """, + findings: [] ) - XCTAssertNotDiagnosed(.moveAssignmentToOwnStatement) } func testReturnStatementWithSimpleAssignmentExpressionIsExpanded() { // For this and similar tests below, we don't try to match the leading indentation in the new // `return` statement; the pretty-printer will fix it up. - XCTAssertFormatting( + assertFormatting( NoAssignmentInExpressions.self, input: """ func foo() { - return a = b + return 1️⃣a = b } """, expected: """ @@ -65,17 +67,19 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { a = b return } - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + ] ) - XCTAssertDiagnosed(.moveAssignmentToOwnStatement, line: 2, column: 10) } func testReturnStatementWithCompoundAssignmentExpressionIsExpanded() { - XCTAssertFormatting( + assertFormatting( NoAssignmentInExpressions.self, input: """ func foo() { - return a += b + return 1️⃣a += b } """, expected: """ @@ -83,18 +87,20 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { a += b return } - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + ] ) - XCTAssertDiagnosed(.moveAssignmentToOwnStatement, line: 2, column: 10) } func testReturnStatementWithAssignmentDealsWithLeadingLineCommentSensibly() { - XCTAssertFormatting( + assertFormatting( NoAssignmentInExpressions.self, input: """ func foo() { // some comment - return a = b + return 1️⃣a = b } """, expected: """ @@ -103,17 +109,19 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { a = b return } - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + ] ) - XCTAssertDiagnosed(.moveAssignmentToOwnStatement, line: 3, column: 10) } func testReturnStatementWithAssignmentDealsWithTrailingLineCommentSensibly() { - XCTAssertFormatting( + assertFormatting( NoAssignmentInExpressions.self, input: """ func foo() { - return a = b // some comment + return 1️⃣a = b // some comment } """, expected: """ @@ -121,17 +129,19 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { a = b return // some comment } - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + ] ) - XCTAssertDiagnosed(.moveAssignmentToOwnStatement, line: 2, column: 10) } func testReturnStatementWithAssignmentDealsWithTrailingBlockCommentSensibly() { - XCTAssertFormatting( + assertFormatting( NoAssignmentInExpressions.self, input: """ func foo() { - return a = b /* some comment */ + return 1️⃣a = b /* some comment */ } """, expected: """ @@ -139,17 +149,19 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { a = b return /* some comment */ } - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + ] ) - XCTAssertDiagnosed(.moveAssignmentToOwnStatement, line: 2, column: 10) } func testReturnStatementWithAssignmentDealsWithNestedBlockCommentSensibly() { - XCTAssertFormatting( + assertFormatting( NoAssignmentInExpressions.self, input: """ func foo() { - return /* some comment */ a = b + return /* some comment */ 1️⃣a = b } """, expected: """ @@ -157,13 +169,15 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { /* some comment */ a = b return } - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + ] ) - XCTAssertDiagnosed(.moveAssignmentToOwnStatement, line: 2, column: 29) } func testTryAndAwaitAssignmentExpressionsAreUnchanged() { - XCTAssertFormatting( + assertFormatting( NoAssignmentInExpressions.self, input: """ func foo() { @@ -176,13 +190,13 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { try a.b = c await a.b = c } - """ + """, + findings: [] ) - XCTAssertNotDiagnosed(.moveAssignmentToOwnStatement) } func testAssignmentExpressionsInAllowedFunctions() { - XCTAssertFormatting( + assertFormatting( NoAssignmentInExpressions.self, input: """ // These should not diagnose. @@ -193,7 +207,7 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { someRegularFunction { a = b } // This should be diagnosed. - someRegularFunction(a = b) + someRegularFunction(1️⃣a = b) """, expected: """ // These should not diagnose. @@ -205,10 +219,10 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { // This should be diagnosed. someRegularFunction(a = b) - """ + """, + findings: [ + FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + ] ) - XCTAssertDiagnosed(.moveAssignmentToOwnStatement, line: 9, column: 21) - // Make sure no other expressions were diagnosed. - XCTAssertNotDiagnosed(.moveAssignmentToOwnStatement) } } diff --git a/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift b/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift index 7ec6dd39d..bc2fe31c7 100644 --- a/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift @@ -1,34 +1,39 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class NoBlockCommentsTests: LintOrFormatRuleTestCase { func testDiagnoseBlockComments() { - let input = + assertLint( + NoBlockComments.self, """ - /* + 1️⃣/* Lorem ipsum dolor sit amet, at nonumes adipisci sea, natum offendit vis ex. Audiam legendos expetenda ei quo, nonumes msensibus eloquentiam ex vix. */ - let a = /*ff*/10 /*ff*/ + 10 - var b = 0/*Block Comment inline with code*/ + let a = 2️⃣/*ff*/10 3️⃣/*ff*/ + 10 + var b = 04️⃣/*Block Comment inline with code*/ - /* + 5️⃣/* Block Comment */ let c = a + b - /* This is the end + 6️⃣/* This is the end of a file */ - """ - performLint(NoBlockComments.self, input: input) - XCTAssertDiagnosed(.avoidBlockComment) - XCTAssertDiagnosed(.avoidBlockComment) - XCTAssertDiagnosed(.avoidBlockComment) - XCTAssertDiagnosed(.avoidBlockComment) - XCTAssertDiagnosed(.avoidBlockComment) - XCTAssertDiagnosed(.avoidBlockComment) + """, + findings: [ + FindingSpec("1️⃣", message: "replace this block comment with line comments"), + FindingSpec("2️⃣", message: "replace this block comment with line comments"), + FindingSpec("3️⃣", message: "replace this block comment with line comments"), + FindingSpec("4️⃣", message: "replace this block comment with line comments"), + FindingSpec("5️⃣", message: "replace this block comment with line comments"), + FindingSpec("6️⃣", message: "replace this block comment with line comments"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift b/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift index b745a3d66..5332e72b5 100644 --- a/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift @@ -1,32 +1,34 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { func testFallthroughCases() { - XCTAssertFormatting( + assertFormatting( NoCasesWithOnlyFallthrough.self, input: """ switch numbers { case 1: print("one") - case 2: fallthrough - case 3: fallthrough + 1️⃣case 2: fallthrough + 2️⃣case 3: fallthrough case 4: print("two to four") - case 5: fallthrough + 3️⃣case 5: fallthrough case 7: print("five or seven") default: break } switch letters { - case "a": fallthrough - case "b", "c": fallthrough + 4️⃣case "a": fallthrough + 5️⃣case "b", "c": fallthrough case "d": print("abcd") case "e": print("e") - case "f": fallthrough + 6️⃣case "f": fallthrough case "z": print("fz") default: break } switch tokens { case .comma: print(",") - case .rightBrace: fallthrough - case .leftBrace: fallthrough + 7️⃣case .rightBrace: fallthrough + 8️⃣case .leftBrace: fallthrough case .braces: print("{}") case .period: print(".") case .empty: fallthrough @@ -54,26 +56,27 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { default: break } """, - checkForUnassertedDiagnostics: true) - - XCTAssertDiagnosed(.collapseCase, line: 3, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 4, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 6, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 11, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 12, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 15, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 21, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 22, column: 1) + findings: [ + FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("2️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("3️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("4️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("5️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("6️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("7️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("8️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + ] + ) } func testFallthroughCasesWithCommentsAreNotCombined() { - XCTAssertFormatting( + assertFormatting( NoCasesWithOnlyFallthrough.self, input: """ switch numbers { case 1: return 0 // This return has an inline comment. - case 2: fallthrough + 1️⃣case 2: fallthrough // This case is commented so it should stay. case 3: fallthrough @@ -81,7 +84,7 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { // This fallthrough is commented so it should stay. fallthrough case 5: fallthrough // This fallthrough is relevant. - case 6: + 2️⃣case 6: fallthrough // This case has a descriptive comment. case 7: print("got here") @@ -102,23 +105,24 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { case 6, 7: print("got here") } """, - checkForUnassertedDiagnostics: true) - - XCTAssertDiagnosed(.collapseCase, line: 4, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 12, column: 1) + findings: [ + FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("2️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + ] + ) } func testCommentsAroundCombinedCasesStayInPlace() { - XCTAssertFormatting( + assertFormatting( NoCasesWithOnlyFallthrough.self, input: """ switch numbers { case 5: return 42 // This return is important. - case 6: fallthrough + 1️⃣case 6: fallthrough // This case has an important comment. case 7: print("6 to 7") - case 8: fallthrough + 2️⃣case 8: fallthrough // This case has an extra leading newline for emphasis. case 9: print("8 to 9") @@ -135,27 +139,29 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { case 8, 9: print("8 to 9") } """, - checkForUnassertedDiagnostics: true) - - XCTAssertDiagnosed(.collapseCase, line: 4, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 7, column: 1) + findings: [ + FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("2️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + ] + ) } func testNestedSwitches() { - XCTAssertFormatting( + // FIXME: Finding #3 is at an odd column; it should be before the `case`. Look into this. + assertFormatting( NoCasesWithOnlyFallthrough.self, input: """ switch x { - case 1: fallthrough - case 2: fallthrough + 1️⃣case 1: fallthrough + 2️⃣case 2: fallthrough case 3: switch y { - case 1: fallthrough + case 13️⃣: fallthrough case 2: print(2) } case 4: switch y { - case 1: fallthrough + 4️⃣case 1: fallthrough case 2: print(2) } } @@ -172,27 +178,27 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { } } """, - checkForUnassertedDiagnostics: true) - - XCTAssertDiagnosed(.collapseCase, line: 2, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 3, column: 1) - // TODO: Column 9 seems wrong here; it should be 3. Look into this. - XCTAssertDiagnosed(.collapseCase, line: 6, column: 9) - XCTAssertDiagnosed(.collapseCase, line: 11, column: 3) + findings: [ + FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("2️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("3️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("4️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + ] + ) } func testCasesInsideConditionalCompilationBlock() { - XCTAssertFormatting( + assertFormatting( NoCasesWithOnlyFallthrough.self, input: """ switch x { case 1: fallthrough #if FOO - case 2: fallthrough + 1️⃣case 2: fallthrough case 3: print(3) case 4: print(4) #endif - case 5: fallthrough + 2️⃣case 5: fallthrough case 6: print(6) #if BAR #if BAZ @@ -222,29 +228,30 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { case 10: print(10) } """, - checkForUnassertedDiagnostics: true) - - XCTAssertDiagnosed(.collapseCase, line: 4, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 8, column: 1) + findings: [ + FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("2️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + ] + ) } func testCasesWithWhereClauses() { // As noted in the rule implementation, the formatted result should include a newline before any // case items that have `where` clauses if they follow any case items that do not, to avoid // compiler warnings. This is handled by the pretty printer, not this rule. - XCTAssertFormatting( + assertFormatting( NoCasesWithOnlyFallthrough.self, input: """ switch x { - case 1 where y < 0: fallthrough - case 2 where y == 0: fallthrough - case 3 where y < 0: fallthrough + 1️⃣case 1 where y < 0: fallthrough + 2️⃣case 2 where y == 0: fallthrough + 3️⃣case 3 where y < 0: fallthrough case 4 where y != 0: print(4) - case 5: fallthrough - case 6: fallthrough - case 7: fallthrough - case 8: fallthrough - case 9: fallthrough + 4️⃣case 5: fallthrough + 5️⃣case 6: fallthrough + 6️⃣case 7: fallthrough + 7️⃣case 8: fallthrough + 8️⃣case 9: fallthrough case 10 where y == 0: print(10) default: print("?") } @@ -256,31 +263,32 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { default: print("?") } """, - checkForUnassertedDiagnostics: true) - - XCTAssertDiagnosed(.collapseCase, line: 2, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 3, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 4, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 6, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 7, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 8, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 9, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 10, column: 1) + findings: [ + FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("2️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("3️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("4️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("5️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("6️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("7️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("8️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + ] + ) } func testCasesWithValueBindingsAreNotMerged() { - XCTAssertFormatting( + assertFormatting( NoCasesWithOnlyFallthrough.self, input: """ switch x { - case .a: fallthrough + 1️⃣case .a: fallthrough case .b: fallthrough case .c(let x): fallthrough case .d(let y): fallthrough - case .e: fallthrough + 2️⃣case .e: fallthrough case .f: fallthrough case (let g, let h): fallthrough - case .i: fallthrough + 3️⃣case .i: fallthrough case .j?: fallthrough case let k as K: fallthrough case .l: break @@ -298,19 +306,20 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { case .l: break } """, - checkForUnassertedDiagnostics: true) - - XCTAssertDiagnosed(.collapseCase, line: 2, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 6, column: 1) - XCTAssertDiagnosed(.collapseCase, line: 9, column: 1) + findings: [ + FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("2️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("3️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + ] + ) } func testFallthroughOnlyCasesAreNotMergedWithDefault() { - XCTAssertFormatting( + assertFormatting( NoCasesWithOnlyFallthrough.self, input: """ switch x { - case .a: fallthrough + 1️⃣case .a: fallthrough case .b: fallthrough default: print("got here") } @@ -321,17 +330,18 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { default: print("got here") } """, - checkForUnassertedDiagnostics: true) - - XCTAssertDiagnosed(.collapseCase, line: 2, column: 1) + findings: [ + FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + ] + ) } func testFallthroughOnlyCasesAreNotMergedWithUnknownDefault() { - XCTAssertFormatting( + assertFormatting( NoCasesWithOnlyFallthrough.self, input: """ switch x { - case .a: fallthrough + 1️⃣case .a: fallthrough case .b: fallthrough @unknown default: print("got here") } @@ -342,8 +352,9 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { @unknown default: print("got here") } """, - checkForUnassertedDiagnostics: true) - - XCTAssertDiagnosed(.collapseCase, line: 2, column: 1) + findings: [ + FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift index 625e8a418..447fbafec 100644 --- a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift @@ -1,88 +1,87 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: Why not emit the finding at the very parentheses we want the user to remove? final class NoEmptyTrailingClosureParenthesesTests: LintOrFormatRuleTestCase { func testInvalidEmptyParenTrailingClosure() { - XCTAssertFormatting( + assertFormatting( NoEmptyTrailingClosureParentheses.self, input: """ - func greetEnthusiastically(_ nameProvider: () -> String) { - // ... - } - func greetApathetically(_ nameProvider: () -> String) { - // ... - } - greetEnthusiastically() { "John" } - greetApathetically { "not John" } - func myfunc(cls: MyClass) { - cls.myClosure { $0 } - } - func myfunc(cls: MyClass) { - cls.myBadClosure() { $0 } - } - DispatchQueue.main.async() { - greetEnthusiastically() { "John" } - DispatchQueue.main.async() { - greetEnthusiastically() { "Willis" } - } - } - DispatchQueue.global.async(inGroup: blah) { - DispatchQueue.main.async() { - greetEnthusiastically() { "Willis" } - } - DispatchQueue.main.async { - greetEnthusiastically() { "Willis" } - } - } - foo(bar() { baz })() { blah } - """, + func greetEnthusiastically(_ nameProvider: () -> String) { + // ... + } + func greetApathetically(_ nameProvider: () -> String) { + // ... + } + 0️⃣greetEnthusiastically() { "John" } + greetApathetically { "not John" } + func myfunc(cls: MyClass) { + cls.myClosure { $0 } + } + func myfunc(cls: MyClass) { + 1️⃣cls.myBadClosure() { $0 } + } + 2️⃣DispatchQueue.main.async() { + 3️⃣greetEnthusiastically() { "John" } + 4️⃣DispatchQueue.main.async() { + 5️⃣greetEnthusiastically() { "Willis" } + } + } + DispatchQueue.global.async(inGroup: blah) { + 6️⃣DispatchQueue.main.async() { + 7️⃣greetEnthusiastically() { "Willis" } + } + DispatchQueue.main.async { + 8️⃣greetEnthusiastically() { "Willis" } + } + } + 9️⃣foo(🔟bar() { baz })() { blah } + """, expected: """ - func greetEnthusiastically(_ nameProvider: () -> String) { - // ... - } - func greetApathetically(_ nameProvider: () -> String) { - // ... - } - greetEnthusiastically { "John" } - greetApathetically { "not John" } - func myfunc(cls: MyClass) { - cls.myClosure { $0 } - } - func myfunc(cls: MyClass) { - cls.myBadClosure { $0 } - } - DispatchQueue.main.async { - greetEnthusiastically { "John" } - DispatchQueue.main.async { - greetEnthusiastically { "Willis" } - } - } - DispatchQueue.global.async(inGroup: blah) { - DispatchQueue.main.async { - greetEnthusiastically { "Willis" } - } - DispatchQueue.main.async { - greetEnthusiastically { "Willis" } - } - } - foo(bar { baz }) { blah } - """, - checkForUnassertedDiagnostics: true) - XCTAssertDiagnosed( - .removeEmptyTrailingParentheses(name: "greetEnthusiastically"), line: 7, column: 1) - XCTAssertDiagnosed(.removeEmptyTrailingParentheses(name: "myBadClosure"), line: 13, column: 3) - XCTAssertNotDiagnosed(.removeEmptyTrailingParentheses(name: "myClosure")) - XCTAssertDiagnosed(.removeEmptyTrailingParentheses(name: "async"), line: 15, column: 1) - XCTAssertDiagnosed( - .removeEmptyTrailingParentheses(name: "greetEnthusiastically"), line: 16, column: 3) - XCTAssertDiagnosed(.removeEmptyTrailingParentheses(name: "async"), line: 17, column: 3) - XCTAssertDiagnosed( - .removeEmptyTrailingParentheses(name: "greetEnthusiastically"), line: 18, column: 5) - XCTAssertDiagnosed(.removeEmptyTrailingParentheses(name: "async"), line: 22, column: 3) - XCTAssertDiagnosed( - .removeEmptyTrailingParentheses(name: "greetEnthusiastically"), line: 23, column: 5) - XCTAssertDiagnosed( - .removeEmptyTrailingParentheses(name: "greetEnthusiastically"), line: 26, column: 5) - XCTAssertDiagnosed(.removeEmptyTrailingParentheses(name: ")"), line: 29, column: 1) - XCTAssertDiagnosed(.removeEmptyTrailingParentheses(name: "bar"), line: 29, column: 5) + func greetEnthusiastically(_ nameProvider: () -> String) { + // ... + } + func greetApathetically(_ nameProvider: () -> String) { + // ... + } + greetEnthusiastically { "John" } + greetApathetically { "not John" } + func myfunc(cls: MyClass) { + cls.myClosure { $0 } + } + func myfunc(cls: MyClass) { + cls.myBadClosure { $0 } + } + DispatchQueue.main.async { + greetEnthusiastically { "John" } + DispatchQueue.main.async { + greetEnthusiastically { "Willis" } + } + } + DispatchQueue.global.async(inGroup: blah) { + DispatchQueue.main.async { + greetEnthusiastically { "Willis" } + } + DispatchQueue.main.async { + greetEnthusiastically { "Willis" } + } + } + foo(bar { baz }) { blah } + """, + findings: [ + FindingSpec("0️⃣", message: "remove the empty parentheses following 'greetEnthusiastically'"), + FindingSpec("1️⃣", message: "remove the empty parentheses following 'myBadClosure'"), + FindingSpec("2️⃣", message: "remove the empty parentheses following 'async'"), + FindingSpec("3️⃣", message: "remove the empty parentheses following 'greetEnthusiastically'"), + FindingSpec("4️⃣", message: "remove the empty parentheses following 'async'"), + FindingSpec("5️⃣", message: "remove the empty parentheses following 'greetEnthusiastically'"), + FindingSpec("6️⃣", message: "remove the empty parentheses following 'async'"), + FindingSpec("7️⃣", message: "remove the empty parentheses following 'greetEnthusiastically'"), + FindingSpec("8️⃣", message: "remove the empty parentheses following 'greetEnthusiastically'"), + FindingSpec("9️⃣", message: "remove the empty parentheses following ')'"), + FindingSpec("🔟", message: "remove the empty parentheses following 'bar'"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift b/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift index f22682ce1..e4cbf1450 100644 --- a/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift @@ -1,32 +1,36 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class NoLabelsInCasePatternsTests: LintOrFormatRuleTestCase { func testRedundantCaseLabels() { - XCTAssertFormatting( + assertFormatting( NoLabelsInCasePatterns.self, input: """ - switch treeNode { - case .root(let data): - break - case .subtree(left: let /*hello*/left, right: let right): - break - case .leaf(element: let element): - break - } - """, + switch treeNode { + case .root(let data): + break + case .subtree(1️⃣left: let /*hello*/left, 2️⃣right: let right): + break + case .leaf(3️⃣element: let element): + break + } + """, expected: """ - switch treeNode { - case .root(let data): - break - case .subtree(let /*hello*/left, let right): - break - case .leaf(let element): - break - } - """) - XCTAssertNotDiagnosed(.removeRedundantLabel(name: "data")) - XCTAssertDiagnosed(.removeRedundantLabel(name: "left")) - XCTAssertDiagnosed(.removeRedundantLabel(name: "right")) - XCTAssertDiagnosed(.removeRedundantLabel(name: "element")) + switch treeNode { + case .root(let data): + break + case .subtree(let /*hello*/left, let right): + break + case .leaf(let element): + break + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the label 'left' from this 'case' pattern"), + FindingSpec("2️⃣", message: "remove the label 'right' from this 'case' pattern"), + FindingSpec("3️⃣", message: "remove the label 'element' from this 'case' pattern"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift b/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift index 286375600..704680f2f 100644 --- a/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift @@ -1,164 +1,166 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class NoLeadingUnderscoresTests: LintOrFormatRuleTestCase { func testVars() { - let input = """ - let _foo = foo - var good_name = 20 - var _badName, okayName, _wor_sEName = 20 + assertLint( + NoLeadingUnderscores.self, """ - performLint(NoLeadingUnderscores.self, input: input) - - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_foo")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "good_name")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_badName")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "okayName")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_wor_sEName")) + let 1️⃣_foo = foo + var good_name = 20 + var 2️⃣_badName, okayName, 3️⃣_wor_sEName = 20 + """, + findings: [ + FindingSpec("1️⃣", message: "remove the leading '_' from the name '_foo'"), + FindingSpec("2️⃣", message: "remove the leading '_' from the name '_badName'"), + FindingSpec("3️⃣", message: "remove the leading '_' from the name '_wor_sEName'"), + ] + ) } func testClasses() { - let input = """ - class Foo { let _foo = foo } - class _Bar {} + assertLint( + NoLeadingUnderscores.self, """ - performLint(NoLeadingUnderscores.self, input: input) - - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "Foo")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_foo")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_Bar")) + class Foo { let 1️⃣_foo = foo } + class 2️⃣_Bar {} + """, + findings: [ + FindingSpec("1️⃣", message: "remove the leading '_' from the name '_foo'"), + FindingSpec("2️⃣", message: "remove the leading '_' from the name '_Bar'"), + ] + ) } func testEnums() { - let input = """ + assertLint( + NoLeadingUnderscores.self, + """ enum Foo { - case _case1 - case case2, _case3 - case caseWithAssociatedValues(_value: Int, otherValue: String) - let _foo = foo + case 1️⃣_case1 + case case2, 2️⃣_case3 + case caseWithAssociatedValues(3️⃣_value: Int, otherValue: String) + let 4️⃣_foo = foo } - enum _Bar {} - """ - performLint(NoLeadingUnderscores.self, input: input) - - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "Foo")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_case1")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "case2")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_case3")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "caseWithAssociatedValues")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_value")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "otherValue")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_foo")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_Bar")) + enum 5️⃣_Bar {} + """, + findings: [ + FindingSpec("1️⃣", message: "remove the leading '_' from the name '_case1'"), + FindingSpec("2️⃣", message: "remove the leading '_' from the name '_case3'"), + FindingSpec("3️⃣", message: "remove the leading '_' from the name '_value'"), + FindingSpec("4️⃣", message: "remove the leading '_' from the name '_foo'"), + FindingSpec("5️⃣", message: "remove the leading '_' from the name '_Bar'"), + ] + ) } func testProtocols() { - let input = """ + assertLint( + NoLeadingUnderscores.self, + """ protocol Foo { - associatedtype _Quux + associatedtype 1️⃣_Quux associatedtype Florb - var _foo: Int { get set } + var 2️⃣_foo: Int { get set } } - protocol _Bar {} - """ - performLint(NoLeadingUnderscores.self, input: input) - - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "Foo")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_foo")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_Bar")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_Quux")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "Florb")) + protocol 3️⃣_Bar {} + """, + findings: [ + FindingSpec("1️⃣", message: "remove the leading '_' from the name '_Quux'"), + FindingSpec("2️⃣", message: "remove the leading '_' from the name '_foo'"), + FindingSpec("3️⃣", message: "remove the leading '_' from the name '_Bar'"), + ] + ) } func testStructs() { - let input = """ - struct Foo { let _foo = foo } - struct _Bar {} + assertLint( + NoLeadingUnderscores.self, """ - performLint(NoLeadingUnderscores.self, input: input) - - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "Foo")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_foo")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_Bar")) + struct Foo { let 1️⃣_foo = foo } + struct 2️⃣_Bar {} + """, + findings: [ + FindingSpec("1️⃣", message: "remove the leading '_' from the name '_foo'"), + FindingSpec("2️⃣", message: "remove the leading '_' from the name '_Bar'"), + ] + ) } func testFunctions() { - let input = """ - func _foo(_ ok: Int, _notOK: Int, _ok _butNotThisOne: Int) {} - func bar() {} + assertLint( + NoLeadingUnderscores.self, """ - performLint(NoLeadingUnderscores.self, input: input) - - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_foo")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "T1")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_T2")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "_")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "ok")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_notOK")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "_ok")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_butNotThisOne")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "bar")) + func 1️⃣_foo(_ ok: Int, 3️⃣_notOK: Int, _ok 4️⃣_butNotThisOne: Int) {} + func bar() {} + """, + findings: [ + FindingSpec("1️⃣", message: "remove the leading '_' from the name '_foo'"), + FindingSpec("2️⃣", message: "remove the leading '_' from the name '_T2'"), + FindingSpec("3️⃣", message: "remove the leading '_' from the name '_notOK'"), + FindingSpec("4️⃣", message: "remove the leading '_' from the name '_butNotThisOne'"), + ] + ) } func testInitializerArguments() { - let input = """ + assertLint( + NoLeadingUnderscores.self, + """ struct X { - init(_ ok: Int, _notOK: Int, _ok _butNotThisOne: Int) {} + init(_ ok: Int, 2️⃣_notOK: Int, _ok 3️⃣_butNotThisOne: Int) {} } - """ - performLint(NoLeadingUnderscores.self, input: input) - - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "T1")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_T2")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "_")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "ok")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_notOK")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "_ok")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_butNotThisOne")) + """, + findings: [ + FindingSpec("1️⃣", message: "remove the leading '_' from the name '_T2'"), + FindingSpec("2️⃣", message: "remove the leading '_' from the name '_notOK'"), + FindingSpec("3️⃣", message: "remove the leading '_' from the name '_butNotThisOne'"), + ] + ) } func testPrecedenceGroups() { - let input = """ + assertLint( + NoLeadingUnderscores.self, + """ precedencegroup FooPrecedence { associativity: left higherThan: BarPrecedence } - precedencegroup _FooPrecedence { + precedencegroup 1️⃣_FooPrecedence { associativity: left higherThan: BarPrecedence } infix operator <> : _BazPrecedence - """ - performLint(NoLeadingUnderscores.self, input: input) - - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "FooPrecedence")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "BarPrecedence")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_FooPrecedence")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "_BazPrecedence")) + """, + findings: [ + FindingSpec("1️⃣", message: "remove the leading '_' from the name '_FooPrecedence'"), + ] + ) } func testTypealiases() { - let input = """ - typealias Foo = _Foo - typealias _Bar = Bar + assertLint( + NoLeadingUnderscores.self, """ - performLint(NoLeadingUnderscores.self, input: input) - - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "Foo")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "_Foo")) - XCTAssertDiagnosed(.doNotStartWithUnderscore(identifier: "_Bar")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "Bar")) + typealias Foo = _Foo + typealias 1️⃣_Bar = Bar + """, + findings: [ + FindingSpec("1️⃣", message: "remove the leading '_' from the name '_Bar'"), + ] + ) } func testIdentifiersAreIgnoredAtUsage() { - let input = """ + assertLint( + NoLeadingUnderscores.self, + """ let x = _y + _z _foo(_bar) - """ - performLint(NoLeadingUnderscores.self, input: input) - - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "_y")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "_z")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "_foo")) - XCTAssertNotDiagnosed(.doNotStartWithUnderscore(identifier: "_bar")) + """, + findings: [] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift index 62fec7da1..859844265 100644 --- a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift @@ -1,176 +1,239 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: Emit final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { func testParensAroundConditions() { - XCTAssertFormatting( + assertFormatting( NoParensAroundConditions.self, input: """ - if (x) {} - while (x) {} - guard (x), (y), (x == 3) else {} - if (foo { x }) {} - repeat {} while(x) - switch (4) { default: break } - """, + if (1️⃣x) {} + while (2️⃣x) {} + guard (3️⃣x), (4️⃣y), (5️⃣x == 3) else {} + if (foo { x }) {} + repeat {} while(6️⃣x) + switch (7️⃣4) { default: break } + """, expected: """ - if x {} - while x {} - guard x, y, x == 3 else {} - if (foo { x }) {} - repeat {} while x - switch 4 { default: break } - """) + if x {} + while x {} + guard x, y, x == 3 else {} + if (foo { x }) {} + repeat {} while x + switch 4 { default: break } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the parentheses around this expression"), + FindingSpec("2️⃣", message: "remove the parentheses around this expression"), + FindingSpec("3️⃣", message: "remove the parentheses around this expression"), + FindingSpec("4️⃣", message: "remove the parentheses around this expression"), + FindingSpec("5️⃣", message: "remove the parentheses around this expression"), + FindingSpec("6️⃣", message: "remove the parentheses around this expression"), + FindingSpec("7️⃣", message: "remove the parentheses around this expression"), + ] + ) } func testParensAroundNestedParenthesizedStatements() { - XCTAssertFormatting( + assertFormatting( + NoParensAroundConditions.self, + input: """ + switch (1️⃣a) { + case 1: + switch (2️⃣b) { + default: break + } + } + if (3️⃣x) { + if (4️⃣y) { + } else if (5️⃣z) { + } else { + } + } else if (6️⃣w) { + } + """, + expected: """ + switch a { + case 1: + switch b { + default: break + } + } + if x { + if y { + } else if z { + } else { + } + } else if w { + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the parentheses around this expression"), + FindingSpec("2️⃣", message: "remove the parentheses around this expression"), + FindingSpec("3️⃣", message: "remove the parentheses around this expression"), + FindingSpec("4️⃣", message: "remove the parentheses around this expression"), + FindingSpec("5️⃣", message: "remove the parentheses around this expression"), + FindingSpec("6️⃣", message: "remove the parentheses around this expression"), + ] + ) + + assertFormatting( NoParensAroundConditions.self, input: """ - switch (a) { - case 1: - switch (b) { - default: break - } - } - if (x) { - if (y) { - } else if (z) { - } else { - } - } else if (w) { - } - while (x) { - while (y) {} - } - guard (x), (y), (x == 3) else { - guard (a), (b), (c == x) else { - return - } - return - } - repeat { - repeat { - } while (y) - } while(x) - if (foo.someCall({ if (x) {} })) {} - """, + while (1️⃣x) { + while (2️⃣y) {} + } + guard (3️⃣x), (4️⃣y), (5️⃣x == 3) else { + guard (6️⃣a), (7️⃣b), (8️⃣c == x) else { + return + } + return + } + repeat { + repeat { + } while (9️⃣y) + } while(🔟x) + if (0️⃣foo.someCall({ if (ℹ️x) {} })) {} + """, expected: """ - switch a { - case 1: - switch b { - default: break - } - } - if x { - if y { - } else if z { - } else { - } - } else if w { - } - while x { - while y {} - } - guard x, y, x == 3 else { - guard a, b, c == x else { - return - } - return - } - repeat { - repeat { - } while y - } while x - if foo.someCall({ if x {} }) {} - """) + while x { + while y {} + } + guard x, y, x == 3 else { + guard a, b, c == x else { + return + } + return + } + repeat { + repeat { + } while y + } while x + if foo.someCall({ if x {} }) {} + """, + findings: [ + FindingSpec("1️⃣", message: "remove the parentheses around this expression"), + FindingSpec("2️⃣", message: "remove the parentheses around this expression"), + FindingSpec("3️⃣", message: "remove the parentheses around this expression"), + FindingSpec("4️⃣", message: "remove the parentheses around this expression"), + FindingSpec("5️⃣", message: "remove the parentheses around this expression"), + FindingSpec("6️⃣", message: "remove the parentheses around this expression"), + FindingSpec("7️⃣", message: "remove the parentheses around this expression"), + FindingSpec("8️⃣", message: "remove the parentheses around this expression"), + FindingSpec("9️⃣", message: "remove the parentheses around this expression"), + FindingSpec("🔟", message: "remove the parentheses around this expression"), + FindingSpec("0️⃣", message: "remove the parentheses around this expression"), + FindingSpec("ℹ️", message: "remove the parentheses around this expression"), + ] + ) } func testParensAroundNestedUnparenthesizedStatements() { - XCTAssertFormatting( + assertFormatting( NoParensAroundConditions.self, input: """ - switch b { - case 2: - switch (d) { - default: break - } - } - if x { - if (y) { - } else if (z) { - } else { - } - } else if (w) { - } - while x { - while (y) {} - } - repeat { - repeat { - } while (y) - } while x - if foo.someCall({ if (x) {} }) {} - """, + switch b { + case 2: + switch (1️⃣d) { + default: break + } + } + if x { + if (2️⃣y) { + } else if (3️⃣z) { + } else { + } + } else if (4️⃣w) { + } + while x { + while (5️⃣y) {} + } + repeat { + repeat { + } while (6️⃣y) + } while x + if foo.someCall({ if (7️⃣x) {} }) {} + """, expected: """ - switch b { - case 2: - switch d { - default: break - } - } - if x { - if y { - } else if z { - } else { - } - } else if w { - } - while x { - while y {} - } - repeat { - repeat { - } while y - } while x - if foo.someCall({ if x {} }) {} - """) + switch b { + case 2: + switch d { + default: break + } + } + if x { + if y { + } else if z { + } else { + } + } else if w { + } + while x { + while y {} + } + repeat { + repeat { + } while y + } while x + if foo.someCall({ if x {} }) {} + """, + findings: [ + FindingSpec("1️⃣", message: "remove the parentheses around this expression"), + FindingSpec("2️⃣", message: "remove the parentheses around this expression"), + FindingSpec("3️⃣", message: "remove the parentheses around this expression"), + FindingSpec("4️⃣", message: "remove the parentheses around this expression"), + FindingSpec("5️⃣", message: "remove the parentheses around this expression"), + FindingSpec("6️⃣", message: "remove the parentheses around this expression"), + FindingSpec("7️⃣", message: "remove the parentheses around this expression"), + ] + ) } func testParensAroundIfAndSwitchExprs() { - XCTAssertFormatting( + assertFormatting( NoParensAroundConditions.self, input: """ - let x = if (x) {} - let y = switch (4) { default: break } - func foo() { - return if (x) {} - } - func bar() { - return switch (4) { default: break } - } - """, + let x = if (1️⃣x) {} + let y = switch (2️⃣4) { default: break } + func foo() { + return if (3️⃣x) {} + } + func bar() { + return switch (4️⃣4) { default: break } + } + """, expected: """ - let x = if x {} - let y = switch 4 { default: break } - func foo() { - return if x {} - } - func bar() { - return switch 4 { default: break } - } - """) + let x = if x {} + let y = switch 4 { default: break } + func foo() { + return if x {} + } + func bar() { + return switch 4 { default: break } + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the parentheses around this expression"), + FindingSpec("2️⃣", message: "remove the parentheses around this expression"), + FindingSpec("3️⃣", message: "remove the parentheses around this expression"), + FindingSpec("4️⃣", message: "remove the parentheses around this expression"), + ] + ) } func testParensAroundAmbiguousConditions() { - XCTAssertFormatting( + assertFormatting( NoParensAroundConditions.self, input: """ - if ({ true }()) {} - if (functionWithTrailingClosure { 5 }) {} - """, + if ({ true }()) {} + if (functionWithTrailingClosure { 5 }) {} + """, expected: """ - if ({ true }()) {} - if (functionWithTrailingClosure { 5 }) {} - """) + if ({ true }()) {} + if (functionWithTrailingClosure { 5 }) {} + """, + findings: [] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift b/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift index daff3b2dd..7c0c5a88d 100644 --- a/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift @@ -1,34 +1,41 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class NoVoidReturnOnFunctionSignatureTests: LintOrFormatRuleTestCase { func testVoidReturns() { - XCTAssertFormatting( + assertFormatting( NoVoidReturnOnFunctionSignature.self, input: """ - func foo() -> () { - } + func foo() -> 1️⃣() { + } - func test() -> Void{ - } + func test() -> 2️⃣Void{ + } - func x() -> Int { return 2 } + func x() -> Int { return 2 } - let x = { () -> Void in - print("Hello, world!") - } - """, + let x = { () -> Void in + print("Hello, world!") + } + """, expected: """ - func foo() { - } + func foo() { + } - func test() { - } + func test() { + } - func x() -> Int { return 2 } + func x() -> Int { return 2 } - let x = { () -> Void in - print("Hello, world!") - } - """) + let x = { () -> Void in + print("Hello, world!") + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the explicit return type '()' from this function"), + FindingSpec("2️⃣", message: "remove the explicit return type 'Void' from this function"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift b/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift index 93397bf53..623418884 100644 --- a/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift +++ b/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift @@ -1,3 +1,5 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class OneCasePerLineTests: LintOrFormatRuleTestCase { @@ -7,22 +9,20 @@ final class OneCasePerLineTests: LintOrFormatRuleTestCase { // running the full formatter. func testInvalidCasesOnLine() { - XCTAssertFormatting( + assertFormatting( OneCasePerLine.self, - input: - """ + input: """ public enum Token { case arrow - case comma, identifier(String), semicolon, stringSegment(String) + case comma, 1️⃣identifier(String), semicolon, 2️⃣stringSegment(String) case period - case ifKeyword(String), forKeyword(String) - indirect case guardKeyword, elseKeyword, contextualKeyword(String) + case 3️⃣ifKeyword(String), 4️⃣forKeyword(String) + indirect case guardKeyword, elseKeyword, 5️⃣contextualKeyword(String) var x: Bool - case leftParen, rightParen = ")", leftBrace, rightBrace = "}" + case leftParen, 6️⃣rightParen = ")", leftBrace, 7️⃣rightBrace = "}" } """, - expected: - """ + expected: """ public enum Token { case arrow case comma @@ -40,41 +40,51 @@ final class OneCasePerLineTests: LintOrFormatRuleTestCase { case leftBrace case rightBrace = "}" } - """) + """, + findings: [ + FindingSpec("1️⃣", message: "move 'identifier' to its own 'case' declaration"), + FindingSpec("2️⃣", message: "move 'stringSegment' to its own 'case' declaration"), + FindingSpec("3️⃣", message: "move 'ifKeyword' to its own 'case' declaration"), + FindingSpec("4️⃣", message: "move 'forKeyword' to its own 'case' declaration"), + FindingSpec("5️⃣", message: "move 'contextualKeyword' to its own 'case' declaration"), + FindingSpec("6️⃣", message: "move 'rightParen' to its own 'case' declaration"), + FindingSpec("7️⃣", message: "move 'rightBrace' to its own 'case' declaration"), + ] + ) } func testElementOrderIsPreserved() { - XCTAssertFormatting( + assertFormatting( OneCasePerLine.self, - input: - """ + input: """ enum Foo: Int { - case a = 0, b, c, d + case 1️⃣a = 0, b, c, d } """, - expected: - """ + expected: """ enum Foo: Int { case a = 0 case b, c, d } - """) + """, + findings: [ + FindingSpec("1️⃣", message: "move 'a' to its own 'case' declaration"), + ] + ) } func testCommentsAreNotRepeated() { - XCTAssertFormatting( + assertFormatting( OneCasePerLine.self, - input: - """ + input: """ enum Foo: Int { /// This should only be above `a`. - case a = 0, b, c, d + case 1️⃣a = 0, b, c, d // This should only be above `e`. - case e, f = 100 + case e, 2️⃣f = 100 } """, - expected: - """ + expected: """ enum Foo: Int { /// This should only be above `a`. case a = 0 @@ -83,22 +93,25 @@ final class OneCasePerLineTests: LintOrFormatRuleTestCase { case e case f = 100 } - """) + """, + findings: [ + FindingSpec("1️⃣", message: "move 'a' to its own 'case' declaration"), + FindingSpec("2️⃣", message: "move 'f' to its own 'case' declaration"), + ] + ) } func testAttributesArePropagated() { - XCTAssertFormatting( + assertFormatting( OneCasePerLine.self, - input: - """ + input: """ enum Foo { - @someAttr case a(String), b, c, d - case e, f(Int) - @anotherAttr case g, h(Float) + @someAttr case 1️⃣a(String), b, c, d + case e, 2️⃣f(Int) + @anotherAttr case g, 3️⃣h(Float) } """, - expected: - """ + expected: """ enum Foo { @someAttr case a(String) @someAttr case b, c, d @@ -107,6 +120,12 @@ final class OneCasePerLineTests: LintOrFormatRuleTestCase { @anotherAttr case g @anotherAttr case h(Float) } - """) + """, + findings: [ + FindingSpec("1️⃣", message: "move 'a' to its own 'case' declaration"), + FindingSpec("2️⃣", message: "move 'f' to its own 'case' declaration"), + FindingSpec("3️⃣", message: "move 'h' to its own 'case' declaration"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift b/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift index d9086df45..cb951bbd5 100644 --- a/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift +++ b/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift @@ -1,19 +1,19 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { func testMultipleVariableBindings() { - XCTAssertFormatting( + assertFormatting( OneVariableDeclarationPerLine.self, - input: - """ - var a = 0, b = 2, (c, d) = (0, "h") - let e = 0, f = 2, (g, h) = (0, "h") + input: """ + 1️⃣var a = 0, b = 2, (c, d) = (0, "h") + 2️⃣let e = 0, f = 2, (g, h) = (0, "h") var x: Int { return 3 } - let a, b, c: Int - var j: Int, k: String, l: Float + 3️⃣let a, b, c: Int + 4️⃣var j: Int, k: String, l: Float """, - expected: - """ + expected: """ var a = 0 var b = 2 var (c, d) = (0, "h") @@ -28,51 +28,50 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { var k: String var l: Float """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'var'"), + FindingSpec("2️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("3️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("4️⃣", message: "split this variable declaration to introduce only one variable per 'var'"), + ] ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "var"), line: 1, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 2, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 4, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "var"), line: 5, column: 1) } func testNestedVariableBindings() { - XCTAssertFormatting( + assertFormatting( OneVariableDeclarationPerLine.self, - input: - """ + input: """ var x: Int = { - let y = 5, z = 10 + 1️⃣let y = 5, z = 10 return z }() func foo() { - let x = 4, y = 10 + 2️⃣let x = 4, y = 10 } var x: Int { - let y = 5, z = 10 + 3️⃣let y = 5, z = 10 return z } var a: String = "foo" { didSet { - let b, c: Bool + 4️⃣let b, c: Bool } } - let + 5️⃣let a: Int = { - let p = 10, q = 20 + 6️⃣let p = 10, q = 20 return p * q }(), b: Int = { - var s: Int, t: Double + 7️⃣var s: Int, t: Double return 20 }() """, - expected: - """ + expected: """ var x: Int = { let y = 5 let z = 10 @@ -110,27 +109,26 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { return 20 }() """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("2️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("3️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("4️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("5️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("6️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("7️⃣", message: "split this variable declaration to introduce only one variable per 'var'"), + ] ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 2, column: 3) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 7, column: 3) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 11, column: 3) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 17, column: 5) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 21, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 23, column: 5) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "var"), line: 27, column: 5) } func testMixedInitializedAndTypedBindings() { - XCTAssertFormatting( + assertFormatting( OneVariableDeclarationPerLine.self, - input: - """ - var a = 5, b: String - let c: Int, d = "d", e = "e", f: Double + input: """ + 1️⃣var a = 5, b: String + 2️⃣let c: Int, d = "d", e = "e", f: Double """, - expected: - """ + expected: """ var a = 5 var b: String let c: Int @@ -138,62 +136,59 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { let e = "e" let f: Double """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'var'"), + FindingSpec("2️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + ] ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "var"), line: 1, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 2, column: 1) } func testCommentPrecedingDeclIsNotRepeated() { - XCTAssertFormatting( + assertFormatting( OneVariableDeclarationPerLine.self, - input: - """ + input: """ // Comment - let a, b, c: Int + 1️⃣let a, b, c: Int """, - expected: - """ + expected: """ // Comment let a: Int let b: Int let c: Int """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + ] ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 2, column: 1) } func testCommentsPrecedingBindingsAreKept() { - XCTAssertFormatting( + assertFormatting( OneVariableDeclarationPerLine.self, - input: - """ - let /* a */ a, /* b */ b, /* c */ c: Int + input: """ + 1️⃣let /* a */ a, /* b */ b, /* c */ c: Int """, - expected: - """ + expected: """ let /* a */ a: Int let /* b */ b: Int let /* c */ c: Int """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + ] ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 1, column: 1) } func testInvalidBindingsAreNotDestroyed() { - XCTAssertFormatting( + assertFormatting( OneVariableDeclarationPerLine.self, - input: - """ - let a, b, c = 5 - let d, e - let f, g, h: Int = 5 - let a: Int, b, c = 5, d, e: Int + input: """ + 1️⃣let a, b, c = 5 + 2️⃣let d, e + 3️⃣let f, g, h: Int = 5 + 4️⃣let a: Int, b, c = 5, d, e: Int """, - expected: - """ + expected: """ let a, b, c = 5 let d, e let f, g, h: Int = 5 @@ -202,31 +197,31 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { let d: Int let e: Int """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("2️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("3️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("4️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + ] ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 1, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 2, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 3, column: 1) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "let"), line: 4, column: 1) } func testMultipleBindingsWithAccessorsAreCorrected() { // Swift parses multiple bindings with accessors but forbids them at a later // stage. That means that if the individual bindings would be correct in // isolation then we can correct them, which is kind of nice. - XCTAssertFormatting( + assertFormatting( OneVariableDeclarationPerLine.self, - input: - """ - var x: Int { return 10 }, y = "foo" { didSet { print("changed") } } + input: """ + 1️⃣var x: Int { return 10 }, y = "foo" { didSet { print("changed") } } """, - expected: - """ + expected: """ var x: Int { return 10 } var y = "foo" { didSet { print("changed") } } """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'var'"), + ] ) - XCTAssertDiagnosed(.onlyOneVariableDeclaration(specifier: "var"), line: 1, column: 1) } } diff --git a/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift b/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift index 976687220..b36a01afb 100644 --- a/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift +++ b/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift @@ -1,20 +1,23 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class OnlyOneTrailingClosureArgumentTests: LintOrFormatRuleTestCase { func testInvalidTrailingClosureCall() { - let input = + assertLint( + OnlyOneTrailingClosureArgument.self, """ - callWithBoth(someClosure: {}) { + 1️⃣callWithBoth(someClosure: {}) { // ... } callWithClosure(someClosure: {}) callWithTrailingClosure { // ... } - """ - performLint(OnlyOneTrailingClosureArgument.self, input: input) - XCTAssertDiagnosed(.removeTrailingClosure) - XCTAssertNotDiagnosed(.removeTrailingClosure) - XCTAssertNotDiagnosed(.removeTrailingClosure) + """, + findings: [ + FindingSpec("1️⃣", message: "revise this function call to avoid using both closure arguments and a trailing closure"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift index cc0af6a80..bae1b3e77 100644 --- a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift @@ -1,223 +1,193 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class OrderedImportsTests: LintOrFormatRuleTestCase { func testInvalidImportsOrder() { - let input = - """ - import Foundation - // Starts Imports - import Core - - - // Comment with new lines - import UIKit - - @testable import SwiftFormat - import enum Darwin.D.isatty - // Starts Test - @testable import MyModuleUnderTest - // Starts Ind - import func Darwin.C.isatty - - let a = 3 - import SwiftSyntax - """ - - let expected = - """ - // Starts Imports - import Core - import Foundation - import SwiftSyntax - // Comment with new lines - import UIKit - - // Starts Ind - import func Darwin.C.isatty - import enum Darwin.D.isatty - - // Starts Test - @testable import MyModuleUnderTest - @testable import SwiftFormat - - let a = 3 - """ - - XCTAssertFormatting( - OrderedImports.self, input: input, expected: expected, checkForUnassertedDiagnostics: true + assertFormatting( + OrderedImports.self, + input: """ + import Foundation + // Starts Imports + 1️⃣import Core + + + // Comment with new lines + import UIKit + + @testable import SwiftFormat + 8️⃣import enum Darwin.D.isatty + // Starts Test + 3️⃣@testable import MyModuleUnderTest + // Starts Ind + 2️⃣7️⃣import func Darwin.C.isatty + + let a = 3 + 4️⃣5️⃣6️⃣import SwiftSyntax + """, + expected: """ + // Starts Imports + import Core + import Foundation + import SwiftSyntax + // Comment with new lines + import UIKit + + // Starts Ind + import func Darwin.C.isatty + import enum Darwin.D.isatty + + // Starts Test + @testable import MyModuleUnderTest + @testable import SwiftFormat + + let a = 3 + """, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("2️⃣", message: "place declaration imports before testable imports"), + FindingSpec("3️⃣", message: "sort import statements lexicographically"), + FindingSpec("4️⃣", message: "place imports at the top of the file"), + FindingSpec("5️⃣", message: "place regular imports before testable imports"), + FindingSpec("6️⃣", message: "place regular imports before declaration imports"), + FindingSpec("7️⃣", message: "sort import statements lexicographically"), + FindingSpec("8️⃣", message: "place declaration imports before testable imports"), + ] ) - - XCTAssertDiagnosed(.sortImports) // import Core - XCTAssertDiagnosed(.sortImports) // import func Darwin.C.isatty - XCTAssertDiagnosed(.sortImports) // @testable import MyModuleUnderTest - - // import SwiftSyntax - XCTAssertDiagnosed(.placeAtTopOfFile) - XCTAssertDiagnosed(.groupImports(before: .regularImport, after: .declImport)) - XCTAssertDiagnosed(.groupImports(before: .regularImport, after: .testableImport)) - - // import func Darwin.C.isatty - XCTAssertDiagnosed(.groupImports(before: .declImport, after: .testableImport)) - - // import enum Darwin.D.isatty - XCTAssertDiagnosed(.groupImports(before: .declImport, after: .testableImport)) } func testImportsOrderWithoutModuleType() { - let input = - """ - @testable import SwiftFormat - import func Darwin.D.isatty - @testable import MyModuleUnderTest - import func Darwin.C.isatty - - let a = 3 - """ - - let expected = - """ - import func Darwin.C.isatty - import func Darwin.D.isatty - - @testable import MyModuleUnderTest - @testable import SwiftFormat - - let a = 3 - """ - - XCTAssertFormatting( - OrderedImports.self, input: input, expected: expected, checkForUnassertedDiagnostics: true + assertFormatting( + OrderedImports.self, + input: """ + @testable import SwiftFormat + 1️⃣import func Darwin.D.isatty + 4️⃣@testable import MyModuleUnderTest + 2️⃣3️⃣import func Darwin.C.isatty + + let a = 3 + """, + expected: """ + import func Darwin.C.isatty + import func Darwin.D.isatty + + @testable import MyModuleUnderTest + @testable import SwiftFormat + + let a = 3 + """, + findings: [ + FindingSpec("1️⃣", message: "place declaration imports before testable imports"), + FindingSpec("2️⃣", message: "place declaration imports before testable imports"), + FindingSpec("3️⃣", message: "sort import statements lexicographically"), + FindingSpec("4️⃣", message: "sort import statements lexicographically"), + ] ) - - // import func Darwin.D.isatty - XCTAssertDiagnosed(.groupImports(before: .declImport, after: .testableImport)) - - // import func Darwin.C.isatty - XCTAssertDiagnosed(.groupImports(before: .declImport, after: .testableImport)) - XCTAssertDiagnosed(.sortImports) - - // @testable import MyModuleUnderTest - XCTAssertDiagnosed(.sortImports) } func testImportsOrderWithDocComment() { - let input = - """ - /// Test imports with comments. - /// - /// Comments at the top of the file - /// should be preserved. - - // Line comment for import - // Foundation. - import Foundation - // Line comment for Core - import Core - import UIKit - - let a = 3 - """ - - let expected = - """ - /// Test imports with comments. - /// - /// Comments at the top of the file - /// should be preserved. - - // Line comment for Core - import Core - // Line comment for import - // Foundation. - import Foundation - import UIKit - - let a = 3 - """ - - XCTAssertFormatting( - OrderedImports.self, input: input, expected: expected, checkForUnassertedDiagnostics: true + assertFormatting( + OrderedImports.self, + input: """ + /// Test imports with comments. + /// + /// Comments at the top of the file + /// should be preserved. + + // Line comment for import + // Foundation. + import Foundation + // Line comment for Core + 1️⃣import Core + import UIKit + + let a = 3 + """, + expected: """ + /// Test imports with comments. + /// + /// Comments at the top of the file + /// should be preserved. + + // Line comment for Core + import Core + // Line comment for import + // Foundation. + import Foundation + import UIKit + + let a = 3 + """, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + ] ) - - // import Core - XCTAssertDiagnosed(.sortImports) } func testValidOrderedImport() { - let input = - """ - import CoreLocation - import MyThirdPartyModule - import SpriteKit - import UIKit - - import func Darwin.C.isatty - - @testable import MyModuleUnderTest - """ - - let expected = - """ - import CoreLocation - import MyThirdPartyModule - import SpriteKit - import UIKit - - import func Darwin.C.isatty - - @testable import MyModuleUnderTest - """ - - XCTAssertFormatting( - OrderedImports.self, input: input, expected: expected, checkForUnassertedDiagnostics: true + assertFormatting( + OrderedImports.self, + input: """ + import CoreLocation + import MyThirdPartyModule + import SpriteKit + import UIKit + + import func Darwin.C.isatty + + @testable import MyModuleUnderTest + """, + expected: """ + import CoreLocation + import MyThirdPartyModule + import SpriteKit + import UIKit + + import func Darwin.C.isatty + + @testable import MyModuleUnderTest + """, + findings: [] ) - - // Should not raise any linter errors. } func testSeparatedFileHeader() { - let input = - """ - // This is part of the file header. - - // So is this. - - // Top comment - import Bimport - import Aimport - - struct MyStruct { - // do stuff - } - - import HoistMe - """ - - let expected = - """ - // This is part of the file header. - - // So is this. - - import Aimport - // Top comment - import Bimport - import HoistMe - - struct MyStruct { - // do stuff - } - """ - - XCTAssertFormatting( - OrderedImports.self, input: input, expected: expected, checkForUnassertedDiagnostics: true + assertFormatting( + OrderedImports.self, + input: """ + // This is part of the file header. + + // So is this. + + // Top comment + import Bimport + 1️⃣import Aimport + + struct MyStruct { + // do stuff + } + + 2️⃣import HoistMe + """, + expected: """ + // This is part of the file header. + + // So is this. + + import Aimport + // Top comment + import Bimport + import HoistMe + + struct MyStruct { + // do stuff + } + """, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("2️⃣", message: "place imports at the top of the file"), + ] ) - - // import Aimport - XCTAssertDiagnosed(.sortImports) - - // import HoistMe - XCTAssertDiagnosed(.placeAtTopOfFile) } func testNonHeaderComment() { @@ -225,7 +195,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { """ // Top comment import Bimport - import Aimport + 1️⃣import Aimport let A = 123 """ @@ -239,406 +209,413 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { let A = 123 """ - XCTAssertFormatting( - OrderedImports.self, input: input, expected: expected, checkForUnassertedDiagnostics: true + assertFormatting( + OrderedImports.self, + input: input, + expected: expected, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + ] ) - - // import Aimport - XCTAssertDiagnosed(.sortImports) } func testMultipleCodeBlocksPerLine() { - let input = - """ - import A;import Z;import D;import C; - foo();bar();baz();quxxe(); - """ - - let expected = - """ - import A; - import C; - import D; - import Z; - - foo();bar();baz();quxxe(); - """ - - XCTAssertFormatting(OrderedImports.self, input: input, expected: expected) + assertFormatting( + OrderedImports.self, + input: """ + import A;import Z;1️⃣import D;import C; + foo();bar();baz();quxxe(); + """, + expected: """ + import A; + import C; + import D; + import Z; + + foo();bar();baz();quxxe(); + """, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + ] + ) } func testMultipleCodeBlocksWithImportsPerLine() { - let input = - """ - import A;import Z;import D;import C;foo();bar();baz();quxxe(); - """ - - let expected = - """ - import A; - import C; - import D; - import Z; - - foo();bar();baz();quxxe(); - """ - - XCTAssertFormatting(OrderedImports.self, input: input, expected: expected) + assertFormatting( + OrderedImports.self, + input: """ + import A;import Z;1️⃣import D;import C;foo();bar();baz();quxxe(); + """, + expected: """ + import A; + import C; + import D; + import Z; + + foo();bar();baz();quxxe(); + """, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + ] + ) } func testDisableOrderedImports() { - let input = - """ - import C - import B - // swift-format-ignore: OrderedImports - import A - let a = 123 - import func Darwin.C.isatty - - // swift-format-ignore - import a - """ - - let expected = - """ - import B - import C - - // swift-format-ignore: OrderedImports - import A - - import func Darwin.C.isatty - - let a = 123 - - // swift-format-ignore - import a - """ - - XCTAssertFormatting( - OrderedImports.self, input: input, expected: expected, checkForUnassertedDiagnostics: true + assertFormatting( + OrderedImports.self, + input: """ + import C + 1️⃣import B + // swift-format-ignore: OrderedImports + import A + let a = 123 + 2️⃣import func Darwin.C.isatty + + // swift-format-ignore + import a + """, + expected: """ + import B + import C + + // swift-format-ignore: OrderedImports + import A + + import func Darwin.C.isatty + + let a = 123 + + // swift-format-ignore + import a + """, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("2️⃣", message: "place imports at the top of the file"), + ] ) - - XCTAssertDiagnosed(.sortImports, line: 2, column: 1) - XCTAssertDiagnosed(.placeAtTopOfFile, line: 6, column: 1) } func testDisableOrderedImportsMovingComments() { - let input = - """ - import C // Trailing comment about C - import B - // Comment about ignored A - // swift-format-ignore: OrderedImports - import A // trailing comment about ignored A - // Comment about Z - import Z - import D - // swift-format-ignore - // Comment about testable testA - @testable import testA - @testable import testZ // trailing comment about testZ - @testable import testC - // swift-format-ignore - @testable import testB - // Comment about Bar - import enum Bar - - let a = 2 - """ - - let expected = - """ - import B - import C // Trailing comment about C - - // Comment about ignored A - // swift-format-ignore: OrderedImports - import A // trailing comment about ignored A - - import D - // Comment about Z - import Z - - // swift-format-ignore - // Comment about testable testA - @testable import testA - - @testable import testC - @testable import testZ // trailing comment about testZ - - // swift-format-ignore - @testable import testB - - // Comment about Bar - import enum Bar - - let a = 2 - """ - - XCTAssertFormatting( - OrderedImports.self, input: input, expected: expected, checkForUnassertedDiagnostics: true + assertFormatting( + OrderedImports.self, + input: """ + import C // Trailing comment about C + 1️⃣import B + // Comment about ignored A + // swift-format-ignore: OrderedImports + import A // trailing comment about ignored A + // Comment about Z + import Z + 2️⃣import D + // swift-format-ignore + // Comment about testable testA + @testable import testA + @testable import testZ // trailing comment about testZ + 3️⃣@testable import testC + // swift-format-ignore + @testable import testB + // Comment about Bar + import enum Bar + + let a = 2 + """, + expected: """ + import B + import C // Trailing comment about C + + // Comment about ignored A + // swift-format-ignore: OrderedImports + import A // trailing comment about ignored A + + import D + // Comment about Z + import Z + + // swift-format-ignore + // Comment about testable testA + @testable import testA + + @testable import testC + @testable import testZ // trailing comment about testZ + + // swift-format-ignore + @testable import testB + + // Comment about Bar + import enum Bar + + let a = 2 + """, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("2️⃣", message: "sort import statements lexicographically"), + FindingSpec("3️⃣", message: "sort import statements lexicographically"), + ] ) - - XCTAssertDiagnosed(.sortImports, line: 2, column: 1) - XCTAssertDiagnosed(.sortImports, line: 8, column: 1) - XCTAssertDiagnosed(.sortImports, line: 13, column: 1) } func testEmptyFile() { - XCTAssertFormatting( - OrderedImports.self, input: "", expected: "", checkForUnassertedDiagnostics: true + assertFormatting( + OrderedImports.self, + input: "", + expected: "", + findings: [] ) - XCTAssertFormatting( - OrderedImports.self, input: "// test", expected: "// test", - checkForUnassertedDiagnostics: true + + assertFormatting( + OrderedImports.self, + input: "// test", + expected: "// test", + findings: [] ) } func testImportsContainingNewlines() { - let input = - """ - import - zeta - import Zeta - import - Alpha - import Beta - """ - - let expected = - """ - import - Alpha - import Beta - import Zeta - import - zeta - """ - - XCTAssertFormatting(OrderedImports.self, input: input, expected: expected) + assertFormatting( + OrderedImports.self, + input: """ + import + zeta + 1️⃣import Zeta + import + Alpha + import Beta + """, + expected: """ + import + Alpha + import Beta + import Zeta + import + zeta + """, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + ] + ) } func testRemovesDuplicateImports() { - let input = - """ - import CoreLocation - import UIKit - import CoreLocation - import ZeeFramework - bar() - """ - - let expected = - """ - import CoreLocation - import UIKit - import ZeeFramework - - bar() - """ - - XCTAssertFormatting( - OrderedImports.self, input: input, expected: expected, checkForUnassertedDiagnostics: true) - XCTAssertDiagnosed(.removeDuplicateImport, line: 3, column: 1) + assertFormatting( + OrderedImports.self, + input: """ + import CoreLocation + import UIKit + 1️⃣import CoreLocation + import ZeeFramework + bar() + """, + expected: """ + import CoreLocation + import UIKit + import ZeeFramework + + bar() + """, + findings: [ + FindingSpec("1️⃣", message: "remove this duplicate import"), + ] + ) } func testDuplicateCommentedImports() { - let input = - """ - import AppKit - // CoreLocation is necessary to get location stuff. - import CoreLocation // This import must stay. - // UIKit does UI Stuff? - import UIKit - // This is the second CoreLocation import. - import CoreLocation // The 2nd CL import has a comment here too. - // Comment about ZeeFramework. - import ZeeFramework - import foo - // Second comment about ZeeFramework. - import ZeeFramework // This one has a trailing comment too. - foo() - """ - - let expected = - """ - import AppKit - // CoreLocation is necessary to get location stuff. - import CoreLocation // This import must stay. - // This is the second CoreLocation import. - import CoreLocation // The 2nd CL import has a comment here too. - // UIKit does UI Stuff? - import UIKit - // Comment about ZeeFramework. - // Second comment about ZeeFramework. - import ZeeFramework // This one has a trailing comment too. - import foo - - foo() - """ - - XCTAssertFormatting( - OrderedImports.self, input: input, expected: expected, checkForUnassertedDiagnostics: true) - - // Even though this import is technically also not sorted, that won't matter if the import is - // removed so there should only be a warning to remove it. - XCTAssertDiagnosed(.removeDuplicateImport, line: 7, column: 1) - XCTAssertDiagnosed(.removeDuplicateImport, line: 12, column: 1) + // Verify that we diagnose redundant imports if they have comments, but don't remove them. + assertFormatting( + OrderedImports.self, + input: """ + import AppKit + // CoreLocation is necessary to get location stuff. + import CoreLocation // This import must stay. + // UIKit does UI Stuff? + import UIKit + // This is the second CoreLocation import. + 1️⃣import CoreLocation // The 2nd CL import has a comment here too. + // Comment about ZeeFramework. + import ZeeFramework + import foo + // Second comment about ZeeFramework. + 2️⃣import ZeeFramework // This one has a trailing comment too. + foo() + """, + expected: """ + import AppKit + // CoreLocation is necessary to get location stuff. + import CoreLocation // This import must stay. + // This is the second CoreLocation import. + import CoreLocation // The 2nd CL import has a comment here too. + // UIKit does UI Stuff? + import UIKit + // Comment about ZeeFramework. + // Second comment about ZeeFramework. + import ZeeFramework // This one has a trailing comment too. + import foo + + foo() + """, + findings: [ + // Even though this import is technically also not sorted, that won't matter if the import + // is removed so there should only be a warning to remove it. + FindingSpec("1️⃣", message: "remove this duplicate import"), + FindingSpec("2️⃣", message: "remove this duplicate import"), + ] + ) } func testDuplicateIgnoredImports() { - let input = - """ - import AppKit - // swift-format-ignore - import CoreLocation - // Second CoreLocation import here. - import CoreLocation - // Comment about ZeeFramework. - import ZeeFramework - // swift-format-ignore - import ZeeFramework // trailing comment - foo() - """ - - let expected = - """ - import AppKit - - // swift-format-ignore - import CoreLocation - - // Second CoreLocation import here. - import CoreLocation - // Comment about ZeeFramework. - import ZeeFramework - - // swift-format-ignore - import ZeeFramework // trailing comment - - foo() - """ - - XCTAssertFormatting( - OrderedImports.self, input: input, expected: expected, checkForUnassertedDiagnostics: true) + assertFormatting( + OrderedImports.self, + input: """ + import AppKit + // swift-format-ignore + import CoreLocation + // Second CoreLocation import here. + import CoreLocation + // Comment about ZeeFramework. + import ZeeFramework + // swift-format-ignore + import ZeeFramework // trailing comment + foo() + """, + expected: """ + import AppKit + + // swift-format-ignore + import CoreLocation + + // Second CoreLocation import here. + import CoreLocation + // Comment about ZeeFramework. + import ZeeFramework + + // swift-format-ignore + import ZeeFramework // trailing comment + + foo() + """, + findings: [] + ) } func testDuplicateAttributedImports() { - let input = - """ - // imports an enum - import enum Darwin.D.isatty - // this is a dup - import enum Darwin.D.isatty - import foo - import a - @testable import foo - // exported import of bar - @_exported import bar - @_implementationOnly import bar - import bar - // second import of foo - import foo - baz() - """ - - let expected = - """ - import a - // exported import of bar - @_exported import bar - @_implementationOnly import bar - import bar - // second import of foo - import foo - - // imports an enum - // this is a dup - import enum Darwin.D.isatty - - @testable import foo - - baz() - """ - - XCTAssertFormatting(OrderedImports.self, input: input, expected: expected) + assertFormatting( + OrderedImports.self, + input: """ + // exported import of bar + @_exported import bar + @_implementationOnly import bar + import bar + import foo + // second import of foo + 1️⃣import foo + + // imports an enum + import enum Darwin.D.isatty + // this is a dup + 2️⃣import enum Darwin.D.isatty + + @testable import foo + + baz() + """, + expected: """ + // exported import of bar + @_exported import bar + @_implementationOnly import bar + import bar + // second import of foo + import foo + + // imports an enum + // this is a dup + import enum Darwin.D.isatty + + @testable import foo + + baz() + """, + findings: [ + FindingSpec("1️⃣", message: "remove this duplicate import"), + FindingSpec("2️⃣", message: "remove this duplicate import"), + ] + ) } func testConditionalImports() { - let input = - """ - import Zebras - import Apples - #if canImport(Darwin) - import Darwin - #elseif canImport(Glibc) - import Glibc - #endif - import Aardvarks - - foo() - bar() - baz() - """ - - let expected = - """ - import Aardvarks - import Apples - import Zebras - - #if canImport(Darwin) - import Darwin - #elseif canImport(Glibc) - import Glibc - #endif - - foo() - bar() - baz() - """ - - XCTAssertFormatting(OrderedImports.self, input: input, expected: expected) + assertFormatting( + OrderedImports.self, + input: """ + import Zebras + 1️⃣import Apples + #if canImport(Darwin) + import Darwin + #elseif canImport(Glibc) + import Glibc + #endif + 2️⃣import Aardvarks + + foo() + bar() + baz() + """, + expected: """ + import Aardvarks + import Apples + import Zebras + + #if canImport(Darwin) + import Darwin + #elseif canImport(Glibc) + import Glibc + #endif + + foo() + bar() + baz() + """, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("2️⃣", message: "place imports at the top of the file"), + ] + ) } func testIgnoredConditionalImports() { - let input = - """ - import Zebras - import Apples - #if canImport(Darwin) - import Darwin - #elseif canImport(Glibc) - import Glibc - #endif - // swift-format-ignore - import Aardvarks - - foo() - bar() - baz() - """ - - let expected = - """ - import Apples - import Zebras - - #if canImport(Darwin) - import Darwin - #elseif canImport(Glibc) - import Glibc - #endif - // swift-format-ignore - import Aardvarks - - foo() - bar() - baz() - """ - - XCTAssertFormatting(OrderedImports.self, input: input, expected: expected) + assertFormatting( + OrderedImports.self, + input: """ + import Zebras + 1️⃣import Apples + #if canImport(Darwin) + import Darwin + #elseif canImport(Glibc) + import Glibc + #endif + // swift-format-ignore + import Aardvarks + + foo() + bar() + baz() + """, + expected: """ + import Apples + import Zebras + + #if canImport(Darwin) + import Darwin + #elseif canImport(Glibc) + import Glibc + #endif + // swift-format-ignore + import Aardvarks + + foo() + bar() + baz() + """, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift b/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift index 27fc788fd..67a1837e1 100644 --- a/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift +++ b/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift @@ -1,70 +1,68 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class ReturnVoidInsteadOfEmptyTupleTests: LintOrFormatRuleTestCase { func testBasic() { - XCTAssertFormatting( + assertFormatting( ReturnVoidInsteadOfEmptyTuple.self, - input: - """ - let callback: () -> () - typealias x = Int -> () - func y() -> Int -> () { return } - func z(d: Bool -> ()) {} + input: """ + let callback: () -> 1️⃣() + typealias x = Int -> 2️⃣() + func y() -> Int -> 3️⃣() { return } + func z(d: Bool -> 4️⃣()) {} """, - expected: - """ + expected: """ let callback: () -> Void typealias x = Int -> Void func y() -> Int -> Void { return } func z(d: Bool -> Void) {} """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "replace '()' with 'Void'"), + FindingSpec("2️⃣", message: "replace '()' with 'Void'"), + FindingSpec("3️⃣", message: "replace '()' with 'Void'"), + FindingSpec("4️⃣", message: "replace '()' with 'Void'"), + ] ) - XCTAssertDiagnosed(.returnVoid, line: 1, column: 21) - XCTAssertDiagnosed(.returnVoid, line: 2, column: 22) - XCTAssertDiagnosed(.returnVoid, line: 3, column: 20) - XCTAssertDiagnosed(.returnVoid, line: 4, column: 19) } func testNestedFunctionTypes() { - XCTAssertFormatting( + assertFormatting( ReturnVoidInsteadOfEmptyTuple.self, - input: - """ - typealias Nested1 = (() -> ()) -> Int - typealias Nested2 = (() -> ()) -> () - typealias Nested3 = Int -> (() -> ()) + input: """ + typealias Nested1 = (() -> 1️⃣()) -> Int + typealias Nested2 = (() -> 2️⃣()) -> 3️⃣() + typealias Nested3 = Int -> (() -> 4️⃣()) """, - expected: - """ + expected: """ typealias Nested1 = (() -> Void) -> Int typealias Nested2 = (() -> Void) -> Void typealias Nested3 = Int -> (() -> Void) """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "replace '()' with 'Void'"), + FindingSpec("2️⃣", message: "replace '()' with 'Void'"), + FindingSpec("3️⃣", message: "replace '()' with 'Void'"), + FindingSpec("4️⃣", message: "replace '()' with 'Void'"), + ] ) - XCTAssertDiagnosed(.returnVoid, line: 1, column: 28) - XCTAssertDiagnosed(.returnVoid, line: 2, column: 28) - XCTAssertDiagnosed(.returnVoid, line: 2, column: 35) - XCTAssertDiagnosed(.returnVoid, line: 3, column: 35) } func testClosureSignatures() { - XCTAssertFormatting( + assertFormatting( ReturnVoidInsteadOfEmptyTuple.self, - input: - """ - callWithTrailingClosure(arg) { arg -> () in body } - callWithTrailingClosure(arg) { arg -> () in - nestedCallWithTrailingClosure(arg) { arg -> () in + input: """ + callWithTrailingClosure(arg) { arg -> 1️⃣() in body } + callWithTrailingClosure(arg) { arg -> 2️⃣() in + nestedCallWithTrailingClosure(arg) { arg -> 3️⃣() in body } } - callWithTrailingClosure(arg) { (arg: () -> ()) -> Int in body } - callWithTrailingClosure(arg) { (arg: () -> ()) -> () in body } + callWithTrailingClosure(arg) { (arg: () -> 4️⃣()) -> Int in body } + callWithTrailingClosure(arg) { (arg: () -> 5️⃣()) -> 6️⃣() in body } """, - expected: - """ + expected: """ callWithTrailingClosure(arg) { arg -> Void in body } callWithTrailingClosure(arg) { arg -> Void in nestedCallWithTrailingClosure(arg) { arg -> Void in @@ -74,60 +72,58 @@ final class ReturnVoidInsteadOfEmptyTupleTests: LintOrFormatRuleTestCase { callWithTrailingClosure(arg) { (arg: () -> Void) -> Int in body } callWithTrailingClosure(arg) { (arg: () -> Void) -> Void in body } """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "replace '()' with 'Void'"), + FindingSpec("2️⃣", message: "replace '()' with 'Void'"), + FindingSpec("3️⃣", message: "replace '()' with 'Void'"), + FindingSpec("4️⃣", message: "replace '()' with 'Void'"), + FindingSpec("5️⃣", message: "replace '()' with 'Void'"), + FindingSpec("6️⃣", message: "replace '()' with 'Void'"), + ] ) - XCTAssertDiagnosed(.returnVoid, line: 1, column: 39) - XCTAssertDiagnosed(.returnVoid, line: 2, column: 39) - XCTAssertDiagnosed(.returnVoid, line: 3, column: 47) - XCTAssertDiagnosed(.returnVoid, line: 7, column: 44) - XCTAssertDiagnosed(.returnVoid, line: 8, column: 44) - XCTAssertDiagnosed(.returnVoid, line: 8, column: 51) } func testTriviaPreservation() { - XCTAssertFormatting( + assertFormatting( ReturnVoidInsteadOfEmptyTuple.self, - input: - """ - let callback: () -> /*foo*/()/*bar*/ - let callback: ((Int) -> /*foo*/ () /*bar*/) -> () + input: """ + let callback: () -> /*foo*/1️⃣()/*bar*/ + let callback: ((Int) -> /*foo*/ 2️⃣() /*bar*/) -> 3️⃣() """, - expected: - """ + expected: """ let callback: () -> /*foo*/Void/*bar*/ let callback: ((Int) -> /*foo*/ Void /*bar*/) -> Void """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "replace '()' with 'Void'"), + FindingSpec("2️⃣", message: "replace '()' with 'Void'"), + FindingSpec("3️⃣", message: "replace '()' with 'Void'"), + ] ) - XCTAssertDiagnosed(.returnVoid, line: 1, column: 28) - XCTAssertDiagnosed(.returnVoid, line: 2, column: 37) - XCTAssertDiagnosed(.returnVoid, line: 2, column: 54) } func testEmptyTupleWithInternalCommentsIsDiagnosedButNotReplaced() { - XCTAssertFormatting( + assertFormatting( ReturnVoidInsteadOfEmptyTuple.self, - input: - """ - let callback: () -> ( ) - let callback: () -> (\t) - let callback: () -> ( + input: """ + let callback: () -> 1️⃣( ) + let callback: () -> 2️⃣(\t) + let callback: () -> 3️⃣( ) - let callback: () -> ( /* please don't change me! */ ) - let callback: () -> ( /** please don't change me! */ ) - let callback: () -> ( + let callback: () -> 4️⃣( /* please don't change me! */ ) + let callback: () -> 5️⃣( /** please don't change me! */ ) + let callback: () -> 6️⃣( // don't change me either! ) - let callback: () -> ( + let callback: () -> 7️⃣( /// don't change me either! ) - let callback: () -> (\u{feff}) + let callback: () -> 8️⃣(\u{feff}) - let callback: (() -> ()) -> ( /* please don't change me! */ ) - callWithTrailingClosure(arg) { (arg: () -> ()) -> ( /* no change */ ) in body } + let callback: (() -> 9️⃣()) -> 🔟( /* please don't change me! */ ) + callWithTrailingClosure(arg) { (arg: () -> 0️⃣()) -> ℹ️( /* no change */ ) in body } """, - expected: - """ + expected: """ let callback: () -> Void let callback: () -> Void let callback: () -> Void @@ -144,19 +140,20 @@ final class ReturnVoidInsteadOfEmptyTupleTests: LintOrFormatRuleTestCase { let callback: (() -> Void) -> ( /* please don't change me! */ ) callWithTrailingClosure(arg) { (arg: () -> Void) -> ( /* no change */ ) in body } """, - checkForUnassertedDiagnostics: true + findings: [ + FindingSpec("1️⃣", message: "replace '()' with 'Void'"), + FindingSpec("2️⃣", message: "replace '()' with 'Void'"), + FindingSpec("3️⃣", message: "replace '()' with 'Void'"), + FindingSpec("4️⃣", message: "replace '()' with 'Void'"), + FindingSpec("5️⃣", message: "replace '()' with 'Void'"), + FindingSpec("6️⃣", message: "replace '()' with 'Void'"), + FindingSpec("7️⃣", message: "replace '()' with 'Void'"), + FindingSpec("8️⃣", message: "replace '()' with 'Void'"), + FindingSpec("9️⃣", message: "replace '()' with 'Void'"), + FindingSpec("🔟", message: "replace '()' with 'Void'"), + FindingSpec("0️⃣", message: "replace '()' with 'Void'"), + FindingSpec("ℹ️", message: "replace '()' with 'Void'"), + ] ) - XCTAssertDiagnosed(.returnVoid, line: 1, column: 21) - XCTAssertDiagnosed(.returnVoid, line: 2, column: 21) - XCTAssertDiagnosed(.returnVoid, line: 3, column: 21) - XCTAssertDiagnosed(.returnVoid, line: 5, column: 21) - XCTAssertDiagnosed(.returnVoid, line: 6, column: 21) - XCTAssertDiagnosed(.returnVoid, line: 7, column: 21) - XCTAssertDiagnosed(.returnVoid, line: 10, column: 21) - XCTAssertDiagnosed(.returnVoid, line: 13, column: 21) - XCTAssertDiagnosed(.returnVoid, line: 15, column: 22) - XCTAssertDiagnosed(.returnVoid, line: 15, column: 29) - XCTAssertDiagnosed(.returnVoid, line: 16, column: 44) - XCTAssertDiagnosed(.returnVoid, line: 16, column: 51) } } diff --git a/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift b/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift index a28e40ddc..28628e1b1 100644 --- a/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift +++ b/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift @@ -1,128 +1,123 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: Diagnostics should be emitted at the identifier, not at the start of the declaration. final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { func testConstruction() { - let input = + assertLint( + TypeNamesShouldBeCapitalized.self, """ - struct a {} - class klassName { - struct subType {} + 1️⃣struct a {} + 2️⃣class klassName { + 3️⃣struct subType {} } - protocol myProtocol {} + 4️⃣protocol myProtocol {} extension myType { - struct innerType {} + 5️⃣struct innerType {} } - """ - - performLint(TypeNamesShouldBeCapitalized.self, input: input) - - XCTAssertDiagnosed(.capitalizeTypeName(name: "a")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "klassName")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "subType")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "myProtocol")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "myType")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "innerType")) + """, + findings: [ + FindingSpec("1️⃣", message: "type names should be capitalized: a -> A"), + FindingSpec("2️⃣", message: "type names should be capitalized: klassName -> KlassName"), + FindingSpec("3️⃣", message: "type names should be capitalized: subType -> SubType"), + FindingSpec("4️⃣", message: "type names should be capitalized: myProtocol -> MyProtocol"), + FindingSpec("5️⃣", message: "type names should be capitalized: innerType -> InnerType"), + ] + ) } func testActors() { - let input = + assertLint( + TypeNamesShouldBeCapitalized.self, """ - actor myActor {} + 1️⃣actor myActor {} actor OtherActor {} - distributed actor greeter {} + 2️⃣distributed actor greeter {} distributed actor DistGreeter {} - """ - - performLint(TypeNamesShouldBeCapitalized.self, input: input) - - XCTAssertDiagnosed(.capitalizeTypeName(name: "myActor")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "OtherActor")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "greeter")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "DistGreeter")) + """, + findings: [ + FindingSpec("1️⃣", message: "type names should be capitalized: myActor -> MyActor"), + FindingSpec("2️⃣", message: "type names should be capitalized: greeter -> Greeter"), + ] + ) } func testAssociatedTypeandTypeAlias() { - let input = + assertLint( + TypeNamesShouldBeCapitalized.self, """ protocol P { - associatedtype kind + 1️⃣associatedtype kind associatedtype OtherKind } - typealias x = Int + 2️⃣typealias x = Int typealias Y = String struct MyType { - typealias data = Y + 3️⃣typealias data = Y func test() { typealias Value = Y } } - """ - - performLint(TypeNamesShouldBeCapitalized.self, input: input) - - XCTAssertDiagnosed(.capitalizeTypeName(name: "kind")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "OtherKind")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "x")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "Y")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "data")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "Value")) + """, + findings: [ + FindingSpec("1️⃣", message: "type names should be capitalized: kind -> Kind"), + FindingSpec("2️⃣", message: "type names should be capitalized: x -> X"), + FindingSpec("3️⃣", message: "type names should be capitalized: data -> Data"), + ] + ) } func testThatUnderscoredNamesAreDiagnosed() { - let input = + assertLint( + TypeNamesShouldBeCapitalized.self, """ - protocol _p { - associatedtype _value + 1️⃣protocol _p { + 2️⃣associatedtype _value associatedtype __Value } protocol ___Q { } - struct _data { - typealias _x = Int + 3️⃣struct _data { + 4️⃣typealias _x = Int } struct _Data {} - actor _internalActor {} + 5️⃣actor _internalActor {} - enum __e { + 6️⃣enum __e { } enum _OtherE { } func test() { - class _myClass {} + 7️⃣class _myClass {} do { class _MyClass {} } } - distributed actor __greeter {} + 8️⃣distributed actor __greeter {} distributed actor __InternalGreeter {} - """ - - performLint(TypeNamesShouldBeCapitalized.self, input: input) - - XCTAssertDiagnosed(.capitalizeTypeName(name: "_p")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "___Q")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "_value")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "__Value")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "_data")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "_Data")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "_x")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "_internalActor")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "__e")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "__OtherE")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "_myClass")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "_MyClass")) - XCTAssertDiagnosed(.capitalizeTypeName(name: "__greeter")) - XCTAssertNotDiagnosed(.capitalizeTypeName(name: "__InternalGreeter")) + """, + findings: [ + FindingSpec("1️⃣", message: "type names should be capitalized: _p -> _P"), + FindingSpec("2️⃣", message: "type names should be capitalized: _value -> _Value"), + FindingSpec("3️⃣", message: "type names should be capitalized: _data -> _Data"), + FindingSpec("4️⃣", message: "type names should be capitalized: _x -> _X"), + FindingSpec("5️⃣", message: "type names should be capitalized: _internalActor -> _InternalActor"), + FindingSpec("6️⃣", message: "type names should be capitalized: __e -> __E"), + FindingSpec("7️⃣", message: "type names should be capitalized: _myClass -> _MyClass"), + FindingSpec("8️⃣", message: "type names should be capitalized: __greeter -> __Greeter"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift b/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift index 279dddf2b..6368366ee 100644 --- a/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift @@ -1,16 +1,21 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: The findings are emitted in odd places; the last test is especially wrong. Their locations +// may be getting computed from the tree post-transformation, so they no longer map to the right +// locations in the original tree. final class UseEarlyExitsTests: LintOrFormatRuleTestCase { func testBasicIfElse() { // In this and other tests, the indentation of the true block in the expected output is // explicitly incorrect because this formatting rule does not fix it up with the assumption that // the pretty-printer will handle it. - XCTAssertFormatting( + assertFormatting( UseEarlyExits.self, input: """ if condition { trueBlock() - } else { + } 1️⃣else { falseBlock() return } @@ -21,17 +26,21 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { return } trueBlock() - """) + """, + findings: [ + FindingSpec("1️⃣", message: "replace the 'if/else' block with a 'guard' statement containing the early exit"), + ] + ) } func testIfElseWithBothEarlyExiting() { - XCTAssertFormatting( + assertFormatting( UseEarlyExits.self, input: """ if condition { trueBlock() return - } else { + } 1️⃣else { falseBlock() return } @@ -43,7 +52,11 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { } trueBlock() return - """) + """, + findings: [ + FindingSpec("1️⃣", message: "replace the 'if/else' block with a 'guard' statement containing the early exit"), + ] + ) } func testElseIfsDoNotChange() { @@ -55,7 +68,7 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { return } """ - XCTAssertFormatting(UseEarlyExits.self, input: input, expected: input) + assertFormatting(UseEarlyExits.self, input: input, expected: input, findings: []) } func testElsesAtEndOfElseIfsDoNotChange() { @@ -70,11 +83,11 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { return } """ - XCTAssertFormatting(UseEarlyExits.self, input: input, expected: input) + assertFormatting(UseEarlyExits.self, input: input, expected: input, findings: []) } func testComplex() { - XCTAssertFormatting( + assertFormatting( UseEarlyExits.self, input: """ func discombobulate(_ values: [Int]) throws -> Int { @@ -88,13 +101,13 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { if first >= 0 { // Comment 4 var result = 0 - for value in values { + 2️⃣ for value in values { result += invertedCombobulatorFactor(of: value) } return result } else { print("Can't have negative energy") - throw DiscombobulationError.negativeEnergy + 1️⃣ throw DiscombobulationError.negativeEnergy } } else { print("The array was empty") @@ -125,6 +138,11 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { } return result } - """) + """, + findings: [ + FindingSpec("1️⃣", message: "replace the 'if/else' block with a 'guard' statement containing the early exit"), + FindingSpec("2️⃣", message: "replace the 'if/else' block with a 'guard' statement containing the early exit"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift b/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift index b1ad1df87..3748173e7 100644 --- a/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift @@ -1,115 +1,122 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class UseLetInEveryBoundCaseVariableTests: LintOrFormatRuleTestCase { - override func setUp() { - super.setUp() - self.shouldCheckForUnassertedDiagnostics = true - } - func testSwitchCase() { - let input = + assertLint( + UseLetInEveryBoundCaseVariable.self, """ switch DataPoint.labeled("hello", 100) { - case let .labeled(label, value): break + case 1️⃣let .labeled(label, value): break case .labeled(label, let value): break case .labeled(let label, let value): break - case let .labeled(label, value)?: break - case let .labeled(label, value)!: break - case let .labeled(label, value)??: break - case let (label, value): break + case 2️⃣let .labeled(label, value)?: break + case 3️⃣let .labeled(label, value)!: break + case 4️⃣let .labeled(label, value)??: break + case 5️⃣let (label, value): break case let x as SomeType: break } - """ - performLint(UseLetInEveryBoundCaseVariable.self, input: input) - - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 2, column: 6) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 5, column: 6) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 6, column: 6) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 7, column: 6) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 8, column: 6) + """, + findings: [ + FindingSpec("1️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("2️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("3️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("4️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("5️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + ] + ) } func testIfCase() { - let input = + assertLint( + UseLetInEveryBoundCaseVariable.self, """ - if case let .labeled(label, value) = DataPoint.labeled("hello", 100) {} + if case 1️⃣let .labeled(label, value) = DataPoint.labeled("hello", 100) {} if case .labeled(label, let value) = DataPoint.labeled("hello", 100) {} if case .labeled(let label, let value) = DataPoint.labeled("hello", 100) {} - if case let .labeled(label, value)? = DataPoint.labeled("hello", 100) {} - if case let .labeled(label, value)! = DataPoint.labeled("hello", 100) {} - if case let .labeled(label, value)?? = DataPoint.labeled("hello", 100) {} - if case let (label, value) = DataPoint.labeled("hello", 100) {} + if case 2️⃣let .labeled(label, value)? = DataPoint.labeled("hello", 100) {} + if case 3️⃣let .labeled(label, value)! = DataPoint.labeled("hello", 100) {} + if case 4️⃣let .labeled(label, value)?? = DataPoint.labeled("hello", 100) {} + if case 5️⃣let (label, value) = DataPoint.labeled("hello", 100) {} if case let x as SomeType = someValue {} - """ - performLint(UseLetInEveryBoundCaseVariable.self, input: input) - - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 1, column: 9) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 4, column: 9) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 5, column: 9) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 6, column: 9) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 7, column: 9) + """, + findings: [ + FindingSpec("1️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("2️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("3️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("4️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("5️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + ] + ) } func testGuardCase() { - let input = + assertLint( + UseLetInEveryBoundCaseVariable.self, """ - guard case let .labeled(label, value) = DataPoint.labeled("hello", 100) else {} + guard case 1️⃣let .labeled(label, value) = DataPoint.labeled("hello", 100) else {} guard case .labeled(label, let value) = DataPoint.labeled("hello", 100) else {} guard case .labeled(let label, let value) = DataPoint.labeled("hello", 100) else {} - guard case let .labeled(label, value)? = DataPoint.labeled("hello", 100) else {} - guard case let .labeled(label, value)! = DataPoint.labeled("hello", 100) else {} - guard case let .labeled(label, value)?? = DataPoint.labeled("hello", 100) else {} - guard case let (label, value) = DataPoint.labeled("hello", 100) else {} + guard case 2️⃣let .labeled(label, value)? = DataPoint.labeled("hello", 100) else {} + guard case 3️⃣let .labeled(label, value)! = DataPoint.labeled("hello", 100) else {} + guard case 4️⃣let .labeled(label, value)?? = DataPoint.labeled("hello", 100) else {} + guard case 5️⃣let (label, value) = DataPoint.labeled("hello", 100) else {} guard case let x as SomeType = someValue else {} - """ - performLint(UseLetInEveryBoundCaseVariable.self, input: input) - - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 1, column: 12) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 4, column: 12) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 5, column: 12) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 6, column: 12) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 7, column: 12) + """, + findings: [ + FindingSpec("1️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("2️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("3️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("4️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("5️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + ] + ) } func testForCase() { - let input = + assertLint( + UseLetInEveryBoundCaseVariable.self, """ - for case let .labeled(label, value) in dataPoints {} + for case 1️⃣let .labeled(label, value) in dataPoints {} for case .labeled(label, let value) in dataPoints {} for case .labeled(let label, let value) in dataPoints {} - for case let .labeled(label, value)? in dataPoints {} - for case let .labeled(label, value)! in dataPoints {} - for case let .labeled(label, value)?? in dataPoints {} - for case let (label, value) in dataPoints {} + for case 2️⃣let .labeled(label, value)? in dataPoints {} + for case 3️⃣let .labeled(label, value)! in dataPoints {} + for case 4️⃣let .labeled(label, value)?? in dataPoints {} + for case 5️⃣let (label, value) in dataPoints {} for case let x as SomeType in {} - """ - performLint(UseLetInEveryBoundCaseVariable.self, input: input) - - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 1, column: 10) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 4, column: 10) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 5, column: 10) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 6, column: 10) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 7, column: 10) + """, + findings: [ + FindingSpec("1️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("2️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("3️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("4️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("5️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + ] + ) } func testWhileCase() { - let input = + assertLint( + UseLetInEveryBoundCaseVariable.self, """ - while case let .labeled(label, value) = iter.next() {} + while case 1️⃣let .labeled(label, value) = iter.next() {} while case .labeled(label, let value) = iter.next() {} while case .labeled(let label, let value) = iter.next() {} - while case let .labeled(label, value)? = iter.next() {} - while case let .labeled(label, value)! = iter.next() {} - while case let .labeled(label, value)?? = iter.next() {} - while case let (label, value) = iter.next() {} + while case 2️⃣let .labeled(label, value)? = iter.next() {} + while case 3️⃣let .labeled(label, value)! = iter.next() {} + while case 4️⃣let .labeled(label, value)?? = iter.next() {} + while case 5️⃣let (label, value) = iter.next() {} while case let x as SomeType = iter.next() {} - """ - performLint(UseLetInEveryBoundCaseVariable.self, input: input) - - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 1, column: 12) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 4, column: 12) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 5, column: 12) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 6, column: 12) - XCTAssertDiagnosed(.useLetInBoundCaseVariables, line: 7, column: 12) + """, + findings: [ + FindingSpec("1️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("2️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("3️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("4️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec("5️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift b/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift index bc856172d..a54b98a9e 100644 --- a/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift @@ -1,75 +1,114 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { func testNamesInTypeContextsAreShortened() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var a: Array = [] - var b: Dictionary = [:] - var c: Optional = nil - """, - expected: - """ + input: """ + var a: 1️⃣Array = [] + var b: 2️⃣Dictionary = [:] + var c: 3️⃣Optional = nil + """, + expected: """ var a: [Int] = [] var b: [String: Int] = [:] var c: Foo? = nil - """) - - XCTAssertDiagnosed(.useTypeShorthand(type: "Array")) - XCTAssertDiagnosed(.useTypeShorthand(type: "Dictionary")) - XCTAssertDiagnosed(.useTypeShorthand(type: "Optional")) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testNestedNamesInTypeContextsAreShortened() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var a: Array> - var b: Array<[Int]> - var c: [Array] - - var a: Dictionary, Int> - var b: Dictionary> - var c: Dictionary, Dictionary> - var d: Dictionary<[String: Int], Int> - var e: Dictionary - var f: Dictionary<[String: Int], [String: Int]> - var g: [Dictionary: Int] - var h: [String: Dictionary] - var i: [Dictionary: Dictionary] - - let a: Optional> - let b: Optional> - let c: Optional> - let d: Array? - let e: Dictionary? - let f: Optional? - let g: Optional - - var a: Array> - var b: Dictionary, Optional> - var c: Array - var d: Dictionary + input: """ + var a: 1️⃣Array<2️⃣Array> + var b: 3️⃣Array<[Int]> + var c: [4️⃣Array] """, - expected: - """ + expected: """ var a: [[Int]] var b: [[Int]] var c: [[Int]] - + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Array' type"), + ] + ) + + assertFormatting( + UseShorthandTypeNames.self, + input: """ + var a: 1️⃣Dictionary<2️⃣Dictionary, Int> + var b: 3️⃣Dictionary> + var c: 5️⃣Dictionary<6️⃣Dictionary, 7️⃣Dictionary> + var d: 8️⃣Dictionary<[String: Int], Int> + var e: 9️⃣Dictionary + """, + expected: """ var a: [[String: Int]: Int] var b: [String: [String: Int]] var c: [[String: Int]: [String: Int]] var d: [[String: Int]: Int] var e: [String: [String: Int]] + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("8️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("9️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + ] + ) + + assertFormatting( + UseShorthandTypeNames.self, + input: """ + var f: 1️⃣Dictionary<[String: Int], [String: Int]> + var g: [2️⃣Dictionary: Int] + var h: [String: 3️⃣Dictionary] + var i: [4️⃣Dictionary: 5️⃣Dictionary] + """, + expected: """ var f: [[String: Int]: [String: Int]] var g: [[String: Int]: Int] var h: [String: [String: Int]] var i: [[String: Int]: [String: Int]] - + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + ] + ) + + assertFormatting( + UseShorthandTypeNames.self, + input: """ + let a: 1️⃣Optional<2️⃣Array> + let b: 3️⃣Optional<4️⃣Dictionary> + let c: 5️⃣Optional<6️⃣Optional> + let d: 7️⃣Array? + let e: 8️⃣Dictionary? + let f: 9️⃣Optional? + let g: 🔟Optional + """, + expected: """ let a: [Int]? let b: [String: Int]? let c: Int?? @@ -77,83 +116,153 @@ final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { let e: [String: Int]? let f: Int?? let g: Int?? - + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("8️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("9️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("🔟", message: "use shorthand syntax for this 'Optional' type"), + ] + ) + + assertFormatting( + UseShorthandTypeNames.self, + input: """ + var a: 1️⃣Array<2️⃣Optional> + var b: 3️⃣Dictionary<4️⃣Optional, 5️⃣Optional> + var c: 6️⃣Array + var d: 7️⃣Dictionary + """, + expected: """ var a: [Int?] var b: [String?: Int?] var c: [Int?] var d: [String?: Int?] - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + ] + ) } func testNamesInNonMemberAccessExpressionContextsAreShortened() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var a = Array() - var b = Dictionary() - var c = Optional(from: decoder) - """, - expected: - """ + input: """ + var a = 1️⃣Array() + var b = 2️⃣Dictionary() + var c = 3️⃣Optional(from: decoder) + """, + expected: """ var a = [Int]() var b = [String: Int]() var c = String?(from: decoder) - """) - - XCTAssertDiagnosed(.useTypeShorthand(type: "Array")) - XCTAssertDiagnosed(.useTypeShorthand(type: "Dictionary")) - XCTAssertDiagnosed(.useTypeShorthand(type: "Optional")) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testNestedNamesInNonMemberAccessExpressionContextsAreShortened() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var a = Array>() - var b = Array<[Int]>() - var c = [Array]() - - var a = Dictionary, Int>() - var b = Dictionary>() - var c = Dictionary, Dictionary>() - var d = Dictionary<[String: Int], Int>() - var e = Dictionary() - var f = Dictionary<[String: Int], [String: Int]>() - var g = [Dictionary: Int]() - var h = [String: Dictionary]() - var i = [Dictionary: Dictionary]() - - var a = Optional>(from: decoder) - var b = Optional>(from: decoder) - var c = Optional>(from: decoder) - var d = Array?(from: decoder) - var e = Dictionary?(from: decoder) - var f = Optional?(from: decoder) - var g = Optional(from: decoder) - - var a = Array>() - var b = Dictionary, Optional>() - var c = Array() - var d = Dictionary() + input: """ + var a = 1️⃣Array<2️⃣Array>() + var b = 3️⃣Array<[Int]>() + var c = [4️⃣Array]() """, - expected: - """ + expected: """ var a = [[Int]]() var b = [[Int]]() var c = [[Int]]() - + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Array' type"), + ] + ) + + assertFormatting( + UseShorthandTypeNames.self, + input: """ + var a = 1️⃣Dictionary<2️⃣Dictionary, Int>() + var b = 3️⃣Dictionary>() + var c = 5️⃣Dictionary<6️⃣Dictionary, 7️⃣Dictionary>() + var d = 8️⃣Dictionary<[String: Int], Int>() + var e = 9️⃣Dictionary() + """, + expected: """ var a = [[String: Int]: Int]() var b = [String: [String: Int]]() var c = [[String: Int]: [String: Int]]() var d = [[String: Int]: Int]() var e = [String: [String: Int]]() + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("8️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("9️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + ] + ) + + assertFormatting( + UseShorthandTypeNames.self, + input: """ + var f = 1️⃣Dictionary<[String: Int], [String: Int]>() + var g = [2️⃣Dictionary: Int]() + var h = [String: 3️⃣Dictionary]() + var i = [4️⃣Dictionary: 5️⃣Dictionary]() + """, + expected: """ var f = [[String: Int]: [String: Int]]() var g = [[String: Int]: Int]() var h = [String: [String: Int]]() var i = [[String: Int]: [String: Int]]() - + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + ] + ) + + assertFormatting( + UseShorthandTypeNames.self, + input: """ + var a = 1️⃣Optional<2️⃣Array>(from: decoder) + var b = 3️⃣Optional<4️⃣Dictionary>(from: decoder) + var c = 5️⃣Optional<6️⃣Optional>(from: decoder) + var d = 7️⃣Array?(from: decoder) + var e = 8️⃣Dictionary?(from: decoder) + var f = 9️⃣Optional?(from: decoder) + var g = 🔟Optional(from: decoder) + """, + expected: """ var a = [Int]?(from: decoder) var b = [String: Int]?(from: decoder) var c = Int??(from: decoder) @@ -161,72 +270,149 @@ final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { var e = [String: Int]?(from: decoder) var f = Int??(from: decoder) var g = Int??(from: decoder) - + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("8️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("9️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("🔟", message: "use shorthand syntax for this 'Optional' type"), + ] + ) + + assertFormatting( + UseShorthandTypeNames.self, + input: """ + var a = 1️⃣Array<2️⃣Optional>() + var b = 3️⃣Dictionary<4️⃣Optional, 5️⃣Optional>() + var c = 6️⃣Array() + var d = 7️⃣Dictionary() + """, + expected: """ var a = [Int?]() var b = [String?: Int?]() var c = [Int?]() var d = [String?: Int?]() - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + ] + ) } func testTypesWithMemberAccessesAreNotShortened() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ + input: """ var a: Array.Index = Array.Index() var b: Dictionary.Index = Dictionary.Index() - var c: Array>.Index = Array>.Index() - var d: Dictionary, Array>.Index = Dictionary, Array>.Index() - var e: Array.Index> = Array.Index>() - - var f: Foo>.Bar = Foo>.Bar() - var g: Foo.Index>.Bar = Foo.Index>.Bar() - var h: Foo.Bar> = Foo.Bar>() - var i: Foo.Bar.Index> = Foo.Bar.Index>() - - var j: Optional>.Publisher = Optional>.Publisher() - var k: Optional>.Publisher = Optional>.Publisher() - var l: Optional>.Publisher = Optional>.Publisher() + var c: Array<1️⃣Optional>.Index = Array<2️⃣Optional>.Index() + var d: Dictionary<3️⃣Optional, 4️⃣Array>.Index = Dictionary<5️⃣Optional, 6️⃣Array>.Index() + var e: 7️⃣Array.Index> = 8️⃣Array.Index>() """, - expected: - """ + expected: """ var a: Array.Index = Array.Index() var b: Dictionary.Index = Dictionary.Index() var c: Array.Index = Array.Index() var d: Dictionary.Index = Dictionary.Index() var e: [Array.Index] = [Array.Index]() - + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("8️⃣", message: "use shorthand syntax for this 'Array' type"), + ] + ) + + assertFormatting( + UseShorthandTypeNames.self, + input: """ + var f: Foo<1️⃣Array>.Bar = Foo<2️⃣Array>.Bar() + var g: Foo.Index>.Bar = Foo.Index>.Bar() + var h: Foo.Bar<3️⃣Array> = Foo.Bar<4️⃣Array>() + var i: Foo.Bar.Index> = Foo.Bar.Index>() + """, + expected: """ var f: Foo<[Int]>.Bar = Foo<[Int]>.Bar() var g: Foo.Index>.Bar = Foo.Index>.Bar() var h: Foo.Bar<[Int]> = Foo.Bar<[Int]>() var i: Foo.Bar.Index> = Foo.Bar.Index>() - + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Array' type"), + ] + ) + + assertFormatting( + UseShorthandTypeNames.self, + input: """ + var j: Optional<1️⃣Array>.Publisher = Optional<2️⃣Array>.Publisher() + var k: Optional<3️⃣Dictionary>.Publisher = Optional<4️⃣Dictionary>.Publisher() + var l: Optional<5️⃣Optional>.Publisher = Optional<6️⃣Optional>.Publisher() + """, + expected: """ var j: Optional<[Int]>.Publisher = Optional<[Int]>.Publisher() var k: Optional<[String: Int]>.Publisher = Optional<[String: Int]>.Publisher() var l: Optional.Publisher = Optional.Publisher() - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testFunctionTypesAreOnlyWrappedWhenShortenedAsOptionals() { // Some of these examples are questionable since function types aren't hashable and thus not // valid dictionary keys, nor are they codable, but syntactically they're fine. - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var a: Array<(Foo) -> Bar> = Array<(Foo) -> Bar>() - var b: Dictionary<(Foo) -> Bar, (Foo) -> Bar> = Dictionary<(Foo) -> Bar, (Foo) -> Bar>() - var c: Optional<(Foo) -> Bar> = Optional<(Foo) -> Bar>(from: decoder) - var d: Optional<((Foo) -> Bar)> = Optional<((Foo) -> Bar)>(from: decoder) - """, - expected: - """ + input: """ + var a: 1️⃣Array<(Foo) -> Bar> = 2️⃣Array<(Foo) -> Bar>() + var b: 3️⃣Dictionary<(Foo) -> Bar, (Foo) -> Bar> = 4️⃣Dictionary<(Foo) -> Bar, (Foo) -> Bar>() + var c: 5️⃣Optional<(Foo) -> Bar> = 6️⃣Optional<(Foo) -> Bar>(from: decoder) + var d: 7️⃣Optional<((Foo) -> Bar)> = 8️⃣Optional<((Foo) -> Bar)>(from: decoder) + """, + expected: """ var a: [(Foo) -> Bar] = [(Foo) -> Bar]() var b: [(Foo) -> Bar: (Foo) -> Bar] = [(Foo) -> Bar: (Foo) -> Bar]() var c: ((Foo) -> Bar)? = ((Foo) -> Bar)?(from: decoder) var d: ((Foo) -> Bar)? = ((Foo) -> Bar)?(from: decoder) - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("8️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testTypesWithEmptyTupleAsGenericArgumentAreNotShortenedInExpressionContexts() { @@ -236,133 +422,165 @@ final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { // containing the empty tuple, and `[(): ()]` would be a dictionary literal mapping the empty // tuple to the empty tuple. Because of this, we cannot permit the empty tuple type to appear // directly inside an expression context. In type contexts, however, it's fine. - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var a: Optional<()> = Optional<()>(from: decoder) - var b: Array<()> = Array<()>() - var c: Dictionary<(), ()> = Dictionary<(), ()>() - var d: Array<(Optional<()>) -> Optional<()>> = Array<(Optional<()>) -> Optional<()>>() - """, - expected: - """ + input: """ + var a: 4️⃣Optional<()> = Optional<()>(from: decoder) + var b: 1️⃣Array<()> = Array<()>() + var c: 3️⃣Dictionary<(), ()> = Dictionary<(), ()>() + var d: 2️⃣Array<(5️⃣Optional<()>) -> 6️⃣Optional<()>> = Array<(7️⃣Optional<()>) -> 8️⃣Optional<()>>() + """, + expected: """ var a: ()? = Optional<()>(from: decoder) var b: [()] = Array<()>() var c: [(): ()] = Dictionary<(), ()>() var d: [(()?) -> ()?] = Array<(()?) -> ()?>() - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("8️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testPreservesNestedGenericsForUnshortenedTypes() { // Regression test for a bug that discarded the generic argument list of a nested type when // shortening something like `Array>` to `[Range]` (instead of `[Range]`. - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var a: Array> = Array>() - var b: Dictionary, Range> = Dictionary, Range>() - var c: Optional> = Optional>(from: decoder) - """, - expected: - """ + input: """ + var a: 1️⃣Array> = 2️⃣Array>() + var b: 3️⃣Dictionary, Range> = 4️⃣Dictionary, Range>() + var c: 5️⃣Optional> = 6️⃣Optional>(from: decoder) + """, + expected: """ var a: [Range] = [Range]() var b: [Range: Range] = [Range: Range]() var c: Range? = Range?(from: decoder) - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testTypesWithIncorrectNumbersOfGenericArgumentsAreNotChanged() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var a: Array, Bar> = Array, Bar>() - var b: Dictionary> = Dictionary>() - var c: Optional, Bar> = Optional, Bar>(from: decoder) - """, - expected: - """ + input: """ + var a: Array<1️⃣Array, Bar> = Array<2️⃣Array, Bar>() + var b: Dictionary<3️⃣Dictionary> = Dictionary<4️⃣Dictionary>() + var c: Optional<5️⃣Optional, Bar> = Optional<6️⃣Optional, Bar>(from: decoder) + """, + expected: """ var a: Array<[Foo], Bar> = Array<[Foo], Bar>() var b: Dictionary<[Foo: Bar]> = Dictionary<[Foo: Bar]>() var c: Optional = Optional(from: decoder) - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testModuleQualifiedNamesAreNotShortened() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var a: Swift.Array> = Swift.Array>() - var b: Swift.Dictionary, Dictionary> = Swift.Dictionary, Dictionary>() - var c: Swift.Optional> = Swift.Optional>(from: decoder) - """, - expected: - """ + input: """ + var a: Swift.Array<1️⃣Array> = Swift.Array<2️⃣Array>() + var b: Swift.Dictionary<3️⃣Dictionary, 4️⃣Dictionary> = Swift.Dictionary<5️⃣Dictionary, 6️⃣Dictionary>() + var c: Swift.Optional<7️⃣Optional> = Swift.Optional<8️⃣Optional>(from: decoder) + """, + expected: """ var a: Swift.Array<[Foo]> = Swift.Array<[Foo]>() var b: Swift.Dictionary<[Foo: Bar], [Foo: Bar]> = Swift.Dictionary<[Foo: Bar], [Foo: Bar]>() var c: Swift.Optional = Swift.Optional(from: decoder) - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Array' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Dictionary' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("8️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testTypesWeDoNotCareAboutAreUnchanged() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ + input: """ var a: Larry = Larry() var b: Pictionary = Pictionary() var c: Sectional = Sectional(from: warehouse) """, - expected: - """ + expected: """ var a: Larry = Larry() var b: Pictionary = Pictionary() var c: Sectional = Sectional(from: warehouse) - """) + """, + findings: [] + ) } func testOptionalStoredVarsWithoutInitializersAreNotChangedUnlessImmutable() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ + input: """ var a: Optional var b: Optional { didSet {} } - let c: Optional + let c: 1️⃣Optional """, - expected: - """ + expected: """ var a: Optional var b: Optional { didSet {} } let c: Int? - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testOptionalComputedVarsAreChanged() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var a: Optional { nil } - var b: Optional { + input: """ + var a: 1️⃣Optional { nil } + var b: 2️⃣Optional { get { 0 } } - var c: Optional { + var c: 3️⃣Optional { _read {} } - var d: Optional { + var d: 4️⃣Optional { unsafeAddress {} } """, - expected: - """ + expected: """ var a: Int? { nil } var b: Int? { get { 0 } @@ -373,68 +591,81 @@ final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { var d: Int? { unsafeAddress {} } - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testOptionalStoredVarsWithInitializersAreChanged() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var a: Optional = nil - var b: Optional = nil { + input: """ + var a: 1️⃣Optional = nil + var b: 2️⃣Optional = nil { didSet {} } - let c: Optional = nil + let c: 3️⃣Optional = nil """, - expected: - """ + expected: """ var a: Int? = nil var b: Int? = nil { didSet {} } let c: Int? = nil - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testOptionalsNestedInOtherTypesInStoredVarsAreStillChanged() { - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - var c: Generic> - var d: [Optional] - var e: [String: Optional] - """, - expected: - """ + input: """ + var c: Generic<1️⃣Optional> + var d: [2️⃣Optional] + var e: [String: 3️⃣Optional] + """, + expected: """ var c: Generic var d: [Int?] var e: [String: Int?] - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } func testSomeAnyTypesInOptionalsAreParenthesized() { // If we need to insert parentheses, verify that we do, but also verify that we don't insert // them unnecessarily. - XCTAssertFormatting( + assertFormatting( UseShorthandTypeNames.self, - input: - """ - func f(_: Optional) {} - func g(_: Optional) {} - var x: Optional = S() - var y: Optional = S() - var z = [Optional]([S()]) - - func f(_: Optional<(some P)>) {} - func g(_: Optional<(any P)>) {} - var x: Optional<(some P)> = S() - var y: Optional<(any P)> = S() - var z = [Optional<(any P)>]([S()]) - """, - expected: - """ + input: """ + func f(_: 1️⃣Optional) {} + func g(_: 2️⃣Optional) {} + var x: 3️⃣Optional = S() + var y: 4️⃣Optional = S() + var z = [5️⃣Optional]([S()]) + + func f(_: 6️⃣Optional<(some P)>) {} + func g(_: 7️⃣Optional<(any P)>) {} + var x: 8️⃣Optional<(some P)> = S() + var y: 9️⃣Optional<(any P)> = S() + var z = [🔟Optional<(any P)>]([S()]) + """, + expected: """ func f(_: (some P)?) {} func g(_: (any P)?) {} var x: (some P)? = S() @@ -446,6 +677,19 @@ final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { var x: (some P)? = S() var y: (any P)? = S() var z = [(any P)?]([S()]) - """) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("8️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("9️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("🔟", message: "use shorthand syntax for this 'Optional' type"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift b/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift index 58ddf252c..c5086c0d6 100644 --- a/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift @@ -1,66 +1,72 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class UseSingleLinePropertyGetterTests: LintOrFormatRuleTestCase { func testMultiLinePropertyGetter() { - XCTAssertFormatting( + assertFormatting( UseSingleLinePropertyGetter.self, input: """ - var g: Int { return 4 } - var h: Int { - get { - return 4 - } - } - var i: Int { - get { return 0 } - set { print("no set, only get") } - } - var j: Int { - mutating get { return 0 } - } - var k: Int { - get async { - return 4 - } - } - var l: Int { - get throws { - return 4 - } - } - var m: Int { - get async throws { - return 4 - } - } - """, + var g: Int { return 4 } + var h: Int { + 1️⃣get { + return 4 + } + } + var i: Int { + get { return 0 } + set { print("no set, only get") } + } + var j: Int { + mutating get { return 0 } + } + var k: Int { + get async { + return 4 + } + } + var l: Int { + get throws { + return 4 + } + } + var m: Int { + get async throws { + return 4 + } + } + """, expected: """ - var g: Int { return 4 } - var h: Int { - return 4 - } - var i: Int { - get { return 0 } - set { print("no set, only get") } - } - var j: Int { - mutating get { return 0 } - } - var k: Int { - get async { - return 4 - } - } - var l: Int { - get throws { - return 4 - } - } - var m: Int { - get async throws { - return 4 - } - } - """) + var g: Int { return 4 } + var h: Int { + return 4 + } + var i: Int { + get { return 0 } + set { print("no set, only get") } + } + var j: Int { + mutating get { return 0 } + } + var k: Int { + get async { + return 4 + } + } + var l: Int { + get throws { + return 4 + } + } + var m: Int { + get async throws { + return 4 + } + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove 'get {...}' around the accessor and move its body directly into the computed property"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift b/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift index d137b1d97..fa00858fe 100644 --- a/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift @@ -1,12 +1,11 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { - override func setUp() { - self.shouldCheckForUnassertedDiagnostics = true - } - func testMemberwiseInitializerIsDiagnosed() { - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { @@ -14,20 +13,22 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { let phoneNumber: String internal let address: String - init(name: String, phoneNumber: String, address: String) { + 1️⃣init(name: String, phoneNumber: String, address: String) { self.name = name self.address = address self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertDiagnosed(.removeRedundantInitializer, line: 7) + """, + findings: [ + FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + ] + ) } func testInternalMemberwiseInitializerIsDiagnosed() { - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { @@ -35,43 +36,47 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { let phoneNumber: String internal let address: String - internal init(name: String, phoneNumber: String, address: String) { + 1️⃣internal init(name: String, phoneNumber: String, address: String) { self.name = name self.address = address self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertDiagnosed(.removeRedundantInitializer, line: 7) + """, + findings: [ + FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + ] + ) } func testMemberwiseInitializerWithDefaultArgumentIsDiagnosed() { - let input = - """ - public struct Person { - - public var name: String = "John Doe" - let phoneNumber: String - internal let address: String - - init(name: String = "John Doe", phoneNumber: String, address: String) { - self.name = name - self.address = address - self.phoneNumber = phoneNumber - } - } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertDiagnosed(.removeRedundantInitializer, line: 7) - } + assertLint( + UseSynthesizedInitializer.self, + """ + public struct Person { + + public var name: String = "John Doe" + let phoneNumber: String + internal let address: String + + 1️⃣init(name: String = "John Doe", phoneNumber: String, address: String) { + self.name = name + self.address = address + self.phoneNumber = phoneNumber + } + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + ] + ) + } func testCustomInitializerVoidsSynthesizedInitializerWarning() { // The compiler won't create a memberwise initializer when there are any other initializers. // It's valid to have a memberwise initializer when there are any custom initializers. - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { @@ -85,64 +90,64 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { self.phoneNumber = phoneNumber } - init(name: String, phoneNumber: String, address: String) { + init(name: String, address: String) { self.name = name self.phoneNumber = "1234578910" self.address = address } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + """, + findings: [] + ) } func testMemberwiseInitializerWithDefaultArgument() { - let input = - """ - public struct Person { - - public var name: String - let phoneNumber: String - let address: String - - init(name: String = "Jane Doe", phoneNumber: String, address: String) { - self.name = name - self.address = address - self.phoneNumber = phoneNumber - } - } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + assertLint( + UseSynthesizedInitializer.self, + """ + public struct Person { + + public var name: String + let phoneNumber: String + let address: String + + init(name: String = "Jane Doe", phoneNumber: String, address: String) { + self.name = name + self.address = address + self.phoneNumber = phoneNumber + } + } + """, + findings: [] + ) } func testMemberwiseInitializerWithNonMatchingDefaultValues() { - let input = - """ - public struct Person { - - public var name: String = "John Doe" - let phoneNumber: String - let address: String - - init(name: String = "Jane Doe", phoneNumber: String, address: String) { - self.name = name - self.address = address - self.phoneNumber = phoneNumber - } - } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + assertLint( + UseSynthesizedInitializer.self, + """ + public struct Person { + + public var name: String = "John Doe" + let phoneNumber: String + let address: String + + init(name: String = "Jane Doe", phoneNumber: String, address: String) { + self.name = name + self.address = address + self.phoneNumber = phoneNumber + } + } + """, + findings: [] + ) } func testMemberwiseInitializerMissingDefaultValues() { // When the initializer doesn't contain a matching default argument, then it isn't equivalent to // the synthesized memberwise initializer. - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { @@ -156,14 +161,14 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + """, + findings: [] + ) } func testCustomInitializerWithMismatchedTypes() { - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { @@ -177,14 +182,14 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + """, + findings: [] + ) } func testCustomInitializerWithExtraParameters() { - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { @@ -198,14 +203,14 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + """, + findings: [] + ) } func testCustomInitializerWithExtraStatements() { - let input = + assertLint( + UseSynthesizedInitializer.self, #""" public struct Person { @@ -221,14 +226,14 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { print("phoneNumber: \(self.phoneNumber)") } } - """# - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + """#, + findings: [] + ) } func testFailableMemberwiseInitializerIsNotDiagnosed() { - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { @@ -242,14 +247,14 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + """, + findings: [] + ) } func testThrowingMemberwiseInitializerIsNotDiagnosed() { - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { @@ -263,14 +268,14 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + """, + findings: [] + ) } func testPublicMemberwiseInitializerIsNotDiagnosed() { - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { @@ -284,16 +289,16 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + """, + findings: [] + ) } func testDefaultMemberwiseInitializerIsNotDiagnosed() { // The synthesized initializer is private when any member is private, so an initializer with // default access control (i.e. internal) is not equivalent to the synthesized initializer. - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { @@ -305,64 +310,68 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + """, + findings: [] + ) } func testPrivateMemberwiseInitializerWithPrivateMemberIsDiagnosed() { // The synthesized initializer is private when any member is private, so a private initializer // is equivalent to the synthesized initializer. - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { let phoneNumber: String private let address: String - private init(phoneNumber: String, address: String) { + 1️⃣private init(phoneNumber: String, address: String) { self.address = address self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertDiagnosed(.removeRedundantInitializer, line: 6) + """, + findings: [ + FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + ] + ) } func testFileprivateMemberwiseInitializerWithFileprivateMemberIsDiagnosed() { // The synthesized initializer is fileprivate when any member is fileprivate, so a fileprivate // initializer is equivalent to the synthesized initializer. - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { let phoneNumber: String fileprivate let address: String - fileprivate init(phoneNumber: String, address: String) { + 1️⃣fileprivate init(phoneNumber: String, address: String) { self.address = address self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertDiagnosed(.removeRedundantInitializer, line: 6) + """, + findings: [ + FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + ] + ) } func testCustomSetterAccessLevel() { // When a property has a different access level for its setter, the setter's access level // doesn't change the access level of the synthesized initializer. - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { let phoneNumber: String private(set) let address: String - init(phoneNumber: String, address: String) { + 1️⃣init(phoneNumber: String, address: String) { self.address = address self.phoneNumber = phoneNumber } @@ -372,7 +381,7 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { fileprivate let phoneNumber: String private(set) let address: String - fileprivate init(phoneNumber: String, address: String) { + 2️⃣fileprivate init(phoneNumber: String, address: String) { self.address = address self.phoneNumber = phoneNumber } @@ -382,7 +391,7 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { fileprivate(set) let phoneNumber: String private(set) let address: String - init(phoneNumber: String, address: String) { + 3️⃣init(phoneNumber: String, address: String) { self.address = address self.phoneNumber = phoneNumber } @@ -397,16 +406,18 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertDiagnosed(.removeRedundantInitializer, line: 5) - XCTAssertDiagnosed(.removeRedundantInitializer, line: 15) - XCTAssertDiagnosed(.removeRedundantInitializer, line: 25) + """, + findings: [ + FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + FindingSpec("2️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + FindingSpec("3️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + ] + ) } func testMemberwiseInitializerWithAttributeIsNotDiagnosed() { - let input = + assertLint( + UseSynthesizedInitializer.self, """ public struct Person { let phoneNumber: String @@ -417,9 +428,8 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { self.phoneNumber = phoneNumber } } - """ - - performLint(UseSynthesizedInitializer.self, input: input) - XCTAssertNotDiagnosed(.removeRedundantInitializer) + """, + findings: [] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift index 7bc8b60f6..757687645 100644 --- a/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift @@ -1,156 +1,169 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: Findings aren't actually being emitted by this rule! final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCase { func testRemoveDocBlockComments() { - XCTAssertFormatting( + assertFormatting( UseTripleSlashForDocumentationComments.self, input: """ - /** - * This comment should not be converted. - */ - - /** - * Returns a docLineComment. - * - * - Parameters: - * - withOutStar: Indicates if the comment start with a star. - * - Returns: docLineComment. - */ - func foo(withOutStar: Bool) {} - """, + /** + * This comment should not be converted. + */ + + /** + * Returns a docLineComment. + * + * - Parameters: + * - withOutStar: Indicates if the comment start with a star. + * - Returns: docLineComment. + */ + func foo(withOutStar: Bool) {} + """, expected: """ - /** - * This comment should not be converted. - */ - - /// Returns a docLineComment. - /// - /// - Parameters: - /// - withOutStar: Indicates if the comment start with a star. - /// - Returns: docLineComment. - func foo(withOutStar: Bool) {} - """) + /** + * This comment should not be converted. + */ + + /// Returns a docLineComment. + /// + /// - Parameters: + /// - withOutStar: Indicates if the comment start with a star. + /// - Returns: docLineComment. + func foo(withOutStar: Bool) {} + """, + findings: [] + ) } func testRemoveDocBlockCommentsWithoutStars() { - XCTAssertFormatting( + assertFormatting( UseTripleSlashForDocumentationComments.self, input: """ - /** - Returns a docLineComment. - - - Parameters: - - withStar: Indicates if the comment start with a star. - - Returns: docLineComment. - */ - public var test = 1 - """, + /** + Returns a docLineComment. + + - Parameters: + - withStar: Indicates if the comment start with a star. + - Returns: docLineComment. + */ + public var test = 1 + """, expected: """ - /// Returns a docLineComment. - /// - /// - Parameters: - /// - withStar: Indicates if the comment start with a star. - /// - Returns: docLineComment. - public var test = 1 - """) + /// Returns a docLineComment. + /// + /// - Parameters: + /// - withStar: Indicates if the comment start with a star. + /// - Returns: docLineComment. + public var test = 1 + """, + findings: [] + ) } func testMultipleTypesOfDocComments() { - XCTAssertFormatting( + assertFormatting( UseTripleSlashForDocumentationComments.self, input: """ - /** - * This is my preamble. It could be important. - * This comment stays as-is. - */ - - /// This decl has a comment. - /// The comment is multiple lines long. - public class AClazz { - } - """, + /** + * This is my preamble. It could be important. + * This comment stays as-is. + */ + + /// This decl has a comment. + /// The comment is multiple lines long. + public class AClazz { + } + """, expected: """ - /** - * This is my preamble. It could be important. - * This comment stays as-is. - */ - - /// This decl has a comment. - /// The comment is multiple lines long. - public class AClazz { - } - """) + /** + * This is my preamble. It could be important. + * This comment stays as-is. + */ + + /// This decl has a comment. + /// The comment is multiple lines long. + public class AClazz { + } + """, + findings: [] + ) } func testMultipleDocLineComments() { - XCTAssertFormatting( + assertFormatting( UseTripleSlashForDocumentationComments.self, input: """ - /// This is my preamble. It could be important. - /// This comment stays as-is. - /// - - /// This decl has a comment. - /// The comment is multiple lines long. - public class AClazz { - } - """, + /// This is my preamble. It could be important. + /// This comment stays as-is. + /// + + /// This decl has a comment. + /// The comment is multiple lines long. + public class AClazz { + } + """, expected: """ - /// This is my preamble. It could be important. - /// This comment stays as-is. - /// - - /// This decl has a comment. - /// The comment is multiple lines long. - public class AClazz { - } - """) + /// This is my preamble. It could be important. + /// This comment stays as-is. + /// + + /// This decl has a comment. + /// The comment is multiple lines long. + public class AClazz { + } + """, + findings: [] + ) } func testManyDocComments() { // Note that this retains the trailing space at the end of a single-line doc block comment // (i.e., the space in `name. */`). It's fine to leave it here; the pretty printer will remove // it later. - XCTAssertFormatting( + assertFormatting( UseTripleSlashForDocumentationComments.self, input: """ - /** - * This is my preamble. It could be important. - * This comment stays as-is. - */ + /** + * This is my preamble. It could be important. + * This comment stays as-is. + */ - /// This is a doc-line comment! + /// This is a doc-line comment! - /** This is a fairly short doc-block comment. */ + /** This is a fairly short doc-block comment. */ - /// Why are there so many comments? - /// Who knows! But there are loads. + /// Why are there so many comments? + /// Who knows! But there are loads. - /** AClazz is a class with good name. */ - public class AClazz { - } - """, + /** AClazz is a class with good name. */ + public class AClazz { + } + """, expected: """ - /** - * This is my preamble. It could be important. - * This comment stays as-is. - */ + /** + * This is my preamble. It could be important. + * This comment stays as-is. + */ - /// This is a doc-line comment! + /// This is a doc-line comment! - /** This is a fairly short doc-block comment. */ + /** This is a fairly short doc-block comment. */ - /// Why are there so many comments? - /// Who knows! But there are loads. + /// Why are there so many comments? + /// Who knows! But there are loads. - /// AClazz is a class with good name.\u{0020} - public class AClazz { - } - """) + /// AClazz is a class with good name.\u{0020} + public class AClazz { + } + """, + findings: [] + ) } func testDocLineCommentsAreNotNormalized() { - XCTAssertFormatting( + assertFormatting( UseTripleSlashForDocumentationComments.self, input: """ /// @@ -169,6 +182,8 @@ final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCas /// public class AClazz { } - """) + """, + findings: [] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift b/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift index d88153267..c4660673d 100644 --- a/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift @@ -1,89 +1,96 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class UseWhereClausesInForLoopsTests: LintOrFormatRuleTestCase { func testForLoopWhereClauses() { - XCTAssertFormatting( + assertFormatting( UseWhereClausesInForLoops.self, input: """ - for i in [0, 1, 2, 3] { - if i > 30 { - print(i) - } - } + for i in [0, 1, 2, 3] { + 1️⃣if i > 30 { + print(i) + } + } - for i in [0, 1, 2, 3] { - if i > 30 { - print(i) - } else { - print(i) - } - } + for i in [0, 1, 2, 3] { + if i > 30 { + print(i) + } else { + print(i) + } + } - for i in [0, 1, 2, 3] { - if i > 30 { - print(i) - } else if i > 40 { - print(i) - } - } + for i in [0, 1, 2, 3] { + if i > 30 { + print(i) + } else if i > 40 { + print(i) + } + } - for i in [0, 1, 2, 3] { - if i > 30 { - print(i) - } - print(i) - } + for i in [0, 1, 2, 3] { + if i > 30 { + print(i) + } + print(i) + } - for i in [0, 1, 2, 3] { - if let x = (2 as Int?) { - print(i) - } - } + for i in [0, 1, 2, 3] { + if let x = (2 as Int?) { + print(i) + } + } - for i in [0, 1, 2, 3] { - guard i > 30 else { - continue - } - print(i) - } - """, + for i in [0, 1, 2, 3] { + 2️⃣guard i > 30 else { + continue + } + print(i) + } + """, expected: """ - for i in [0, 1, 2, 3] where i > 30 { - print(i) - } + for i in [0, 1, 2, 3] where i > 30 { + print(i) + } - for i in [0, 1, 2, 3] { - if i > 30 { - print(i) - } else { - print(i) - } - } + for i in [0, 1, 2, 3] { + if i > 30 { + print(i) + } else { + print(i) + } + } - for i in [0, 1, 2, 3] { - if i > 30 { - print(i) - } else if i > 40 { - print(i) - } - } + for i in [0, 1, 2, 3] { + if i > 30 { + print(i) + } else if i > 40 { + print(i) + } + } - for i in [0, 1, 2, 3] { - if i > 30 { - print(i) - } - print(i) - } + for i in [0, 1, 2, 3] { + if i > 30 { + print(i) + } + print(i) + } - for i in [0, 1, 2, 3] { - if let x = (2 as Int?) { - print(i) - } - } + for i in [0, 1, 2, 3] { + if let x = (2 as Int?) { + print(i) + } + } - for i in [0, 1, 2, 3] where i > 30 { - print(i) - } - """) + for i in [0, 1, 2, 3] where i > 30 { + print(i) + } + """, + findings: [ + FindingSpec("1️⃣", message: "replace this 'if' statement with a 'where' clause"), + FindingSpec("2️⃣", message: "replace this 'guard' statement with a 'where' clause"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift index 659809473..2b1fcbeb2 100644 --- a/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift @@ -1,301 +1,270 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat +// FIXME: Diagnostics should be emitted inside the comment, not at the beginning of the declaration. final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { - override func setUp() { - super.setUp() - shouldCheckForUnassertedDiagnostics = true - } - func testParameterDocumentation() { - let input = - """ - /// Uses 'Parameters' when it only has one parameter. - /// - /// - Parameters: - /// - singular: singular description. - /// - Returns: A string containing the contents of a - /// description - func testPluralParamDesc(singular: String) -> Bool {} + assertLint( + ValidateDocumentationComments.self, + """ + /// Uses 'Parameters' when it only has one parameter. + /// + /// - Parameters: + /// - singular: singular description. + /// - Returns: A string containing the contents of a + /// description + 1️⃣func testPluralParamDesc(singular: String) -> Bool {} - /// Returns the output generated by executing a command with the given string - /// used as standard input. - /// - /// - Parameter command: The command to execute in the shell environment. - /// - Parameter stdin: The string to use as standard input. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - func testInvalidParameterDesc(command: String, stdin: String) -> String {} - """ - performLint(ValidateDocumentationComments.self, input: input) - XCTAssertDiagnosed(.useSingularParameter, line: 7, column: 1) - XCTAssertDiagnosed(.usePluralParameters, line: 16, column: 1) + /// Returns the output generated by executing a command with the given string + /// used as standard input. + /// + /// - Parameter command: The command to execute in the shell environment. + /// - Parameter stdin: The string to use as standard input. + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + 2️⃣func testInvalidParameterDesc(command: String, stdin: String) -> String {} + """, + findings: [ + FindingSpec("1️⃣", message: "replace the plural 'Parameters:' section with a singular inline 'Parameter' section"), + FindingSpec("2️⃣", message: "replace the singular inline 'Parameter' section with a plural 'Parameters:' section that has the parameters nested inside it"), + ] + ) } func testParametersName() { - let input = - """ - /// Parameters dont match. - /// - /// - Parameters: - /// - sum: The sum of all numbers. - /// - avg: The average of all numbers. - /// - Returns: The sum of sum and avg. - func sum(avg: Int, sum: Int) -> Int {} + assertLint( + ValidateDocumentationComments.self, + """ + /// Parameters dont match. + /// + /// - Parameters: + /// - sum: The sum of all numbers. + /// - avg: The average of all numbers. + /// - Returns: The sum of sum and avg. + 1️⃣func sum(avg: Int, sum: Int) -> Int {} - /// Missing one parameter documentation. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - Returns: an integer. - func foo(p1: Int, p2: Int, p3: Int) -> Int {} - """ - performLint(ValidateDocumentationComments.self, input: input) - XCTAssertDiagnosed(.parametersDontMatch(funcName: "sum"), line: 7, column: 1) - XCTAssertDiagnosed(.parametersDontMatch(funcName: "foo"), line: 15, column: 1) + /// Missing one parameter documentation. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - Returns: an integer. + 2️⃣func foo(p1: Int, p2: Int, p3: Int) -> Int {} + """, + findings: [ + FindingSpec("1️⃣", message: "change the parameters of the documentation of 'sum' to match its parameters"), + FindingSpec("2️⃣", message: "change the parameters of the documentation of 'foo' to match its parameters"), + ] + ) } func testThrowsDocumentation() { - let input = - """ - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - /// - Throws: an error. - func doesNotThrow(p1: Int, p2: Int, p3: Int) {} + assertLint( + ValidateDocumentationComments.self, + """ + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + /// - Throws: an error. + 1️⃣func doesNotThrow(p1: Int, p2: Int, p3: Int) {} - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - func doesThrow(p1: Int, p2: Int, p3: Int) throws {} + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + func doesThrow(p1: Int, p2: Int, p3: Int) 2️⃣throws {} - /// One sentence summary. - /// - /// - Parameter p1: Parameter 1. - /// - Throws: doesn't really throw, just rethrows - func doesRethrow(p1: (() throws -> ())) rethrows {} - """ - performLint(ValidateDocumentationComments.self, input: input) - XCTAssertDiagnosed(.removeThrowsComment(funcName: "doesNotThrow"), line: 8, column: 1) - XCTAssertDiagnosed(.documentErrorsThrown(funcName: "doesThrow"), line: 16, column: 43) - XCTAssertDiagnosed(.removeThrowsComment(funcName: "doesRethrow"), line: 22, column: 41) + /// One sentence summary. + /// + /// - Parameter p1: Parameter 1. + /// - Throws: doesn't really throw, just rethrows + func doesRethrow(p1: (() throws -> ())) 3️⃣rethrows {} + """, + findings: [ + FindingSpec("1️⃣", message: "remove the 'Throws:' sections of 'doesNotThrow'; it does not throw any errors"), + FindingSpec("2️⃣", message: "add a 'Throws:' section to document the errors thrown by 'doesThrow'"), + FindingSpec("3️⃣", message: "remove the 'Throws:' sections of 'doesRethrow'; it does not throw any errors"), + ] + ) } func testReturnDocumentation() { - let input = - """ - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - /// - Returns: an integer. - func noReturn(p1: Int, p2: Int, p3: Int) {} + assertLint( + ValidateDocumentationComments.self, + """ + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + /// - Returns: an integer. + 1️⃣func noReturn(p1: Int, p2: Int, p3: Int) {} - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - func foo(p1: Int, p2: Int, p3: Int) -> Int {} + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + func foo(p1: Int, p2: Int, p3: Int) 2️⃣-> Int {} - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - func neverReturns(p1: Int, p2: Int, p3: Int) -> Never {} + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + func neverReturns(p1: Int, p2: Int, p3: Int) -> Never {} - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - /// - Returns: Never returns. - func documentedNeverReturns(p1: Int, p2: Int, p3: Int) -> Never {} - """ - performLint(ValidateDocumentationComments.self, input: input) - XCTAssertDiagnosed(.removeReturnComment(funcName: "noReturn"), line: 8, column: 1) - XCTAssertDiagnosed(.documentReturnValue(funcName: "foo"), line: 16, column: 37) - XCTAssertNotDiagnosed(.documentReturnValue(funcName: "neverReturns")) - XCTAssertNotDiagnosed(.removeReturnComment(funcName: "documentedNeverReturns")) + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + /// - Returns: Never returns. + func documentedNeverReturns(p1: Int, p2: Int, p3: Int) -> Never {} + """, + findings: [ + FindingSpec("1️⃣", message: "remove the 'Returns:' section of 'noReturn'; it does not return a value"), + FindingSpec("2️⃣", message: "add a 'Returns:' section to document the return value of 'foo'"), + ] + ) } func testValidDocumentation() { - let input = - """ - /// Returns the output generated by executing a command. - /// - /// - Parameter command: The command to execute in the shell environment. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - func singularParam(command: String) -> String { - // ... - } - - /// Returns the output generated by executing a command with the given string - /// used as standard input. - /// - /// - Parameters: - /// - command: The command to execute in the shell environment. - /// - stdin: The string to use as standard input. - /// - Throws: An error, possibly. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - func pluralParam(command: String, stdin: String) throws -> String { - // ... - } - - /// One sentence summary. - /// - /// - Parameter p1: Parameter 1. - func rethrower(p1: (() throws -> ())) rethrows { - // ... - } - - /// Parameter(s) and Returns tags may be omitted only if the single-sentence - /// brief summary fully describes the meaning of those items and including the - /// tags would only repeat what has already been said - func omittedFunc(p1: Int) - """ - performLint(ValidateDocumentationComments.self, input: input) - XCTAssertNotDiagnosed(.useSingularParameter) - XCTAssertNotDiagnosed(.usePluralParameters) - - XCTAssertNotDiagnosed(.documentReturnValue(funcName: "singularParam")) - XCTAssertNotDiagnosed(.removeReturnComment(funcName: "singularParam")) - XCTAssertNotDiagnosed(.parametersDontMatch(funcName: "singularParam")) + assertLint( + ValidateDocumentationComments.self, + """ + /// Returns the output generated by executing a command. + /// + /// - Parameter command: The command to execute in the shell environment. + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func singularParam(command: String) -> String { + // ... + } - XCTAssertNotDiagnosed(.documentReturnValue(funcName: "pluralParam")) - XCTAssertNotDiagnosed(.removeReturnComment(funcName: "pluralParam")) - XCTAssertNotDiagnosed(.parametersDontMatch(funcName: "pluralParam")) - XCTAssertNotDiagnosed(.documentErrorsThrown(funcName: "pluralParam")) - XCTAssertNotDiagnosed(.removeThrowsComment(funcName: "pluralParam")) + /// Returns the output generated by executing a command with the given string + /// used as standard input. + /// + /// - Parameters: + /// - command: The command to execute in the shell environment. + /// - stdin: The string to use as standard input. + /// - Throws: An error, possibly. + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func pluralParam(command: String, stdin: String) throws -> String { + // ... + } - XCTAssertNotDiagnosed(.documentErrorsThrown(funcName: "pluralParam")) - XCTAssertNotDiagnosed(.removeThrowsComment(funcName: "pluralParam")) + /// One sentence summary. + /// + /// - Parameter p1: Parameter 1. + func rethrower(p1: (() throws -> ())) rethrows { + // ... + } - XCTAssertNotDiagnosed(.documentReturnValue(funcName: "omittedFunc")) - XCTAssertNotDiagnosed(.removeReturnComment(funcName: "omittedFunc")) - XCTAssertNotDiagnosed(.parametersDontMatch(funcName: "omittedFunc")) + /// Parameter(s) and Returns tags may be omitted only if the single-sentence + /// brief summary fully describes the meaning of those items and including the + /// tags would only repeat what has already been said + func omittedFunc(p1: Int) + """, + findings: [] + ) } func testSeparateLabelAndIdentifier() { - let input = - """ - /// Returns the output generated by executing a command. - /// - /// - Parameter command: The command to execute in the shell environment. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - func incorrectParam(label commando: String) -> String { - // ... - } - - /// Returns the output generated by executing a command. - /// - /// - Parameter command: The command to execute in the shell environment. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - func singularParam(label command: String) -> String { - // ... - } - - /// Returns the output generated by executing a command with the given string - /// used as standard input. - /// - /// - Parameters: - /// - command: The command to execute in the shell environment. - /// - stdin: The string to use as standard input. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - func pluralParam(label command: String, label2 stdin: String) -> String { - // ... - } - """ - performLint(ValidateDocumentationComments.self, input: input) - XCTAssertNotDiagnosed(.useSingularParameter) - XCTAssertNotDiagnosed(.usePluralParameters) - - XCTAssertDiagnosed(.parametersDontMatch(funcName: "incorrectParam"), line: 6, column: 1) - - XCTAssertNotDiagnosed(.documentReturnValue(funcName: "singularParam")) - XCTAssertNotDiagnosed(.removeReturnComment(funcName: "singularParam")) - XCTAssertNotDiagnosed(.parametersDontMatch(funcName: "singularParam")) - - XCTAssertNotDiagnosed(.documentReturnValue(funcName: "pluralParam")) - XCTAssertNotDiagnosed(.removeReturnComment(funcName: "pluralParam")) - XCTAssertNotDiagnosed(.parametersDontMatch(funcName: "pluralParam")) - } - - func testInitializer() { - let input = - """ - struct SomeType { - /// Brief summary. + assertLint( + ValidateDocumentationComments.self, + """ + /// Returns the output generated by executing a command. /// /// - Parameter command: The command to execute in the shell environment. - /// - Returns: Shouldn't be here. - init(label commando: String) { + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + 1️⃣func incorrectParam(label commando: String) -> String { // ... } - /// Brief summary. + /// Returns the output generated by executing a command. /// /// - Parameter command: The command to execute in the shell environment. - init(label command: String) { + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func singularParam(label command: String) -> String { // ... } - /// Brief summary. + /// Returns the output generated by executing a command with the given string + /// used as standard input. /// /// - Parameters: /// - command: The command to execute in the shell environment. /// - stdin: The string to use as standard input. - init(label command: String, label2 stdin: String) { + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func pluralParam(label command: String, label2 stdin: String) -> String { // ... } + """, + findings: [ + FindingSpec("1️⃣", message: "change the parameters of the documentation of 'incorrectParam' to match its parameters"), + ] + ) + } - /// Brief summary. - /// - /// - Parameters: - /// - command: The command to execute in the shell environment. - /// - stdin: The string to use as standard input. - /// - Throws: An error. - init(label command: String, label2 stdin: String) throws { - // ... + func testInitializer() { + assertLint( + ValidateDocumentationComments.self, + """ + struct SomeType { + /// Brief summary. + /// + /// - Parameter command: The command to execute in the shell environment. + /// - Returns: Shouldn't be here. + 1️⃣2️⃣init(label commando: String) { + // ... + } + + /// Brief summary. + /// + /// - Parameter command: The command to execute in the shell environment. + init(label command: String) { + // ... + } + + /// Brief summary. + /// + /// - Parameters: + /// - command: The command to execute in the shell environment. + /// - stdin: The string to use as standard input. + init(label command: String, label2 stdin: String) { + // ... + } + + /// Brief summary. + /// + /// - Parameters: + /// - command: The command to execute in the shell environment. + /// - stdin: The string to use as standard input. + /// - Throws: An error. + init(label command: String, label2 stdin: String) throws { + // ... + } } - } - """ - performLint(ValidateDocumentationComments.self, input: input) - XCTAssertNotDiagnosed(.useSingularParameter) - XCTAssertNotDiagnosed(.usePluralParameters) - - XCTAssertDiagnosed(.parametersDontMatch(funcName: "init"), line: 6, column: 3) - XCTAssertDiagnosed(.removeReturnComment(funcName: "init"), line: 6, column: 3) - - XCTAssertNotDiagnosed(.documentReturnValue(funcName: "init")) - XCTAssertNotDiagnosed(.removeReturnComment(funcName: "init")) - XCTAssertNotDiagnosed(.parametersDontMatch(funcName: "init")) - - XCTAssertNotDiagnosed(.documentReturnValue(funcName: "init")) - XCTAssertNotDiagnosed(.removeReturnComment(funcName: "init")) - XCTAssertNotDiagnosed(.parametersDontMatch(funcName: "init")) - - XCTAssertNotDiagnosed(.documentReturnValue(funcName: "init")) - XCTAssertNotDiagnosed(.removeReturnComment(funcName: "init")) - XCTAssertNotDiagnosed(.parametersDontMatch(funcName: "init")) - XCTAssertNotDiagnosed(.documentErrorsThrown(funcName: "init")) - XCTAssertNotDiagnosed(.removeThrowsComment(funcName: "init")) + """, + findings: [ + FindingSpec("1️⃣", message: "remove the 'Returns:' section of 'init'; it does not return a value"), + FindingSpec("2️⃣", message: "change the parameters of the documentation of 'init' to match its parameters"), + ] + ) } } From a6041ff96a0f48227fe3ebd25e0b817e59eb66c5 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 17 Aug 2023 10:04:13 -0700 Subject: [PATCH 099/332] [Lint/Format] Add a rule to detect extraneous return statements in function bodies --- .../Core/Pipelines+Generated.swift | 2 + .../Core/RuleNameCache+Generated.swift | 1 + Sources/SwiftFormat/Rules/OmitReturns.swift | 64 +++++++++++++++++++ .../RuleRegistry+Generated.swift | 1 + .../Rules/OmitReturnsTests.swift | 18 ++++++ 5 files changed, 86 insertions(+) create mode 100644 Sources/SwiftFormat/Rules/OmitReturns.swift create mode 100644 Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 2516f874b..832c66237 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -146,6 +146,7 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(OmitReturns.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) visitIfEnabled(ValidateDocumentationComments.visit, for: node) return .visitChildren @@ -343,6 +344,7 @@ extension FormatPipeline { node = NoLabelsInCasePatterns(context: context).rewrite(node) node = NoParensAroundConditions(context: context).rewrite(node) node = NoVoidReturnOnFunctionSignature(context: context).rewrite(node) + node = OmitReturns(context: context).rewrite(node) node = OneCasePerLine(context: context).rewrite(node) node = OneVariableDeclarationPerLine(context: context).rewrite(node) node = OrderedImports(context: context).rewrite(node) diff --git a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift index 2e8f3825a..62c6d3cb7 100644 --- a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift @@ -37,6 +37,7 @@ public let ruleNameCache: [ObjectIdentifier: String] = [ ObjectIdentifier(NoLeadingUnderscores.self): "NoLeadingUnderscores", ObjectIdentifier(NoParensAroundConditions.self): "NoParensAroundConditions", ObjectIdentifier(NoVoidReturnOnFunctionSignature.self): "NoVoidReturnOnFunctionSignature", + ObjectIdentifier(OmitReturns.self): "OmitReturns", ObjectIdentifier(OneCasePerLine.self): "OneCasePerLine", ObjectIdentifier(OneVariableDeclarationPerLine.self): "OneVariableDeclarationPerLine", ObjectIdentifier(OnlyOneTrailingClosureArgument.self): "OnlyOneTrailingClosureArgument", diff --git a/Sources/SwiftFormat/Rules/OmitReturns.swift b/Sources/SwiftFormat/Rules/OmitReturns.swift new file mode 100644 index 000000000..4e4148ee6 --- /dev/null +++ b/Sources/SwiftFormat/Rules/OmitReturns.swift @@ -0,0 +1,64 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// Single-expression functions, closures, subscripts can omit `return` statement. +/// +/// Lint: `func () { return ... }` and similar single expression constructs will yield a lint error. +/// +/// Format: `func () { return ... }` constructs will be replaced with +/// equivalent `func () { ... }` constructs. +@_spi(Rules) +public final class OmitReturns: SyntaxFormatRule { + public override class var isOptIn: Bool { return true } + + public override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax { + let decl = super.visit(node) + + // func () -> { return ... } + if var funcDecl = decl.as(FunctionDeclSyntax.self), + let body = funcDecl.body, + let `return` = containsSingleReturn(body.statements) { + funcDecl.body?.statements = unwrapReturnStmt(`return`) + diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) + return DeclSyntax(funcDecl) + } + + return decl + } + + private func containsSingleReturn(_ body: CodeBlockItemListSyntax) -> ReturnStmtSyntax? { + if let element = body.firstAndOnly?.as(CodeBlockItemSyntax.self), + let ret = element.item.as(ReturnStmtSyntax.self), + !ret.children(viewMode: .all).isEmpty, ret.expression != nil { + return ret + } + + return nil + } + + private func unwrapReturnStmt(_ `return`: ReturnStmtSyntax) -> CodeBlockItemListSyntax { + CodeBlockItemListSyntax([ + CodeBlockItemSyntax( + leadingTrivia: `return`.leadingTrivia, + item: .expr(`return`.expression!), + semicolon: nil, + trailingTrivia: `return`.trailingTrivia) + ]) + } +} + +extension Finding.Message { + public static let omitReturnStatement: Finding.Message = + "`return` can be omitted because body consists of a single expression" +} diff --git a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift index b234d90bd..f17ff0c9f 100644 --- a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift @@ -36,6 +36,7 @@ enum RuleRegistry { "NoLeadingUnderscores": false, "NoParensAroundConditions": true, "NoVoidReturnOnFunctionSignature": true, + "OmitReturns": false, "OneCasePerLine": true, "OneVariableDeclarationPerLine": true, "OnlyOneTrailingClosureArgument": true, diff --git a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift new file mode 100644 index 000000000..6f9f181bd --- /dev/null +++ b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift @@ -0,0 +1,18 @@ +@_spi(Rules) import SwiftFormat + +final class OmitReturnsTests: LintOrFormatRuleTestCase { + func testOmitReturnInFunction() { + XCTAssertFormatting( + OmitReturns.self, + input: """ + func test() -> Bool { + return false + } + """, + expected: """ + func test() -> Bool { + false + } + """) + } +} From ed1c8a6442637a186a62dca207f224d33c6b3ead Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 18 Aug 2023 17:37:50 -0700 Subject: [PATCH 100/332] [Lint/Format] Extend return omission rule to include closures --- .../SwiftFormat/Core/Pipelines+Generated.swift | 5 +++++ Sources/SwiftFormat/Rules/OmitReturns.swift | 14 ++++++++++++++ .../SwiftFormatTests/Rules/OmitReturnsTests.swift | 15 +++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 832c66237..a09c3da10 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -60,6 +60,11 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } + override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(OmitReturns.visit, for: node) + return .visitChildren + } + override func visit(_ node: ClosureParameterSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren diff --git a/Sources/SwiftFormat/Rules/OmitReturns.swift b/Sources/SwiftFormat/Rules/OmitReturns.swift index 4e4148ee6..b34a1b1f6 100644 --- a/Sources/SwiftFormat/Rules/OmitReturns.swift +++ b/Sources/SwiftFormat/Rules/OmitReturns.swift @@ -37,6 +37,20 @@ public final class OmitReturns: SyntaxFormatRule { return decl } + public override func visit(_ node: ClosureExprSyntax) -> ExprSyntax { + let expr = super.visit(node) + + // test { return ... } + if var closure = expr.as(ClosureExprSyntax.self), + let `return` = containsSingleReturn(closure.statements) { + closure.statements = unwrapReturnStmt(`return`) + diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) + return ExprSyntax(closure) + } + + return expr + } + private func containsSingleReturn(_ body: CodeBlockItemListSyntax) -> ReturnStmtSyntax? { if let element = body.firstAndOnly?.as(CodeBlockItemSyntax.self), let ret = element.item.as(ReturnStmtSyntax.self), diff --git a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift index 6f9f181bd..652942f88 100644 --- a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift @@ -15,4 +15,19 @@ final class OmitReturnsTests: LintOrFormatRuleTestCase { } """) } + + func testOmitReturnInClosure() { + XCTAssertFormatting( + OmitReturns.self, + input: """ + vals.filter { + return $0.count == 1 + } + """, + expected: """ + vals.filter { + $0.count == 1 + } + """) + } } From e5fce04e417eb44593f275aca2fe4b95e60309dc Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Mon, 21 Aug 2023 11:43:05 -0700 Subject: [PATCH 101/332] [Lint/Format] Extend return omission rule to include subscripts --- .../Core/Pipelines+Generated.swift | 1 + Sources/SwiftFormat/Rules/OmitReturns.swift | 63 +++++++++++++++++++ .../Rules/OmitReturnsTests.swift | 42 +++++++++++++ 3 files changed, 106 insertions(+) diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index a09c3da10..f608d458c 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -281,6 +281,7 @@ class LintPipeline: SyntaxVisitor { override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(OmitReturns.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } diff --git a/Sources/SwiftFormat/Rules/OmitReturns.swift b/Sources/SwiftFormat/Rules/OmitReturns.swift index b34a1b1f6..66a23fb23 100644 --- a/Sources/SwiftFormat/Rules/OmitReturns.swift +++ b/Sources/SwiftFormat/Rules/OmitReturns.swift @@ -37,6 +37,69 @@ public final class OmitReturns: SyntaxFormatRule { return decl } + public override func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax { + let decl = super.visit(node) + + guard var `subscript` = decl.as(SubscriptDeclSyntax.self) else { + return decl + } + + if let accessorBlock = `subscript`.accessorBlock { + // We are assuming valid Swift code here where only + // one `get { ... }` is allowed. + switch accessorBlock.accessors { + case .accessors(let accessors): + guard var getter = accessors.filter({ + $0.accessorSpecifier.tokenKind == .keyword(.get) + }).first else { + return decl + } + + guard let body = getter.body, + let `return` = containsSingleReturn(body.statements) else { + return decl + } + + guard let getterAt = accessors.firstIndex(where: { + $0.accessorSpecifier.tokenKind == .keyword(.get) + }) else { + return decl + } + + getter.body?.statements = unwrapReturnStmt(`return`) + + `subscript`.accessorBlock = .init( + leadingTrivia: accessorBlock.leadingTrivia, + leftBrace: accessorBlock.leftBrace, + accessors: .accessors(accessors.with(\.[getterAt], getter)), + rightBrace: accessorBlock.rightBrace, + trailingTrivia: accessorBlock.trailingTrivia) + + diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) + + return DeclSyntax(`subscript`) + + case .getter(let getter): + guard let `return` = containsSingleReturn(getter) else { + return decl + } + + `subscript`.accessorBlock = .init( + leadingTrivia: accessorBlock.leadingTrivia, + leftBrace: accessorBlock.leftBrace, + accessors: .getter(unwrapReturnStmt(`return`)), + rightBrace: accessorBlock.rightBrace, + trailingTrivia: accessorBlock.trailingTrivia) + + diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) + + return DeclSyntax(`subscript`) + } + } + + return decl + } + public override func visit(_ node: ClosureExprSyntax) -> ExprSyntax { let expr = super.visit(node) diff --git a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift index 652942f88..7ae9b3784 100644 --- a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift @@ -30,4 +30,46 @@ final class OmitReturnsTests: LintOrFormatRuleTestCase { } """) } + + func testOmitReturnInSubscript() { + XCTAssertFormatting( + OmitReturns.self, + input: """ + struct Test { + subscript(x: Int) -> Bool { + return false + } + } + """, + expected: """ + struct Test { + subscript(x: Int) -> Bool { + false + } + } + """) + + XCTAssertFormatting( + OmitReturns.self, + input: """ + struct Test { + subscript(x: Int) -> Bool { + get { + return false + } + set { } + } + } + """, + expected: """ + struct Test { + subscript(x: Int) -> Bool { + get { + false + } + set { } + } + } + """) + } } From a13a52f1b6f3a0df40efecef167cd99079a218fc Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Mon, 21 Aug 2023 11:54:51 -0700 Subject: [PATCH 102/332] [Lint/Format] Extend return omission rule to include computed variables --- .../Core/Pipelines+Generated.swift | 1 + Sources/SwiftFormat/Rules/OmitReturns.swift | 118 ++++++++++-------- .../Rules/OmitReturnsTests.swift | 38 ++++++ 3 files changed, 106 insertions(+), 51 deletions(-) diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index f608d458c..b63b6a1a8 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -232,6 +232,7 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: PatternBindingSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(OmitReturns.visit, for: node) visitIfEnabled(UseSingleLinePropertyGetter.visit, for: node) return .visitChildren } diff --git a/Sources/SwiftFormat/Rules/OmitReturns.swift b/Sources/SwiftFormat/Rules/OmitReturns.swift index 66a23fb23..e1c295f14 100644 --- a/Sources/SwiftFormat/Rules/OmitReturns.swift +++ b/Sources/SwiftFormat/Rules/OmitReturns.swift @@ -44,62 +44,29 @@ public final class OmitReturns: SyntaxFormatRule { return decl } - if let accessorBlock = `subscript`.accessorBlock { - // We are assuming valid Swift code here where only - // one `get { ... }` is allowed. - switch accessorBlock.accessors { - case .accessors(let accessors): - guard var getter = accessors.filter({ - $0.accessorSpecifier.tokenKind == .keyword(.get) - }).first else { - return decl - } - - guard let body = getter.body, - let `return` = containsSingleReturn(body.statements) else { - return decl - } - - guard let getterAt = accessors.firstIndex(where: { - $0.accessorSpecifier.tokenKind == .keyword(.get) - }) else { - return decl - } - - getter.body?.statements = unwrapReturnStmt(`return`) - - `subscript`.accessorBlock = .init( - leadingTrivia: accessorBlock.leadingTrivia, - leftBrace: accessorBlock.leftBrace, - accessors: .accessors(accessors.with(\.[getterAt], getter)), - rightBrace: accessorBlock.rightBrace, - trailingTrivia: accessorBlock.trailingTrivia) - - diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) - - return DeclSyntax(`subscript`) - - case .getter(let getter): - guard let `return` = containsSingleReturn(getter) else { - return decl - } - - `subscript`.accessorBlock = .init( - leadingTrivia: accessorBlock.leadingTrivia, - leftBrace: accessorBlock.leftBrace, - accessors: .getter(unwrapReturnStmt(`return`)), - rightBrace: accessorBlock.rightBrace, - trailingTrivia: accessorBlock.trailingTrivia) - - diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) - - return DeclSyntax(`subscript`) - } + if let accessorBlock = `subscript`.accessorBlock, + // We are assuming valid Swift code here where only + // one `get { ... }` is allowed. + let transformed = transformAccessorBlock(accessorBlock) { + `subscript`.accessorBlock = transformed + return DeclSyntax(`subscript`) } return decl } + public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax { + var binding = node + + if let accessorBlock = binding.accessorBlock, + let transformed = transformAccessorBlock(accessorBlock) { + binding.accessorBlock = transformed + return binding + } + + return node + } + public override func visit(_ node: ClosureExprSyntax) -> ExprSyntax { let expr = super.visit(node) @@ -114,6 +81,55 @@ public final class OmitReturns: SyntaxFormatRule { return expr } + private func transformAccessorBlock(_ accessorBlock: AccessorBlockSyntax) -> AccessorBlockSyntax? { + // We are assuming valid Swift code here where only + // one `get { ... }` is allowed. + switch accessorBlock.accessors { + case .accessors(let accessors): + guard var getter = accessors.filter({ + $0.accessorSpecifier.tokenKind == .keyword(.get) + }).first else { + return nil + } + + guard let body = getter.body, + let `return` = containsSingleReturn(body.statements) else { + return nil + } + + guard let getterAt = accessors.firstIndex(where: { + $0.accessorSpecifier.tokenKind == .keyword(.get) + }) else { + return nil + } + + getter.body?.statements = unwrapReturnStmt(`return`) + + diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) + + return .init( + leadingTrivia: accessorBlock.leadingTrivia, + leftBrace: accessorBlock.leftBrace, + accessors: .accessors(accessors.with(\.[getterAt], getter)), + rightBrace: accessorBlock.rightBrace, + trailingTrivia: accessorBlock.trailingTrivia) + + case .getter(let getter): + guard let `return` = containsSingleReturn(getter) else { + return nil + } + + diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) + + return .init( + leadingTrivia: accessorBlock.leadingTrivia, + leftBrace: accessorBlock.leftBrace, + accessors: .getter(unwrapReturnStmt(`return`)), + rightBrace: accessorBlock.rightBrace, + trailingTrivia: accessorBlock.trailingTrivia) + } + } + private func containsSingleReturn(_ body: CodeBlockItemListSyntax) -> ReturnStmtSyntax? { if let element = body.firstAndOnly?.as(CodeBlockItemSyntax.self), let ret = element.item.as(ReturnStmtSyntax.self), diff --git a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift index 7ae9b3784..888eab448 100644 --- a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift @@ -72,4 +72,42 @@ final class OmitReturnsTests: LintOrFormatRuleTestCase { } """) } + + func testOmitReturnInComputedVars() { + XCTAssertFormatting( + OmitReturns.self, + input: """ + var x: Int { + return 42 + } + """, + expected: """ + var x: Int { + 42 + } + """) + + XCTAssertFormatting( + OmitReturns.self, + input: """ + struct Test { + var x: Int { + get { + return 42 + } + set { } + } + } + """, + expected: """ + struct Test { + var x: Int { + get { + 42 + } + set { } + } + } + """) + } } From 9e5ece1231f5e630392543ac6c5c33d5bdf4bbaf Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 21 Aug 2023 15:00:02 -0400 Subject: [PATCH 103/332] Fix a bunch of FIXMEs around linter findings. - Whitespace linter: Fix improper pluralization of "add 1 line breaks". - `DoNotUseSemicolons`: Only show the part of the message about moving the next statement to a new line if a statement actually follows the semicolon on the same line. - `DontRepeatTypeInStaticProperties`: Place the finding on the identifier, not at the start of the decl. - `FullyIndirectEnum`: Move the finding to the `enum` keyword and add notes to each `indirect` keyword that should be removed. - `GroupNumericLiterals`: Clean up the finding message and include the numeric base of the literal. - `NoAccessLevelOnExtensionDeclaration`: Add notes for each extension declaration that needs to have the access modifier added to it. - `NoCasesWithOnlyFallthrough`: Put findings at the right locations. - `NoEmptyTrailingClosureParentheses`: Place the finding on the parentheses themselves. - `NoParensAroundConditions`: Place the finding on the parentheses themselves. - `TypeNamesShouldBeCapitalized`: Place the finding on the name instead of the declaration and clean up the message to make it more consistent with other rules. - `UseEarlyExits`: Put findings at the right locations. - `UseTripleSlashForDocumentationComments`: Place the finding on the comment, not on the declaration. --- .../PrettyPrint/WhitespaceLinter.swift | 5 +- .../Rules/DoNotUseSemicolons.swift | 14 +- .../DontRepeatTypeInStaticProperties.swift | 2 +- .../SwiftFormat/Rules/FullyIndirectEnum.swift | 32 +++- .../Rules/GroupNumericLiterals.swift | 11 +- .../NoAccessLevelOnExtensionDeclaration.swift | 55 ++++-- .../Rules/NoCasesWithOnlyFallthrough.swift | 15 +- .../NoEmptyTrailingClosureParentheses.swift | 10 +- .../Rules/NoParensAroundConditions.swift | 3 +- .../Rules/TypeNamesShouldBeCapitalized.swift | 26 +-- Sources/SwiftFormat/Rules/UseEarlyExits.swift | 66 +++---- ...eTripleSlashForDocumentationComments.swift | 2 + .../Rules/UseWhereClausesInForLoops.swift | 2 +- .../PrettyPrint/WhitespaceLintTests.swift | 9 +- .../Rules/DoNotUseSemicolonsTests.swift | 17 +- ...ontRepeatTypeInStaticPropertiesTests.swift | 19 +- .../Rules/FullyIndirectEnumTests.swift | 39 +++-- .../Rules/GroupNumericLiteralsTests.swift | 20 +-- ...cessLevelOnExtensionDeclarationTests.swift | 162 ++++++++++++------ .../NoCasesWithOnlyFallthroughTests.swift | 3 +- ...EmptyTrailingClosureParenthesesTests.swift | 21 ++- .../Rules/NoParensAroundConditionsTests.swift | 59 ++++--- .../TypeNamesShouldBeCapitalizedTests.swift | 73 ++++---- .../Rules/UseEarlyExitsTests.swift | 27 ++- ...leSlashForDocumentationCommentsTests.swift | 19 +- 25 files changed, 413 insertions(+), 298 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift index 2c5dcb30a..2afb0e5f6 100644 --- a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift +++ b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift @@ -481,7 +481,10 @@ extension Finding.Message { public static let removeLineError: Finding.Message = "remove line break" - public static func addLinesError(_ lines: Int) -> Finding.Message { "add \(lines) line breaks" } + public static func addLinesError(_ lines: Int) -> Finding.Message { + let noun = lines == 1 ? "break" : "breaks" + return "add \(lines) line \(noun)" + } public static let lineLengthError: Finding.Message = "line is too long" } diff --git a/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift index 4bef9c4c2..3d5fd530c 100644 --- a/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift @@ -80,10 +80,18 @@ public final class DoNotUseSemicolons: SyntaxFormatRule { } } - // This discards any trailingTrivia from the semicolon. That trivia is at most some spaces, - // and the pretty printer adds any necessary spaces so it's safe to discard. + // This discards any trailing trivia from the semicolon. That trivia will only be horizontal + // whitespace, and the pretty printer adds any necessary spaces so it's safe to discard. + // TODO: When we stop using the legacy trivia transform, we need to fix this to preserve + // trailing comments. newItem = newItem.with(\.semicolon, nil) - if idx < node.count - 1 { + + // When emitting the finding, tell the user to move the next statement down if there is + // another statement following this one. Otherwise, just tell them to remove the semicolon. + if let nextToken = semicolon.nextToken(viewMode: .sourceAccurate), + nextToken.tokenKind != .rightBrace && nextToken.tokenKind != .endOfFile + && !nextToken.leadingTrivia.containsNewlines + { diagnose(.removeSemicolonAndMove, on: semicolon) } else { diagnose(.removeSemicolon, on: semicolon) diff --git a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift index eca677c55..8a765876d 100644 --- a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift @@ -77,7 +77,7 @@ public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { for pattern in varDecl.identifiers { let varName = pattern.identifier.text if varName.contains(bareTypeName) { - diagnose(.removeTypeFromName(name: varName, type: bareTypeName), on: varDecl) + diagnose(.removeTypeFromName(name: varName, type: bareTypeName), on: pattern.identifier) } } } diff --git a/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift index 3e793e2a8..af190f1d7 100644 --- a/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift @@ -25,12 +25,20 @@ public final class FullyIndirectEnum: SyntaxFormatRule { public override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { let enumMembers = node.memberBlock.members guard !node.modifiers.has(modifier: "indirect"), - allCasesAreIndirect(in: enumMembers) + case let indirectModifiers = indirectModifiersIfAllCasesIndirect(in: enumMembers), + !indirectModifiers.isEmpty else { return DeclSyntax(node) } - diagnose(.moveIndirectKeywordToEnumDecl(name: node.name.text), on: node.name) + let notes = indirectModifiers.map { modifier in + Finding.Note( + message: .removeIndirect, + location: Finding.Location( + modifier.startLocation(converter: self.context.sourceLocationConverter))) + } + diagnose( + .moveIndirectKeywordToEnumDecl(name: node.name.text), on: node.enumKeyword, notes: notes) // Removes 'indirect' keyword from cases, reformats let newMembers = enumMembers.map { @@ -75,17 +83,21 @@ public final class FullyIndirectEnum: SyntaxFormatRule { /// Returns a value indicating whether all enum cases in the given list are indirect. /// /// Note that if the enum has no cases, this returns false. - private func allCasesAreIndirect(in members: MemberBlockItemListSyntax) -> Bool { - var hadCases = false + private func indirectModifiersIfAllCasesIndirect(in members: MemberBlockItemListSyntax) + -> [DeclModifierSyntax] + { + var indirectModifiers = [DeclModifierSyntax]() for member in members { if let caseMember = member.decl.as(EnumCaseDeclSyntax.self) { - hadCases = true - guard caseMember.modifiers.has(modifier: "indirect") else { - return false + guard let indirectModifier = caseMember.modifiers.first( + where: { $0.name.text == "indirect" } + ) else { + return [] } + indirectModifiers.append(indirectModifier) } } - return hadCases + return indirectModifiers } /// Transfers given leading trivia to the first token in the case declaration. @@ -112,6 +124,8 @@ public final class FullyIndirectEnum: SyntaxFormatRule { extension Finding.Message { @_spi(Rules) public static func moveIndirectKeywordToEnumDecl(name: String) -> Finding.Message { - "move 'indirect' before the enum declaration '\(name)' when all cases are indirect" + "declare enum '\(name)' itself as indirect when all cases are indirect" } + + public static let removeIndirect: Finding.Message = "remove 'indirect' here" } diff --git a/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift b/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift index 555b3c75a..ad0d3f0dd 100644 --- a/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift +++ b/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift @@ -40,13 +40,13 @@ public final class GroupNumericLiterals: SyntaxFormatRule { // Hexadecimal let digitsNoPrefix = String(originalDigits.dropFirst(2)) guard digitsNoPrefix.count >= 8 else { return ExprSyntax(node) } - diagnose(.groupNumericLiteral(every: 4), on: node) + diagnose(.groupNumericLiteral(every: 4, base: "hexadecimal"), on: node) newDigits = "0x" + digits(digitsNoPrefix, groupedEvery: 4) case "0b": // Binary let digitsNoPrefix = String(originalDigits.dropFirst(2)) guard digitsNoPrefix.count >= 10 else { return ExprSyntax(node) } - diagnose(.groupNumericLiteral(every: 8), on: node) + diagnose(.groupNumericLiteral(every: 8, base: "binary"), on: node) newDigits = "0b" + digits(digitsNoPrefix, groupedEvery: 8) case "0o": // Octal @@ -54,7 +54,7 @@ public final class GroupNumericLiterals: SyntaxFormatRule { default: // Decimal guard originalDigits.count >= 7 else { return ExprSyntax(node) } - diagnose(.groupNumericLiteral(every: 3), on: node) + diagnose(.groupNumericLiteral(every: 3, base: "decimal"), on: node) newDigits = digits(originalDigits, groupedEvery: 3) } @@ -84,8 +84,7 @@ public final class GroupNumericLiterals: SyntaxFormatRule { extension Finding.Message { @_spi(Rules) - public static func groupNumericLiteral(every stride: Int) -> Finding.Message { - let ending = stride == 3 ? "rd" : "th" - return "group numeric literal using '_' every \(stride)\(ending) number" + public static func groupNumericLiteral(every stride: Int, base: String) -> Finding.Message { + return "group every \(stride) digits in this \(base) literal using a '_' separator" } } diff --git a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift index b86124d0e..e1dac51b2 100644 --- a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift @@ -19,11 +19,8 @@ import SwiftSyntax /// Format: The access level is removed from the extension declaration and is added to each /// declaration in the extension; declarations with redundant access levels (e.g. /// `internal`, as that is the default access level) have the explicit access level removed. -/// -/// TODO: Find a better way to access modifiers and keyword tokens besides casting each declaration @_spi(Rules) public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { - public override func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax { guard !node.modifiers.isEmpty else { return DeclSyntax(node) } guard let accessKeyword = node.modifiers.accessLevelModifier else { return DeclSyntax(node) } @@ -32,21 +29,26 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { switch keywordKind { // Public, private, or fileprivate keywords need to be moved to members case .keyword(.public), .keyword(.private), .keyword(.fileprivate): - diagnose(.moveAccessKeyword(keyword: accessKeyword.name.text), on: accessKeyword) - // 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. let accessKeywordToAdd: DeclModifierSyntax + let message: Finding.Message if keywordKind == .keyword(.private) { accessKeywordToAdd = accessKeyword.with(\.name, accessKeyword.name.with(\.tokenKind, .keyword(.fileprivate))) + message = .moveAccessKeywordAndMakeFileprivate(keyword: accessKeyword.name.text) } else { accessKeywordToAdd = accessKeyword + message = .moveAccessKeyword(keyword: accessKeyword.name.text) } + let (newMemberBlock, notes) = addMemberAccessKeywords( + memDeclBlock: node.memberBlock, keyword: accessKeywordToAdd) + diagnose(message, on: accessKeyword, notes: notes) + let newMembers = MemberBlockSyntax( leftBrace: node.memberBlock.leftBrace, - members: addMemberAccessKeywords(memDeclBlock: node.memberBlock, keyword: accessKeywordToAdd), + members: newMemberBlock, rightBrace: node.memberBlock.rightBrace) var newKeyword = node.extensionKeyword newKeyword.leadingTrivia = accessKeyword.leadingTrivia @@ -57,9 +59,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { // Internal keyword redundant, delete case .keyword(.internal): - diagnose( - .removeRedundantAccessKeyword(name: node.extendedType.description), - on: accessKeyword) + diagnose(.removeRedundantAccessKeyword, on: accessKeyword) var newKeyword = node.extensionKeyword newKeyword.leadingTrivia = accessKeyword.leadingTrivia let result = node.with(\.modifiers, node.modifiers.remove(name: accessKeyword.name.text)) @@ -76,8 +76,9 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { private func addMemberAccessKeywords( memDeclBlock: MemberBlockSyntax, keyword: DeclModifierSyntax - ) -> MemberBlockItemListSyntax { + ) -> (MemberBlockItemListSyntax, [Finding.Note]) { var newMembers: [MemberBlockItemSyntax] = [] + var notes: [Finding.Note] = [] var formattedKeyword = keyword formattedKeyword.leadingTrivia = [] @@ -85,24 +86,46 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { for memberItem in memDeclBlock.members { let member = memberItem.decl guard + let modifiers = member.asProtocol(WithModifiersSyntax.self)?.modifiers, // addModifier relocates trivia for any token(s) displaced by the new modifier. let newDecl = addModifier(declaration: member, modifierKeyword: formattedKeyword) .as(DeclSyntax.self) - else { continue } + else { + newMembers.append(memberItem) + continue + } + newMembers.append(memberItem.with(\.decl, newDecl)) + + // If it already had an explicit access modifier, don't leave a note. + if modifiers.accessLevelModifier == nil { + notes.append(Finding.Note( + message: .addModifierToExtensionMember(keyword: formattedKeyword.name.text), + location: Finding.Location(member.startLocation(converter: context.sourceLocationConverter)) + )) + } } - return MemberBlockItemListSyntax(newMembers) + return (MemberBlockItemListSyntax(newMembers), notes) } } extension Finding.Message { @_spi(Rules) - public static func removeRedundantAccessKeyword(name: String) -> Finding.Message { - "remove redundant 'internal' access keyword from '\(name)'" - } + public static let removeRedundantAccessKeyword: Finding.Message = + "remove this redundant 'internal' access modifier from this extension" @_spi(Rules) public static func moveAccessKeyword(keyword: String) -> Finding.Message { - "move the '\(keyword)' access keyword to precede each member inside the extension" + "move this '\(keyword)' access modifier to precede each member inside this extension" + } + + @_spi(Rules) + public static func moveAccessKeywordAndMakeFileprivate(keyword: String) -> Finding.Message { + "remove this '\(keyword)' access modifier and declare each member inside this extension as 'fileprivate'" + } + + @_spi(Rules) + public static func addModifierToExtensionMember(keyword: String) -> Finding.Message { + "add '\(keyword)' access modifier to this declaration" } } diff --git a/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift index 7eb6a9b00..f2cf72648 100644 --- a/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift +++ b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift @@ -28,7 +28,7 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { /// Flushes any un-collapsed violations to the new cases list. func flushViolations() { fallthroughOnlyCases.forEach { - newChildren.append(.switchCase(super.visit($0))) + newChildren.append(.switchCase(visit($0))) } fallthroughOnlyCases.removeAll() } @@ -57,7 +57,8 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { if canMergeWithPreviousCases(switchCase) { // If the current case can be merged with the ones before it, merge them all, leaving no // `fallthrough`-only cases behind. - newChildren.append(.switchCase(visit(mergedCases(fallthroughOnlyCases + [switchCase])))) + let newSwitchCase = visit(switchCase) + newChildren.append(.switchCase(visit(mergedCases(fallthroughOnlyCases + [newSwitchCase])))) } else { // If the current case can't be merged with the ones before it, merge the previous ones // into a single `fallthrough`-only case and then append the current one. This could @@ -171,6 +172,11 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { var newCaseItems: [SwitchCaseItemSyntax] = [] let labels = cases.lazy.compactMap({ $0.label.as(SwitchCaseLabelSyntax.self) }) for label in labels.dropLast() { + // Diagnose the cases being collapsed. We do this for all but the last one in the array; the + // last one isn't diagnosed because it will contain the body that applies to all the previous + // cases. + diagnose(.collapseCase, on: label) + // We can blindly append all but the last case item because they must already have a trailing // comma. Then, we need to add a trailing comma to the last one, since it will be followed by // more items. @@ -179,11 +185,6 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { label.caseItems.last!.with( \.trailingComma, TokenSyntax.commaToken(trailingTrivia: .spaces(1)))) - - // Diagnose the cases being collapsed. We do this for all but the last one in the array; the - // last one isn't diagnosed because it will contain the body that applies to all the previous - // cases. - diagnose(.collapseCase, on: label) } newCaseItems.append(contentsOf: labels.last!.caseItems) diff --git a/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift b/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift index 0f6a0cbc0..e79c38701 100644 --- a/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift +++ b/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift @@ -24,16 +24,18 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { public override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax { guard node.arguments.count == 0 else { return super.visit(node) } - guard let trailingClosure = node.trailingClosure, - node.arguments.isEmpty && node.leftParen != nil else - { + guard + let trailingClosure = node.trailingClosure, + let leftParen = node.leftParen, + node.arguments.isEmpty + else { return super.visit(node) } guard let name = node.calledExpression.lastToken(viewMode: .sourceAccurate) else { return super.visit(node) } - diagnose(.removeEmptyTrailingParentheses(name: "\(name.trimmedDescription)"), on: node) + diagnose(.removeEmptyTrailingParentheses(name: "\(name.trimmedDescription)"), on: leftParen) // Need to visit `calledExpression` before creating a new node so that the location data (column // and line numbers) is available. diff --git a/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift index cd1906e76..75a334d98 100644 --- a/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift @@ -42,7 +42,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { } } - diagnose(.removeParensAroundExpression, on: expr) + diagnose(.removeParensAroundExpression, on: tuple.leftParen) guard let visitedTuple = visit(tuple).as(TupleExprSyntax.self), @@ -75,7 +75,6 @@ public final class NoParensAroundConditions: SyntaxFormatRule { return node.with(\.condition, .expression(extractExpr(tup))) } - /// FIXME(hbh): Parsing for SwitchExprSyntax is not implemented. public override func visit(_ node: SwitchExprSyntax) -> ExprSyntax { guard let tup = node.subject.as(TupleExprSyntax.self), tup.elements.firstAndOnly != nil diff --git a/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift b/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift index 21c441993..bc5a6ba86 100644 --- a/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift +++ b/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift @@ -18,56 +18,60 @@ import SwiftSyntax @_spi(Rules) public final class TypeNamesShouldBeCapitalized : SyntaxLintRule { public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseNameConventionMismatch(node, name: node.name) + diagnoseNameConventionMismatch(node, name: node.name, kind: "struct") return .visitChildren } public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseNameConventionMismatch(node, name: node.name) + diagnoseNameConventionMismatch(node, name: node.name, kind: "class") return .visitChildren } public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseNameConventionMismatch(node, name: node.name) + diagnoseNameConventionMismatch(node, name: node.name, kind: "enum") return .visitChildren } public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseNameConventionMismatch(node, name: node.name) + diagnoseNameConventionMismatch(node, name: node.name, kind: "protocol") return .visitChildren } public override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseNameConventionMismatch(node, name: node.name) + diagnoseNameConventionMismatch(node, name: node.name, kind: "actor") return .visitChildren } public override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseNameConventionMismatch(node, name: node.name) + diagnoseNameConventionMismatch(node, name: node.name, kind: "associated type") return .visitChildren } public override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseNameConventionMismatch(node, name: node.name) + diagnoseNameConventionMismatch(node, name: node.name, kind: "type alias") return .visitChildren } - private func diagnoseNameConventionMismatch(_ type: T, name: TokenSyntax) { + private func diagnoseNameConventionMismatch( + _ type: T, + name: TokenSyntax, + kind: String + ) { let leadingUnderscores = name.text.prefix { $0 == "_" } if let firstChar = name.text[leadingUnderscores.endIndex...].first, firstChar.uppercased() != String(firstChar) { - diagnose(.capitalizeTypeName(name: name.text), on: type, severity: .convention) + diagnose(.capitalizeTypeName(name: name.text, kind: kind), on: name, severity: .convention) } } } extension Finding.Message { @_spi(Rules) - public static func capitalizeTypeName(name: String) -> Finding.Message { + public static func capitalizeTypeName(name: String, kind: String) -> Finding.Message { var capitalized = name let leadingUnderscores = capitalized.prefix { $0 == "_" } let charAt = leadingUnderscores.endIndex capitalized.replaceSubrange(charAt...charAt, with: capitalized[charAt].uppercased()) - return "type names should be capitalized: \(name) -> \(capitalized)" + return "rename the \(kind) '\(name)' using UpperCamelCase; for example, '\(capitalized)'" } } diff --git a/Sources/SwiftFormat/Rules/UseEarlyExits.swift b/Sources/SwiftFormat/Rules/UseEarlyExits.swift index 9f4d15334..da2b3b61c 100644 --- a/Sources/SwiftFormat/Rules/UseEarlyExits.swift +++ b/Sources/SwiftFormat/Rules/UseEarlyExits.swift @@ -49,42 +49,42 @@ public final class UseEarlyExits: SyntaxFormatRule { public override class var isOptIn: Bool { return true } public override func visit(_ node: CodeBlockItemListSyntax) -> CodeBlockItemListSyntax { - // Continue recursing down the tree first, so that any nested/child nodes get transformed first. - let codeBlockItems = super.visit(node) - - let result = CodeBlockItemListSyntax( - codeBlockItems.flatMap { (codeBlockItem: CodeBlockItemSyntax) -> [CodeBlockItemSyntax] in - // The `elseBody` of an `IfExprSyntax` will be a `CodeBlockSyntax` if it's an `else` block, - // or another `IfExprSyntax` if it's an `else if` block. We only want to handle the former. - guard let exprStmt = codeBlockItem.item.as(ExpressionStmtSyntax.self), - let ifStatement = exprStmt.expression.as(IfExprSyntax.self), - let elseBody = ifStatement.elseBody?.as(CodeBlockSyntax.self), - codeBlockEndsWithEarlyExit(elseBody) - else { - return [codeBlockItem] - } + var newItems = [CodeBlockItemSyntax]() - diagnose(.useGuardStatement, on: ifStatement.elseKeyword) + for codeBlockItem in node { + // The `elseBody` of an `IfExprSyntax` will be a `CodeBlockSyntax` if it's an `else` block, + // or another `IfExprSyntax` if it's an `else if` block. We only want to handle the former. + guard + let exprStmt = codeBlockItem.item.as(ExpressionStmtSyntax.self), + let ifStatement = exprStmt.expression.as(IfExprSyntax.self), + let elseBody = ifStatement.elseBody?.as(CodeBlockSyntax.self), + codeBlockEndsWithEarlyExit(elseBody) + else { + newItems.append(visit(codeBlockItem)) + continue + } + + diagnose(.useGuardStatement, on: ifStatement) + + let guardKeyword = TokenSyntax.keyword( + .guard, + leadingTrivia: ifStatement.ifKeyword.leadingTrivia, + trailingTrivia: .spaces(1)) + let guardStatement = GuardStmtSyntax( + guardKeyword: guardKeyword, + conditions: ifStatement.conditions, + elseKeyword: TokenSyntax.keyword(.else, trailingTrivia: .spaces(1)), + body: visit(elseBody)) - let trueBlock = ifStatement.body + newItems.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(guardStatement)))) - let guardKeyword = TokenSyntax.keyword(.guard, - leadingTrivia: ifStatement.ifKeyword.leadingTrivia, - trailingTrivia: .spaces(1)) - let guardStatement = GuardStmtSyntax( - guardKeyword: guardKeyword, - conditions: ifStatement.conditions, - elseKeyword: TokenSyntax.keyword(.else, trailingTrivia: .spaces(1)), - body: elseBody) + let trueBlock = visit(ifStatement.body) + for trueStmt in trueBlock.statements { + newItems.append(trueStmt) + } + } - var items = [ - CodeBlockItemSyntax( - item: .stmt(StmtSyntax(guardStatement)), semicolon: nil), - ] - items.append(contentsOf: trueBlock.statements) - return items - }) - return result + return CodeBlockItemListSyntax(newItems) } /// Returns true if the last statement in the given code block is one that will cause an early @@ -109,5 +109,5 @@ public final class UseEarlyExits: SyntaxFormatRule { extension Finding.Message { @_spi(Rules) public static let useGuardStatement: Finding.Message = - "replace the 'if/else' block with a 'guard' statement containing the early exit" + "replace this 'if/else' block with a 'guard' statement containing the early exit" } diff --git a/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift b/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift index 9fb325503..e1f2e3eeb 100644 --- a/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift +++ b/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift @@ -80,6 +80,8 @@ public final class UseTripleSlashForDocumentationComments: SyntaxFormatRule { return decl } + diagnose(.avoidDocBlockComment, on: decl, leadingTriviaIndex: commentInfo.startIndex) + // Keep any trivia leading up to the doc comment. var pieces = Array(decl.leadingTrivia[.. Element {} - protocol SomeProtocol {} - class SomeClass {} - struct SomeStruct {} - enum SomeEnum {} - typealias Foo = Bar - } - 2️⃣internal extension Bar { - var a: Int - var b: Int + 4️⃣static func someFunc() {} + 5️⃣init() {} + 6️⃣subscript(index: Int) -> Element {} + 7️⃣class SomeClass {} + 8️⃣struct SomeStruct {} + 9️⃣enum SomeEnum {} + 🔟typealias Foo = Bar } """, expected: """ @@ -42,20 +34,49 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { public static func someFunc() {} public init() {} public subscript(index: Int) -> Element {} - public protocol SomeProtocol {} public class SomeClass {} public struct SomeStruct {} public enum SomeEnum {} public typealias Foo = Bar } + """, + findings: [ + FindingSpec( + "1️⃣", + message: "move this 'public' access modifier to precede each member inside this extension", + notes: [ + NoteSpec("2️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("3️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("4️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("5️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("6️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("7️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("8️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("9️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("🔟", message: "add 'public' access modifier to this declaration"), + ] + ), + ] + ) + } + + func testRemoveRedundantInternal() { + assertFormatting( + NoAccessLevelOnExtensionDeclaration.self, + input: """ + 1️⃣internal extension Bar { + var a: Int + var b: Int + } + """, + expected: """ extension Bar { var a: Int var b: Int } """, findings: [ - FindingSpec("1️⃣", message: "move the 'public' access keyword to precede each member inside the extension"), - FindingSpec("2️⃣", message: "remove redundant 'internal' access keyword from 'Bar '"), + FindingSpec("1️⃣", message: "remove this redundant 'internal' access modifier from this extension"), ] ) } @@ -66,9 +87,9 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { input: """ /// This doc comment should stick around. 1️⃣public extension Foo { - func f() {} + 3️⃣func f() {} // This should not change. - func g() {} + 4️⃣func g() {} } /// So should this one. @@ -94,8 +115,15 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move the 'public' access keyword to precede each member inside the extension"), - FindingSpec("2️⃣", message: "remove redundant 'internal' access keyword from 'Foo '"), + FindingSpec( + "1️⃣", + message: "move this 'public' access modifier to precede each member inside this extension", + notes: [ + NoteSpec("3️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("4️⃣", message: "add 'public' access modifier to this declaration"), + ] + ), + FindingSpec("2️⃣", message: "remove this redundant 'internal' access modifier from this extension"), ] ) } @@ -105,7 +133,7 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { NoAccessLevelOnExtensionDeclaration.self, input: """ 1️⃣private extension Foo { - func f() {} + 2️⃣func f() {} } """, expected: """ @@ -114,7 +142,13 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move the 'private' access keyword to precede each member inside the extension"), + FindingSpec( + "1️⃣", + message: "remove this 'private' access modifier and declare each member inside this extension as 'fileprivate'", + notes: [ + NoteSpec("2️⃣", message: "add 'fileprivate' access modifier to this declaration"), + ] + ), ] ) } @@ -133,7 +167,7 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move the 'public' access keyword to precede each member inside the extension"), + FindingSpec("1️⃣", message: "move this 'public' access modifier to precede each member inside this extension"), ] ) } @@ -145,17 +179,16 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { /// This extension has a comment. 1️⃣public extension Foo { /// This property has a doc comment. - @objc var x: Bool { get { return true }} + 2️⃣@objc var x: Bool { get { return true }} // This property has a developer comment. - @objc static var z: Bool { get { return false }} + 3️⃣@objc static var z: Bool { get { return false }} /// This static function has a doc comment. - @objc static func someStaticFunc() {} - @objc init(with foo: Foo) {} - @objc func someOtherFunc() {} - @objc protocol SomeProtocol {} - @objc class SomeClass : NSObject {} - @objc associatedtype SomeType - @objc enum SomeEnum : Int { + 4️⃣@objc static func someStaticFunc() {} + 5️⃣@objc init(with foo: Foo) {} + 6️⃣@objc func someOtherFunc() {} + 7️⃣@objc class SomeClass : NSObject {} + 8️⃣@objc typealias SomeType = SomeOtherType + 9️⃣@objc enum SomeEnum : Int { case SomeInt = 32 } } @@ -171,16 +204,28 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { @objc public static func someStaticFunc() {} @objc public init(with foo: Foo) {} @objc public func someOtherFunc() {} - @objc public protocol SomeProtocol {} @objc public class SomeClass : NSObject {} - @objc public associatedtype SomeType + @objc public typealias SomeType = SomeOtherType @objc public enum SomeEnum : Int { case SomeInt = 32 } } """, findings: [ - FindingSpec("1️⃣", message: "move the 'public' access keyword to precede each member inside the extension"), + FindingSpec( + "1️⃣", + message: "move this 'public' access modifier to precede each member inside this extension", + notes: [ + NoteSpec("2️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("3️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("4️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("5️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("6️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("7️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("8️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("9️⃣", message: "add 'public' access modifier to this declaration"), + ] + ), ] ) } @@ -192,25 +237,23 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { /// This extension has a comment. 1️⃣public extension Foo { /// This property has a doc comment. - @available(iOS 13, *) + 2️⃣@available(iOS 13, *) var x: Bool { get { return true }} // This property has a developer comment. - @available(iOS 13, *) + 3️⃣@available(iOS 13, *) static var z: Bool { get { return false }} // This static function has a developer comment. - @objc(someStaticFunction) + 4️⃣@objc(someStaticFunction) static func someStaticFunc() {} - @objc(initWithFoo:) + 5️⃣@objc(initWithFoo:) init(with foo: Foo) {} - @objc + 6️⃣@objc func someOtherFunc() {} - @objc - protocol SomeProtocol {} - @objc + 7️⃣@objc class SomeClass : NSObject {} - @available(iOS 13, *) - associatedtype SomeType - @objc + 8️⃣@available(iOS 13, *) + typealias SomeType = SomeOtherType + 9️⃣@objc enum SomeEnum : Int { case SomeInt = 32 } @@ -241,11 +284,9 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { @objc public func someOtherFunc() {} @objc - public protocol SomeProtocol {} - @objc public class SomeClass : NSObject {} @available(iOS 13, *) - public associatedtype SomeType + public typealias SomeType = SomeOtherType @objc public enum SomeEnum : Int { case SomeInt = 32 @@ -261,7 +302,20 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move the 'public' access keyword to precede each member inside the extension"), + FindingSpec( + "1️⃣", + message: "move this 'public' access modifier to precede each member inside this extension", + notes: [ + NoteSpec("2️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("3️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("4️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("5️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("6️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("7️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("8️⃣", message: "add 'public' access modifier to this declaration"), + NoteSpec("9️⃣", message: "add 'public' access modifier to this declaration"), + ] + ), ] ) } diff --git a/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift b/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift index 5332e72b5..873b60797 100644 --- a/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift @@ -147,7 +147,6 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { } func testNestedSwitches() { - // FIXME: Finding #3 is at an odd column; it should be before the `case`. Look into this. assertFormatting( NoCasesWithOnlyFallthrough.self, input: """ @@ -156,7 +155,7 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { 2️⃣case 2: fallthrough case 3: switch y { - case 13️⃣: fallthrough + 3️⃣case 1: fallthrough case 2: print(2) } case 4: diff --git a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift index 447fbafec..bee6c215c 100644 --- a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift @@ -2,7 +2,6 @@ import _SwiftFormatTestSupport @_spi(Rules) import SwiftFormat -// FIXME: Why not emit the finding at the very parentheses we want the user to remove? final class NoEmptyTrailingClosureParenthesesTests: LintOrFormatRuleTestCase { func testInvalidEmptyParenTrailingClosure() { assertFormatting( @@ -14,29 +13,29 @@ final class NoEmptyTrailingClosureParenthesesTests: LintOrFormatRuleTestCase { func greetApathetically(_ nameProvider: () -> String) { // ... } - 0️⃣greetEnthusiastically() { "John" } + greetEnthusiastically0️⃣() { "John" } greetApathetically { "not John" } func myfunc(cls: MyClass) { cls.myClosure { $0 } } func myfunc(cls: MyClass) { - 1️⃣cls.myBadClosure() { $0 } + cls.myBadClosure1️⃣() { $0 } } - 2️⃣DispatchQueue.main.async() { - 3️⃣greetEnthusiastically() { "John" } - 4️⃣DispatchQueue.main.async() { - 5️⃣greetEnthusiastically() { "Willis" } + DispatchQueue.main.async2️⃣() { + greetEnthusiastically3️⃣() { "John" } + DispatchQueue.main.async4️⃣() { + greetEnthusiastically5️⃣() { "Willis" } } } DispatchQueue.global.async(inGroup: blah) { - 6️⃣DispatchQueue.main.async() { - 7️⃣greetEnthusiastically() { "Willis" } + DispatchQueue.main.async6️⃣() { + greetEnthusiastically7️⃣() { "Willis" } } DispatchQueue.main.async { - 8️⃣greetEnthusiastically() { "Willis" } + greetEnthusiastically8️⃣() { "Willis" } } } - 9️⃣foo(🔟bar() { baz })() { blah } + foo(bar🔟() { baz })9️⃣() { blah } """, expected: """ func greetEnthusiastically(_ nameProvider: () -> String) { diff --git a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift index 859844265..e28fac949 100644 --- a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift @@ -2,18 +2,17 @@ import _SwiftFormatTestSupport @_spi(Rules) import SwiftFormat -// FIXME: Emit final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { func testParensAroundConditions() { assertFormatting( NoParensAroundConditions.self, input: """ - if (1️⃣x) {} - while (2️⃣x) {} - guard (3️⃣x), (4️⃣y), (5️⃣x == 3) else {} + if 1️⃣(x) {} + while 2️⃣(x) {} + guard 3️⃣(x), 4️⃣(y), 5️⃣(x == 3) else {} if (foo { x }) {} - repeat {} while(6️⃣x) - switch (7️⃣4) { default: break } + repeat {} while6️⃣(x) + switch 7️⃣(4) { default: break } """, expected: """ if x {} @@ -39,18 +38,18 @@ final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { assertFormatting( NoParensAroundConditions.self, input: """ - switch (1️⃣a) { + switch 1️⃣(a) { case 1: - switch (2️⃣b) { + switch 2️⃣(b) { default: break } } - if (3️⃣x) { - if (4️⃣y) { - } else if (5️⃣z) { + if 3️⃣(x) { + if 4️⃣(y) { + } else if 5️⃣(z) { } else { } - } else if (6️⃣w) { + } else if 6️⃣(w) { } """, expected: """ @@ -81,20 +80,20 @@ final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { assertFormatting( NoParensAroundConditions.self, input: """ - while (1️⃣x) { - while (2️⃣y) {} + while 1️⃣(x) { + while 2️⃣(y) {} } - guard (3️⃣x), (4️⃣y), (5️⃣x == 3) else { - guard (6️⃣a), (7️⃣b), (8️⃣c == x) else { + guard 3️⃣(x), 4️⃣(y), 5️⃣(x == 3) else { + guard 6️⃣(a), 7️⃣(b), 8️⃣(c == x) else { return } return } repeat { repeat { - } while (9️⃣y) - } while(🔟x) - if (0️⃣foo.someCall({ if (ℹ️x) {} })) {} + } while 9️⃣(y) + } while🔟(x) + if 0️⃣(foo.someCall({ if ℹ️(x) {} })) {} """, expected: """ while x { @@ -135,25 +134,25 @@ final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { input: """ switch b { case 2: - switch (1️⃣d) { + switch 1️⃣(d) { default: break } } if x { - if (2️⃣y) { - } else if (3️⃣z) { + if 2️⃣(y) { + } else if 3️⃣(z) { } else { } - } else if (4️⃣w) { + } else if 4️⃣(w) { } while x { - while (5️⃣y) {} + while 5️⃣(y) {} } repeat { repeat { - } while (6️⃣y) + } while 6️⃣(y) } while x - if foo.someCall({ if (7️⃣x) {} }) {} + if foo.someCall({ if 7️⃣(x) {} }) {} """, expected: """ switch b { @@ -194,13 +193,13 @@ final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { assertFormatting( NoParensAroundConditions.self, input: """ - let x = if (1️⃣x) {} - let y = switch (2️⃣4) { default: break } + let x = if 1️⃣(x) {} + let y = switch 2️⃣(4) { default: break } func foo() { - return if (3️⃣x) {} + return if 3️⃣(x) {} } func bar() { - return switch (4️⃣4) { default: break } + return switch 4️⃣(4) { default: break } } """, expected: """ diff --git a/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift b/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift index 28628e1b1..f44ea743d 100644 --- a/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift +++ b/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift @@ -2,28 +2,27 @@ import _SwiftFormatTestSupport @_spi(Rules) import SwiftFormat -// FIXME: Diagnostics should be emitted at the identifier, not at the start of the declaration. final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { func testConstruction() { assertLint( TypeNamesShouldBeCapitalized.self, """ - 1️⃣struct a {} - 2️⃣class klassName { - 3️⃣struct subType {} + struct 1️⃣a {} + class 2️⃣klassName { + struct 3️⃣subType {} } - 4️⃣protocol myProtocol {} + protocol 4️⃣myProtocol {} extension myType { - 5️⃣struct innerType {} + struct 5️⃣innerType {} } """, findings: [ - FindingSpec("1️⃣", message: "type names should be capitalized: a -> A"), - FindingSpec("2️⃣", message: "type names should be capitalized: klassName -> KlassName"), - FindingSpec("3️⃣", message: "type names should be capitalized: subType -> SubType"), - FindingSpec("4️⃣", message: "type names should be capitalized: myProtocol -> MyProtocol"), - FindingSpec("5️⃣", message: "type names should be capitalized: innerType -> InnerType"), + FindingSpec("1️⃣", message: "rename the struct 'a' using UpperCamelCase; for example, 'A'"), + FindingSpec("2️⃣", message: "rename the class 'klassName' using UpperCamelCase; for example, 'KlassName'"), + FindingSpec("3️⃣", message: "rename the struct 'subType' using UpperCamelCase; for example, 'SubType'"), + FindingSpec("4️⃣", message: "rename the protocol 'myProtocol' using UpperCamelCase; for example, 'MyProtocol'"), + FindingSpec("5️⃣", message: "rename the struct 'innerType' using UpperCamelCase; for example, 'InnerType'"), ] ) } @@ -32,14 +31,14 @@ final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { assertLint( TypeNamesShouldBeCapitalized.self, """ - 1️⃣actor myActor {} + actor 1️⃣myActor {} actor OtherActor {} - 2️⃣distributed actor greeter {} + distributed actor 2️⃣greeter {} distributed actor DistGreeter {} """, findings: [ - FindingSpec("1️⃣", message: "type names should be capitalized: myActor -> MyActor"), - FindingSpec("2️⃣", message: "type names should be capitalized: greeter -> Greeter"), + FindingSpec("1️⃣", message: "rename the actor 'myActor' using UpperCamelCase; for example, 'MyActor'"), + FindingSpec("2️⃣", message: "rename the actor 'greeter' using UpperCamelCase; for example, 'Greeter'"), ] ) } @@ -49,15 +48,15 @@ final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { TypeNamesShouldBeCapitalized.self, """ protocol P { - 1️⃣associatedtype kind + associatedtype 1️⃣kind associatedtype OtherKind } - 2️⃣typealias x = Int + typealias 2️⃣x = Int typealias Y = String struct MyType { - 3️⃣typealias data = Y + typealias 3️⃣data = Y func test() { typealias Value = Y @@ -65,9 +64,9 @@ final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "type names should be capitalized: kind -> Kind"), - FindingSpec("2️⃣", message: "type names should be capitalized: x -> X"), - FindingSpec("3️⃣", message: "type names should be capitalized: data -> Data"), + FindingSpec("1️⃣", message: "rename the associated type 'kind' using UpperCamelCase; for example, 'Kind'"), + FindingSpec("2️⃣", message: "rename the type alias 'x' using UpperCamelCase; for example, 'X'"), + FindingSpec("3️⃣", message: "rename the type alias 'data' using UpperCamelCase; for example, 'Data'"), ] ) } @@ -76,47 +75,47 @@ final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { assertLint( TypeNamesShouldBeCapitalized.self, """ - 1️⃣protocol _p { - 2️⃣associatedtype _value + protocol 1️⃣_p { + associatedtype 2️⃣_value associatedtype __Value } protocol ___Q { } - 3️⃣struct _data { - 4️⃣typealias _x = Int + struct 3️⃣_data { + typealias 4️⃣_x = Int } struct _Data {} - 5️⃣actor _internalActor {} + actor 5️⃣_internalActor {} - 6️⃣enum __e { + enum 6️⃣__e { } enum _OtherE { } func test() { - 7️⃣class _myClass {} + class 7️⃣_myClass {} do { class _MyClass {} } } - 8️⃣distributed actor __greeter {} + distributed actor 8️⃣__greeter {} distributed actor __InternalGreeter {} """, findings: [ - FindingSpec("1️⃣", message: "type names should be capitalized: _p -> _P"), - FindingSpec("2️⃣", message: "type names should be capitalized: _value -> _Value"), - FindingSpec("3️⃣", message: "type names should be capitalized: _data -> _Data"), - FindingSpec("4️⃣", message: "type names should be capitalized: _x -> _X"), - FindingSpec("5️⃣", message: "type names should be capitalized: _internalActor -> _InternalActor"), - FindingSpec("6️⃣", message: "type names should be capitalized: __e -> __E"), - FindingSpec("7️⃣", message: "type names should be capitalized: _myClass -> _MyClass"), - FindingSpec("8️⃣", message: "type names should be capitalized: __greeter -> __Greeter"), + FindingSpec("1️⃣", message: "rename the protocol '_p' using UpperCamelCase; for example, '_P'"), + FindingSpec("2️⃣", message: "rename the associated type '_value' using UpperCamelCase; for example, '_Value'"), + FindingSpec("3️⃣", message: "rename the struct '_data' using UpperCamelCase; for example, '_Data'"), + FindingSpec("4️⃣", message: "rename the type alias '_x' using UpperCamelCase; for example, '_X'"), + FindingSpec("5️⃣", message: "rename the actor '_internalActor' using UpperCamelCase; for example, '_InternalActor'"), + FindingSpec("6️⃣", message: "rename the enum '__e' using UpperCamelCase; for example, '__E'"), + FindingSpec("7️⃣", message: "rename the class '_myClass' using UpperCamelCase; for example, '_MyClass'"), + FindingSpec("8️⃣", message: "rename the actor '__greeter' using UpperCamelCase; for example, '__Greeter'"), ] ) } diff --git a/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift b/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift index 6368366ee..f9d54d2fc 100644 --- a/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift @@ -2,9 +2,6 @@ import _SwiftFormatTestSupport @_spi(Rules) import SwiftFormat -// FIXME: The findings are emitted in odd places; the last test is especially wrong. Their locations -// may be getting computed from the tree post-transformation, so they no longer map to the right -// locations in the original tree. final class UseEarlyExitsTests: LintOrFormatRuleTestCase { func testBasicIfElse() { // In this and other tests, the indentation of the true block in the expected output is @@ -13,9 +10,9 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { assertFormatting( UseEarlyExits.self, input: """ - if condition { + 1️⃣if condition { trueBlock() - } 1️⃣else { + } else { falseBlock() return } @@ -28,7 +25,7 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { trueBlock() """, findings: [ - FindingSpec("1️⃣", message: "replace the 'if/else' block with a 'guard' statement containing the early exit"), + FindingSpec("1️⃣", message: "replace this 'if/else' block with a 'guard' statement containing the early exit"), ] ) } @@ -37,10 +34,10 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { assertFormatting( UseEarlyExits.self, input: """ - if condition { + 1️⃣if condition { trueBlock() return - } 1️⃣else { + } else { falseBlock() return } @@ -54,7 +51,7 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { return """, findings: [ - FindingSpec("1️⃣", message: "replace the 'if/else' block with a 'guard' statement containing the early exit"), + FindingSpec("1️⃣", message: "replace this 'if/else' block with a 'guard' statement containing the early exit"), ] ) } @@ -94,20 +91,20 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { // Comment 1 - /*Comment 2*/ if let first = values.first { + /*Comment 2*/ 1️⃣if let first = values.first { // Comment 3 /// Doc comment - if first >= 0 { + 2️⃣if first >= 0 { // Comment 4 var result = 0 - 2️⃣ for value in values { + for value in values { result += invertedCombobulatorFactor(of: value) } return result } else { print("Can't have negative energy") - 1️⃣ throw DiscombobulationError.negativeEnergy + throw DiscombobulationError.negativeEnergy } } else { print("The array was empty") @@ -140,8 +137,8 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "replace the 'if/else' block with a 'guard' statement containing the early exit"), - FindingSpec("2️⃣", message: "replace the 'if/else' block with a 'guard' statement containing the early exit"), + FindingSpec("1️⃣", message: "replace this 'if/else' block with a 'guard' statement containing the early exit"), + FindingSpec("2️⃣", message: "replace this 'if/else' block with a 'guard' statement containing the early exit"), ] ) } diff --git a/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift index 757687645..d5c460c72 100644 --- a/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift @@ -2,7 +2,6 @@ import _SwiftFormatTestSupport @_spi(Rules) import SwiftFormat -// FIXME: Findings aren't actually being emitted by this rule! final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCase { func testRemoveDocBlockComments() { assertFormatting( @@ -12,7 +11,7 @@ final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCas * This comment should not be converted. */ - /** + 1️⃣/** * Returns a docLineComment. * * - Parameters: @@ -33,7 +32,9 @@ final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCas /// - Returns: docLineComment. func foo(withOutStar: Bool) {} """, - findings: [] + findings: [ + FindingSpec("1️⃣", message: "replace documentation block comments with documentation line comments") + ] ) } @@ -41,7 +42,7 @@ final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCas assertFormatting( UseTripleSlashForDocumentationComments.self, input: """ - /** + 1️⃣/** Returns a docLineComment. - Parameters: @@ -58,7 +59,9 @@ final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCas /// - Returns: docLineComment. public var test = 1 """, - findings: [] + findings: [ + FindingSpec("1️⃣", message: "replace documentation block comments with documentation line comments") + ] ) } @@ -137,7 +140,7 @@ final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCas /// Why are there so many comments? /// Who knows! But there are loads. - /** AClazz is a class with good name. */ + 1️⃣/** AClazz is a class with good name. */ public class AClazz { } """, @@ -158,7 +161,9 @@ final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCas public class AClazz { } """, - findings: [] + findings: [ + FindingSpec("1️⃣", message: "replace documentation block comments with documentation line comments") + ] ) } From fcdf5ee2700b3e9021751d019b3e021213b0f16e Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Mon, 21 Aug 2023 12:10:31 -0700 Subject: [PATCH 104/332] Adjust OmitReturnsTests to use the most recent APIs --- .../Rules/OmitReturnsTests.swift | 116 +++++++++--------- 1 file changed, 61 insertions(+), 55 deletions(-) diff --git a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift index 888eab448..70d39cec7 100644 --- a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift @@ -1,105 +1,107 @@ +import _SwiftFormatTestSupport + @_spi(Rules) import SwiftFormat final class OmitReturnsTests: LintOrFormatRuleTestCase { func testOmitReturnInFunction() { - XCTAssertFormatting( + assertFormatting( OmitReturns.self, input: """ func test() -> Bool { - return false + 1️⃣return false } - """, + """, expected: """ func test() -> Bool { false } - """) + """, + findings: [ + FindingSpec("1️⃣", message: "`return` can be omitted because body consists of a single expression") + ]) } func testOmitReturnInClosure() { - XCTAssertFormatting( + assertFormatting( OmitReturns.self, input: """ vals.filter { - return $0.count == 1 + 1️⃣return $0.count == 1 } - """, + """, expected: """ vals.filter { $0.count == 1 } - """) + """, + findings: [ + FindingSpec("1️⃣", message: "`return` can be omitted because body consists of a single expression") + ]) } func testOmitReturnInSubscript() { - XCTAssertFormatting( - OmitReturns.self, - input: """ - struct Test { - subscript(x: Int) -> Bool { - return false - } - } - """, - expected: """ - struct Test { - subscript(x: Int) -> Bool { - false - } + assertFormatting( + OmitReturns.self, + input: """ + struct Test { + subscript(x: Int) -> Bool { + 1️⃣return false } - """) + } - XCTAssertFormatting( - OmitReturns.self, - input: """ - struct Test { - subscript(x: Int) -> Bool { - get { - return false - } - set { } + struct Test { + subscript(x: Int) -> Bool { + get { + 2️⃣return false } + set { } + } + } + """, + expected: """ + struct Test { + subscript(x: Int) -> Bool { + false } - """, - expected: """ - struct Test { - subscript(x: Int) -> Bool { - get { - false - } - set { } + } + + struct Test { + subscript(x: Int) -> Bool { + get { + false } + set { } } - """) + } + """, + findings: [ + FindingSpec("1️⃣", message: "`return` can be omitted because body consists of a single expression"), + FindingSpec("2️⃣", message: "`return` can be omitted because body consists of a single expression") + ]) } func testOmitReturnInComputedVars() { - XCTAssertFormatting( + assertFormatting( OmitReturns.self, input: """ var x: Int { - return 42 - } - """, - expected: """ - var x: Int { - 42 + 1️⃣return 42 } - """) - XCTAssertFormatting( - OmitReturns.self, - input: """ struct Test { var x: Int { get { - return 42 + 2️⃣return 42 } set { } } } - """, + """, expected: """ + var x: Int { + 42 + } + struct Test { var x: Int { get { @@ -108,6 +110,10 @@ final class OmitReturnsTests: LintOrFormatRuleTestCase { set { } } } - """) + """, + findings: [ + FindingSpec("1️⃣", message: "`return` can be omitted because body consists of a single expression"), + FindingSpec("2️⃣", message: "`return` can be omitted because body consists of a single expression") + ]) } } From a0ee6ebde021a9e3afeccdecb144a49b76007bd4 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 22 Aug 2023 09:31:33 -0400 Subject: [PATCH 105/332] Don't do anything if the input is empty. Formatting/linting a 0 byte file should not insert or diagnose a trailing newline. This makes us consistent with clang-format's behavior even when its `InsertNewlineAtEOF` setting is enabled. --- Behavior before: ``` $ echo -n 'foo()' | .build/debug/swift-format | hexdump -C 00000000 66 6f 6f 28 29 0a |foo().| 00000006 $ echo -n '' | .build/debug/swift-format | hexdump -C 00000000 0a |.| 00000001 ``` Behavior after: ``` $ echo -n 'foo()' | .build/debug/swift-format | hexdump -C 00000000 66 6f 6f 28 29 0a |foo().| 00000006 $ echo -n '' | .build/debug/swift-format | hexdump -C ``` Likewise, lint mode now emits no finding for an empty file instead of previously emitting `warning: [AddLines] add 1 line breaks`. --- Sources/SwiftFormat/API/SwiftFormatter.swift | 16 +++++++++------- Sources/SwiftFormat/API/SwiftLinter.swift | 14 ++++++++------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Sources/SwiftFormat/API/SwiftFormatter.swift b/Sources/SwiftFormat/API/SwiftFormatter.swift index b676019aa..8ab36f0f6 100644 --- a/Sources/SwiftFormat/API/SwiftFormatter.swift +++ b/Sources/SwiftFormat/API/SwiftFormatter.swift @@ -67,15 +67,12 @@ public final class SwiftFormatter { if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir), isDir.boolValue { throw SwiftFormatError.isDirectory } - let source = try String(contentsOf: url, encoding: .utf8) - let sourceFile = try parseAndEmitDiagnostics( - source: source, - operatorTable: .standardOperators, + + try format( + source: String(contentsOf: url, encoding: .utf8), assumingFileURL: url, + to: &outputStream, parsingDiagnosticHandler: parsingDiagnosticHandler) - try format( - syntax: sourceFile, operatorTable: .standardOperators, assumingFileURL: url, source: source, - to: &outputStream) } /// Formats the given Swift source code and writes the result to an output stream. @@ -101,6 +98,11 @@ public final class SwiftFormatter { to outputStream: inout Output, parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil ) throws { + // If the file or input string is completely empty, do nothing. This prevents even a trailing + // newline from being emitted for an empty file. (This is consistent with clang-format, which + // also does not touch an empty file even if the setting to add trailing newlines is enabled.) + guard !source.isEmpty else { return } + let sourceFile = try parseAndEmitDiagnostics( source: source, operatorTable: .standardOperators, diff --git a/Sources/SwiftFormat/API/SwiftLinter.swift b/Sources/SwiftFormat/API/SwiftLinter.swift index f1787502e..c891fff3a 100644 --- a/Sources/SwiftFormat/API/SwiftLinter.swift +++ b/Sources/SwiftFormat/API/SwiftLinter.swift @@ -64,14 +64,11 @@ public final class SwiftLinter { if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir), isDir.boolValue { throw SwiftFormatError.isDirectory } - let source = try String(contentsOf: url, encoding: .utf8) - let sourceFile = try parseAndEmitDiagnostics( - source: source, - operatorTable: .standardOperators, + + try lint( + source: String(contentsOf: url, encoding: .utf8), assumingFileURL: url, parsingDiagnosticHandler: parsingDiagnosticHandler) - try lint( - syntax: sourceFile, operatorTable: .standardOperators, assumingFileURL: url, source: source) } /// Lints the given Swift source code. @@ -92,6 +89,11 @@ public final class SwiftLinter { assumingFileURL url: URL, parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil ) throws { + // If the file or input string is completely empty, do nothing. This prevents even a trailing + // newline from being diagnosed for an empty file. (This is consistent with clang-format, which + // also does not touch an empty file even if the setting to add trailing newlines is enabled.) + guard !source.isEmpty else { return } + let sourceFile = try parseAndEmitDiagnostics( source: source, operatorTable: .standardOperators, From 4d54de36481a92182c54ec91d3f34453592568a5 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 24 Aug 2023 00:13:40 -0700 Subject: [PATCH 106/332] Address naming suggestions and use `guard let` instead of `if let` --- Sources/SwiftFormat/Rules/OmitReturns.swift | 99 ++++++++++----------- 1 file changed, 45 insertions(+), 54 deletions(-) diff --git a/Sources/SwiftFormat/Rules/OmitReturns.swift b/Sources/SwiftFormat/Rules/OmitReturns.swift index e1c295f14..b3b31f05f 100644 --- a/Sources/SwiftFormat/Rules/OmitReturns.swift +++ b/Sources/SwiftFormat/Rules/OmitReturns.swift @@ -26,59 +26,56 @@ public final class OmitReturns: SyntaxFormatRule { let decl = super.visit(node) // func () -> { return ... } - if var funcDecl = decl.as(FunctionDeclSyntax.self), - let body = funcDecl.body, - let `return` = containsSingleReturn(body.statements) { - funcDecl.body?.statements = unwrapReturnStmt(`return`) - diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) - return DeclSyntax(funcDecl) + guard var funcDecl = decl.as(FunctionDeclSyntax.self), + let body = funcDecl.body, + let returnStmt = containsSingleReturn(body.statements) else { + return decl } - return decl + funcDecl.body?.statements = unwrapReturnStmt(returnStmt) + diagnose(.omitReturnStatement, on: returnStmt, severity: .refactoring) + return DeclSyntax(funcDecl) } public override func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax { let decl = super.visit(node) - guard var `subscript` = decl.as(SubscriptDeclSyntax.self) else { + guard var subscriptDecl = decl.as(SubscriptDeclSyntax.self), + let accessorBlock = subscriptDecl.accessorBlock, + // We are assuming valid Swift code here where only + // one `get { ... }` is allowed. + let transformed = transformAccessorBlock(accessorBlock) else { return decl } - if let accessorBlock = `subscript`.accessorBlock, - // We are assuming valid Swift code here where only - // one `get { ... }` is allowed. - let transformed = transformAccessorBlock(accessorBlock) { - `subscript`.accessorBlock = transformed - return DeclSyntax(`subscript`) - } - - return decl + subscriptDecl.accessorBlock = transformed + return DeclSyntax(subscriptDecl) } public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax { var binding = node - if let accessorBlock = binding.accessorBlock, - let transformed = transformAccessorBlock(accessorBlock) { - binding.accessorBlock = transformed - return binding + guard let accessorBlock = binding.accessorBlock, + let transformed = transformAccessorBlock(accessorBlock) else { + return node } - return node + binding.accessorBlock = transformed + return binding } public override func visit(_ node: ClosureExprSyntax) -> ExprSyntax { let expr = super.visit(node) // test { return ... } - if var closure = expr.as(ClosureExprSyntax.self), - let `return` = containsSingleReturn(closure.statements) { - closure.statements = unwrapReturnStmt(`return`) - diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) - return ExprSyntax(closure) + guard var closureExpr = expr.as(ClosureExprSyntax.self), + let returnStmt = containsSingleReturn(closureExpr.statements) else { + return expr } - return expr + closureExpr.statements = unwrapReturnStmt(returnStmt) + diagnose(.omitReturnStatement, on: returnStmt, severity: .refactoring) + return ExprSyntax(closureExpr) } private func transformAccessorBlock(_ accessorBlock: AccessorBlockSyntax) -> AccessorBlockSyntax? { @@ -93,7 +90,7 @@ public final class OmitReturns: SyntaxFormatRule { } guard let body = getter.body, - let `return` = containsSingleReturn(body.statements) else { + let returnStmt = containsSingleReturn(body.statements) else { return nil } @@ -103,50 +100,44 @@ public final class OmitReturns: SyntaxFormatRule { return nil } - getter.body?.statements = unwrapReturnStmt(`return`) + getter.body?.statements = unwrapReturnStmt(returnStmt) - diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) + diagnose(.omitReturnStatement, on: returnStmt, severity: .refactoring) - return .init( - leadingTrivia: accessorBlock.leadingTrivia, - leftBrace: accessorBlock.leftBrace, - accessors: .accessors(accessors.with(\.[getterAt], getter)), - rightBrace: accessorBlock.rightBrace, - trailingTrivia: accessorBlock.trailingTrivia) + var newBlock = accessorBlock + newBlock.accessors = .accessors(accessors.with(\.[getterAt], getter)) + return newBlock case .getter(let getter): - guard let `return` = containsSingleReturn(getter) else { + guard let returnStmt = containsSingleReturn(getter) else { return nil } - diagnose(.omitReturnStatement, on: `return`, severity: .refactoring) + diagnose(.omitReturnStatement, on: returnStmt, severity: .refactoring) - return .init( - leadingTrivia: accessorBlock.leadingTrivia, - leftBrace: accessorBlock.leftBrace, - accessors: .getter(unwrapReturnStmt(`return`)), - rightBrace: accessorBlock.rightBrace, - trailingTrivia: accessorBlock.trailingTrivia) + var newBlock = accessorBlock + newBlock.accessors = .getter(unwrapReturnStmt(returnStmt)) + return newBlock } } private func containsSingleReturn(_ body: CodeBlockItemListSyntax) -> ReturnStmtSyntax? { - if let element = body.firstAndOnly?.as(CodeBlockItemSyntax.self), - let ret = element.item.as(ReturnStmtSyntax.self), - !ret.children(viewMode: .all).isEmpty, ret.expression != nil { - return ret + guard let element = body.firstAndOnly?.as(CodeBlockItemSyntax.self), + let returnStmt = element.item.as(ReturnStmtSyntax.self) else + { + return nil } - return nil + return !returnStmt.children(viewMode: .all).isEmpty && returnStmt.expression != nil ? returnStmt : nil } - private func unwrapReturnStmt(_ `return`: ReturnStmtSyntax) -> CodeBlockItemListSyntax { + private func unwrapReturnStmt(_ returnStmt: ReturnStmtSyntax) -> CodeBlockItemListSyntax { CodeBlockItemListSyntax([ CodeBlockItemSyntax( - leadingTrivia: `return`.leadingTrivia, - item: .expr(`return`.expression!), + leadingTrivia: returnStmt.leadingTrivia, + item: .expr(returnStmt.expression!), semicolon: nil, - trailingTrivia: `return`.trailingTrivia) + trailingTrivia: returnStmt.trailingTrivia) ]) } } From 9e0f249d80faf39d4c836987c3b9aabe24a6cb78 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 24 Aug 2023 09:33:59 -0700 Subject: [PATCH 107/332] NFC: Rename OmitReturns rule into OmitExplicitReturns --- Sources/SwiftFormat/Core/Pipelines+Generated.swift | 10 +++++----- Sources/SwiftFormat/Core/RuleNameCache+Generated.swift | 2 +- .../{OmitReturns.swift => OmitExplicitReturns.swift} | 2 +- .../RuleRegistry+Generated.swift | 2 +- Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) rename Sources/SwiftFormat/Rules/{OmitReturns.swift => OmitExplicitReturns.swift} (98%) diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index b63b6a1a8..c095ca479 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -61,7 +61,7 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind { - visitIfEnabled(OmitReturns.visit, for: node) + visitIfEnabled(OmitExplicitReturns.visit, for: node) return .visitChildren } @@ -151,7 +151,7 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) - visitIfEnabled(OmitReturns.visit, for: node) + visitIfEnabled(OmitExplicitReturns.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) visitIfEnabled(ValidateDocumentationComments.visit, for: node) return .visitChildren @@ -232,7 +232,7 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: PatternBindingSyntax) -> SyntaxVisitorContinueKind { - visitIfEnabled(OmitReturns.visit, for: node) + visitIfEnabled(OmitExplicitReturns.visit, for: node) visitIfEnabled(UseSingleLinePropertyGetter.visit, for: node) return .visitChildren } @@ -282,7 +282,7 @@ class LintPipeline: SyntaxVisitor { override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) - visitIfEnabled(OmitReturns.visit, for: node) + visitIfEnabled(OmitExplicitReturns.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } @@ -351,7 +351,7 @@ extension FormatPipeline { node = NoLabelsInCasePatterns(context: context).rewrite(node) node = NoParensAroundConditions(context: context).rewrite(node) node = NoVoidReturnOnFunctionSignature(context: context).rewrite(node) - node = OmitReturns(context: context).rewrite(node) + node = OmitExplicitReturns(context: context).rewrite(node) node = OneCasePerLine(context: context).rewrite(node) node = OneVariableDeclarationPerLine(context: context).rewrite(node) node = OrderedImports(context: context).rewrite(node) diff --git a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift index 62c6d3cb7..248adceec 100644 --- a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift @@ -37,7 +37,7 @@ public let ruleNameCache: [ObjectIdentifier: String] = [ ObjectIdentifier(NoLeadingUnderscores.self): "NoLeadingUnderscores", ObjectIdentifier(NoParensAroundConditions.self): "NoParensAroundConditions", ObjectIdentifier(NoVoidReturnOnFunctionSignature.self): "NoVoidReturnOnFunctionSignature", - ObjectIdentifier(OmitReturns.self): "OmitReturns", + ObjectIdentifier(OmitExplicitReturns.self): "OmitExplicitReturns", ObjectIdentifier(OneCasePerLine.self): "OneCasePerLine", ObjectIdentifier(OneVariableDeclarationPerLine.self): "OneVariableDeclarationPerLine", ObjectIdentifier(OnlyOneTrailingClosureArgument.self): "OnlyOneTrailingClosureArgument", diff --git a/Sources/SwiftFormat/Rules/OmitReturns.swift b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift similarity index 98% rename from Sources/SwiftFormat/Rules/OmitReturns.swift rename to Sources/SwiftFormat/Rules/OmitExplicitReturns.swift index b3b31f05f..d2a96379c 100644 --- a/Sources/SwiftFormat/Rules/OmitReturns.swift +++ b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift @@ -19,7 +19,7 @@ import SwiftSyntax /// Format: `func () { return ... }` constructs will be replaced with /// equivalent `func () { ... }` constructs. @_spi(Rules) -public final class OmitReturns: SyntaxFormatRule { +public final class OmitExplicitReturns: SyntaxFormatRule { public override class var isOptIn: Bool { return true } public override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax { diff --git a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift index f17ff0c9f..75570000f 100644 --- a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift @@ -36,7 +36,7 @@ enum RuleRegistry { "NoLeadingUnderscores": false, "NoParensAroundConditions": true, "NoVoidReturnOnFunctionSignature": true, - "OmitReturns": false, + "OmitExplicitReturns": false, "OneCasePerLine": true, "OneVariableDeclarationPerLine": true, "OnlyOneTrailingClosureArgument": true, diff --git a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift index 70d39cec7..111068d3a 100644 --- a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift @@ -5,7 +5,7 @@ import _SwiftFormatTestSupport final class OmitReturnsTests: LintOrFormatRuleTestCase { func testOmitReturnInFunction() { assertFormatting( - OmitReturns.self, + OmitExplicitReturns.self, input: """ func test() -> Bool { 1️⃣return false @@ -23,7 +23,7 @@ final class OmitReturnsTests: LintOrFormatRuleTestCase { func testOmitReturnInClosure() { assertFormatting( - OmitReturns.self, + OmitExplicitReturns.self, input: """ vals.filter { 1️⃣return $0.count == 1 @@ -41,7 +41,7 @@ final class OmitReturnsTests: LintOrFormatRuleTestCase { func testOmitReturnInSubscript() { assertFormatting( - OmitReturns.self, + OmitExplicitReturns.self, input: """ struct Test { subscript(x: Int) -> Bool { @@ -82,7 +82,7 @@ final class OmitReturnsTests: LintOrFormatRuleTestCase { func testOmitReturnInComputedVars() { assertFormatting( - OmitReturns.self, + OmitExplicitReturns.self, input: """ var x: Int { 1️⃣return 42 From 34551d1c3a45b004083678697e18d0e776d33383 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 24 Aug 2023 09:39:55 -0700 Subject: [PATCH 108/332] OmitExplicitReturns: replace backticks with quotes in the Lint diagnostic message --- Sources/SwiftFormat/Rules/OmitExplicitReturns.swift | 2 +- Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift index d2a96379c..469ccc3e1 100644 --- a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift +++ b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift @@ -144,5 +144,5 @@ public final class OmitExplicitReturns: SyntaxFormatRule { extension Finding.Message { public static let omitReturnStatement: Finding.Message = - "`return` can be omitted because body consists of a single expression" + "'return' can be omitted because body consists of a single expression" } diff --git a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift index 111068d3a..b5c2f9449 100644 --- a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift @@ -17,7 +17,7 @@ final class OmitReturnsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "`return` can be omitted because body consists of a single expression") + FindingSpec("1️⃣", message: "'return' can be omitted because body consists of a single expression") ]) } @@ -35,7 +35,7 @@ final class OmitReturnsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "`return` can be omitted because body consists of a single expression") + FindingSpec("1️⃣", message: "'return' can be omitted because body consists of a single expression") ]) } @@ -75,8 +75,8 @@ final class OmitReturnsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "`return` can be omitted because body consists of a single expression"), - FindingSpec("2️⃣", message: "`return` can be omitted because body consists of a single expression") + FindingSpec("1️⃣", message: "'return' can be omitted because body consists of a single expression"), + FindingSpec("2️⃣", message: "'return' can be omitted because body consists of a single expression") ]) } @@ -112,8 +112,8 @@ final class OmitReturnsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "`return` can be omitted because body consists of a single expression"), - FindingSpec("2️⃣", message: "`return` can be omitted because body consists of a single expression") + FindingSpec("1️⃣", message: "'return' can be omitted because body consists of a single expression"), + FindingSpec("2️⃣", message: "'return' can be omitted because body consists of a single expression") ]) } } From eabc443431ec9f3e87e2fa01564b8af862e601b2 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 24 Aug 2023 09:42:48 -0700 Subject: [PATCH 109/332] OmitExplicitReturns: Rename unwrapReturnStmt into rewrapReturnedExpression --- Sources/SwiftFormat/Rules/OmitExplicitReturns.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift index 469ccc3e1..31966cc0e 100644 --- a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift +++ b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift @@ -32,7 +32,7 @@ public final class OmitExplicitReturns: SyntaxFormatRule { return decl } - funcDecl.body?.statements = unwrapReturnStmt(returnStmt) + funcDecl.body?.statements = rewrapReturnedExpression(returnStmt) diagnose(.omitReturnStatement, on: returnStmt, severity: .refactoring) return DeclSyntax(funcDecl) } @@ -73,7 +73,7 @@ public final class OmitExplicitReturns: SyntaxFormatRule { return expr } - closureExpr.statements = unwrapReturnStmt(returnStmt) + closureExpr.statements = rewrapReturnedExpression(returnStmt) diagnose(.omitReturnStatement, on: returnStmt, severity: .refactoring) return ExprSyntax(closureExpr) } @@ -100,7 +100,7 @@ public final class OmitExplicitReturns: SyntaxFormatRule { return nil } - getter.body?.statements = unwrapReturnStmt(returnStmt) + getter.body?.statements = rewrapReturnedExpression(returnStmt) diagnose(.omitReturnStatement, on: returnStmt, severity: .refactoring) @@ -116,7 +116,7 @@ public final class OmitExplicitReturns: SyntaxFormatRule { diagnose(.omitReturnStatement, on: returnStmt, severity: .refactoring) var newBlock = accessorBlock - newBlock.accessors = .getter(unwrapReturnStmt(returnStmt)) + newBlock.accessors = .getter(rewrapReturnedExpression(returnStmt)) return newBlock } } @@ -131,7 +131,7 @@ public final class OmitExplicitReturns: SyntaxFormatRule { return !returnStmt.children(viewMode: .all).isEmpty && returnStmt.expression != nil ? returnStmt : nil } - private func unwrapReturnStmt(_ returnStmt: ReturnStmtSyntax) -> CodeBlockItemListSyntax { + private func rewrapReturnedExpression(_ returnStmt: ReturnStmtSyntax) -> CodeBlockItemListSyntax { CodeBlockItemListSyntax([ CodeBlockItemSyntax( leadingTrivia: returnStmt.leadingTrivia, From 9dc726f7f087ca4dcafbd064e080f9dc3cfb9d17 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 29 Aug 2023 11:53:33 -0700 Subject: [PATCH 110/332] [Lint] Add a rule to replace `.forEach` with a for-in loop If there is no chaining using for-in loop more understandable and requires less chaining/nesting. --- .../Core/Pipelines+Generated.swift | 1 + .../Core/RuleNameCache+Generated.swift | 1 + .../Rules/ReplaceForEachWithForLoop.swift | 53 +++++++++++++++++++ .../RuleRegistry+Generated.swift | 1 + .../ReplaceForEachWithForLoopTests.swift | 34 ++++++++++++ 5 files changed, 90 insertions(+) create mode 100644 Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift create mode 100644 Tests/SwiftFormatTests/Rules/ReplaceForEachWithForLoopTests.swift diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index c095ca479..566d4a568 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -143,6 +143,7 @@ class LintPipeline: SyntaxVisitor { override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoEmptyTrailingClosureParentheses.visit, for: node) visitIfEnabled(OnlyOneTrailingClosureArgument.visit, for: node) + visitIfEnabled(ReplaceForEachWithForLoop.visit, for: node) return .visitChildren } diff --git a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift index 248adceec..db36040ce 100644 --- a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift @@ -42,6 +42,7 @@ public let ruleNameCache: [ObjectIdentifier: String] = [ ObjectIdentifier(OneVariableDeclarationPerLine.self): "OneVariableDeclarationPerLine", ObjectIdentifier(OnlyOneTrailingClosureArgument.self): "OnlyOneTrailingClosureArgument", ObjectIdentifier(OrderedImports.self): "OrderedImports", + ObjectIdentifier(ReplaceForEachWithForLoop.self): "ReplaceForEachWithForLoop", ObjectIdentifier(ReturnVoidInsteadOfEmptyTuple.self): "ReturnVoidInsteadOfEmptyTuple", ObjectIdentifier(TypeNamesShouldBeCapitalized.self): "TypeNamesShouldBeCapitalized", ObjectIdentifier(UseEarlyExits.self): "UseEarlyExits", diff --git a/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift b/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift new file mode 100644 index 000000000..927070df5 --- /dev/null +++ b/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// Replace `forEach` with `for-in` loop unless its argument is a function reference. +/// +/// Lint: invalid use of `forEach` yield will yield a lint error. +@_spi(Rules) +public final class ReplaceForEachWithForLoop : SyntaxLintRule { + public override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { + // We are only interested in calls with a single trailing closure + // argument. + if !node.arguments.isEmpty || node.trailingClosure == nil || + !node.additionalTrailingClosures.isEmpty { + return .visitChildren + } + + guard let member = node.calledExpression.as(MemberAccessExprSyntax.self) else { + return .visitChildren + } + + guard let memberName = member.declName.baseName.as(TokenSyntax.self), + memberName.text == "forEach" else { + return .visitChildren + } + + // If there is another chained member after `.forEach`, + // let's skip the diagnostic because resulting code might + // be less understandable. + if node.parent?.is(MemberAccessExprSyntax.self) == true { + return .visitChildren + } + + diagnose(.replaceForEachWithLoop(), on: memberName) + return .visitChildren + } +} + +extension Finding.Message { + public static func replaceForEachWithLoop() -> Finding.Message { + "replace use of '.forEach { ... }' with for-in loop" + } +} diff --git a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift index 75570000f..b7a17de2d 100644 --- a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift @@ -41,6 +41,7 @@ enum RuleRegistry { "OneVariableDeclarationPerLine": true, "OnlyOneTrailingClosureArgument": true, "OrderedImports": true, + "ReplaceForEachWithForLoop": true, "ReturnVoidInsteadOfEmptyTuple": true, "TypeNamesShouldBeCapitalized": true, "UseEarlyExits": false, diff --git a/Tests/SwiftFormatTests/Rules/ReplaceForEachWithForLoopTests.swift b/Tests/SwiftFormatTests/Rules/ReplaceForEachWithForLoopTests.swift new file mode 100644 index 000000000..295f3bfe1 --- /dev/null +++ b/Tests/SwiftFormatTests/Rules/ReplaceForEachWithForLoopTests.swift @@ -0,0 +1,34 @@ +import _SwiftFormatTestSupport + +@_spi(Rules) import SwiftFormat + + +final class ReplaceForEachWithForLoopTests: LintOrFormatRuleTestCase { + func test() { + assertLint( + ReplaceForEachWithForLoop.self, + """ + values.1️⃣forEach { $0 * 2 } + values.map { $0 }.2️⃣forEach { print($0) } + values.forEach(callback) + values.forEach { $0 }.chained() + values.forEach({ $0 }).chained() + values.3️⃣forEach { + let arg = $0 + return arg + 1 + } + values.forEach { + let arg = $0 + return arg + 1 + } other: { + 42 + } + """, + findings: [ + FindingSpec("1️⃣", message: "replace use of '.forEach { ... }' with for-in loop"), + FindingSpec("2️⃣", message: "replace use of '.forEach { ... }' with for-in loop"), + FindingSpec("3️⃣", message: "replace use of '.forEach { ... }' with for-in loop") + ] + ) + } +} From cf11fc8df512abb41e7023710a47d90236da928c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 1 Sep 2023 19:35:12 -0700 Subject: [PATCH 111/332] Adjust for "remark" diagnostic severity --- Sources/swift-format/Utilities/DiagnosticsEngine.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/swift-format/Utilities/DiagnosticsEngine.swift b/Sources/swift-format/Utilities/DiagnosticsEngine.swift index 0eead4342..c2353b196 100644 --- a/Sources/swift-format/Utilities/DiagnosticsEngine.swift +++ b/Sources/swift-format/Utilities/DiagnosticsEngine.swift @@ -101,6 +101,7 @@ final class DiagnosticsEngine { case .error: severity = .error case .warning: severity = .warning case .note: severity = .note + case .remark: severity = .note // should we model this? } return Diagnostic( severity: severity, From f8fb0ec70178015d4eb0b04d88a3489847b4d0b9 Mon Sep 17 00:00:00 2001 From: Brian Henry Date: Sun, 3 Sep 2023 10:49:25 -0700 Subject: [PATCH 112/332] Default to all targets when plugin `--target` parameter missing --- Package.swift | 3 + Plugins/FormatPlugin/plugin.swift | 2 +- Plugins/LintPlugin/plugin.swift | 2 +- .../PluginRunTests.swift | 272 ++++++++++++++++++ 4 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 Tests/SwiftFormatPluginTests/PluginRunTests.swift diff --git a/Package.swift b/Package.swift index 6d7045c78..ad7647ce2 100644 --- a/Package.swift +++ b/Package.swift @@ -139,6 +139,9 @@ let package = Package( .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ] ), + .testTarget( + name: "SwiftFormatPluginTests" + ), ] ) diff --git a/Plugins/FormatPlugin/plugin.swift b/Plugins/FormatPlugin/plugin.swift index a89f7d652..e544a6464 100644 --- a/Plugins/FormatPlugin/plugin.swift +++ b/Plugins/FormatPlugin/plugin.swift @@ -38,7 +38,7 @@ extension FormatPlugin: CommandPlugin { var argExtractor = ArgumentExtractor(arguments) let targetNames = argExtractor.extractOption(named: "target") - let targetsToFormat = try context.package.targets(named: targetNames) + let targetsToFormat = targetNames.isEmpty ? context.package.targets : try context.package.targets(named: targetNames) let configurationFilePath = argExtractor.extractOption(named: "configuration").first diff --git a/Plugins/LintPlugin/plugin.swift b/Plugins/LintPlugin/plugin.swift index 4dd72204f..0589b7530 100644 --- a/Plugins/LintPlugin/plugin.swift +++ b/Plugins/LintPlugin/plugin.swift @@ -39,8 +39,8 @@ extension LintPlugin: CommandPlugin { // Extract the arguments that specify what targets to format. var argExtractor = ArgumentExtractor(arguments) let targetNames = argExtractor.extractOption(named: "target") - let targetsToFormat = try context.package.targets(named: targetNames) + let targetsToFormat = targetNames.isEmpty ? context.package.targets : try context.package.targets(named: targetNames) let configurationFilePath = argExtractor.extractOption(named: "configuration").first let sourceCodeTargets = targetsToFormat.compactMap { $0 as? SourceModuleTarget } diff --git a/Tests/SwiftFormatPluginTests/PluginRunTests.swift b/Tests/SwiftFormatPluginTests/PluginRunTests.swift new file mode 100644 index 000000000..6e8a87a5b --- /dev/null +++ b/Tests/SwiftFormatPluginTests/PluginRunTests.swift @@ -0,0 +1,272 @@ +import Foundation +import XCTest + +final class PluginRunTests: XCTestCase { + + var workingDirUrl: URL! + var taskProcess: Process! + + var allOutputTxt: String = "" + var stdoutTxt: String = "" + var stderrTxt: String = "" + + override func setUp() { + setupProject() + fixCodesigning() + setupProcess() + } + + // In a tmp dir, create a SPM project which depends on this project, and build it. + // Project contains targets: executable, library, test, plugin. + func setupProject() { + + // currentDirectoryPath: /path/to/swift-format/.build/arm64-apple-macosx/debug + let swiftFormatProjectDirPath = URL( + fileURLWithPath: FileManager.default.currentDirectoryPath, + isDirectory: true + ) + .deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().path + + let packageTxt = """ + // swift-tools-version: 5.9 + + import PackageDescription + + let package = Package( + name: "Swift-Format-Plugin-Test", + products: [ + .library( + name: "LibraryTarget", + targets: ["LibraryTarget"] + ), + .plugin( + name: "PluginTarget", + targets: ["PluginTarget"] + ) + ], + dependencies: [ + .package(path: "\(swiftFormatProjectDirPath)"), + ], + targets: [ + .executableTarget( + name: "ExecutableTarget", + dependencies: [ + "LibraryTarget", + ], + path: "Sources/ExecutableTarget" + ), + .target( + name: "LibraryTarget", + path: "Sources/LibraryTarget" + ), + .plugin( + name: "PluginTarget", + capability: .command( + intent: .custom( + verb: "test-plugin", + description: "A test plugin" + ), + permissions: [ + .writeToPackageDirectory(reason: "This command generates files") + ] + ), + dependencies: [ + "ExecutableTarget" + ], + path: "Plugins/PluginTarget" + ), + .testTarget( + name: "TestTarget", + dependencies: [ + "LibraryTarget" + ] + ) + ] + ) + """ + + let tempDirUrl = FileManager.default.temporaryDirectory.appendingPathComponent( + UUID().uuidString) + + do { + try FileManager.default.createDirectory(at: tempDirUrl, withIntermediateDirectories: false) + + FileManager.default.createFile( + atPath: tempDirUrl.appendingPathComponent("package.swift").path, + contents: packageTxt.data(using: .utf8) + ) + + try FileManager.default.createDirectory( + at: tempDirUrl.appendingPathComponent("Sources").appendingPathComponent("ExecutableTarget"), + withIntermediateDirectories: true) + FileManager.default.createFile( + atPath: tempDirUrl.appendingPathComponent("Sources").appendingPathComponent( + "ExecutableTarget" + ).appendingPathComponent("maain.swift").path, + contents: "".data(using: .utf8) + ) + + try FileManager.default.createDirectory( + at: tempDirUrl.appendingPathComponent("Sources").appendingPathComponent("LibraryTarget"), + withIntermediateDirectories: true) + FileManager.default.createFile( + atPath: tempDirUrl.appendingPathComponent("Sources").appendingPathComponent("LibraryTarget") + .appendingPathComponent("library.swift").path, + contents: "".data(using: .utf8) + ) + + try FileManager.default.createDirectory( + at: tempDirUrl.appendingPathComponent("Tests").appendingPathComponent("TestTarget"), + withIntermediateDirectories: true) + FileManager.default.createFile( + atPath: tempDirUrl.appendingPathComponent("Tests").appendingPathComponent("TestTarget") + .appendingPathComponent("test.swift").path, + contents: "".data(using: .utf8) + ) + + let pluginTxt = """ + import Foundation + import PackagePlugin + + @main + struct TestPlugin: CommandPlugin { + func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { + print("TestPlugin is working.") + } + } + """ + + try FileManager.default.createDirectory( + at: tempDirUrl.appendingPathComponent("Plugins").appendingPathComponent("PluginTarget"), + withIntermediateDirectories: true) + FileManager.default.createFile( + atPath: tempDirUrl.appendingPathComponent("Plugins").appendingPathComponent("PluginTarget") + .appendingPathComponent("plugin.swift").path, + contents: pluginTxt.data(using: .utf8) + ) + + setupProcess() + taskProcess.arguments = [ + "build" + ] + try taskProcess.run() + taskProcess.waitUntilExit() + + XCTAssertTrue(stdoutTxt.contains("Build complete!")) + + workingDirUrl = tempDirUrl + } catch { + XCTFail("Error setting up test fixture project at: \(tempDirUrl.path)") + } + } + + // Prepare a new NSTask/Process with output recorded to string variables. + func setupProcess() { + + allOutputTxt = "" + stdoutTxt = "" + stderrTxt = "" + + let stdoutHandler = { (file: FileHandle!) -> Void in + let data = file.availableData + + guard !data.isEmpty, let output = String(data: data, encoding: .utf8), !output.isEmpty + else { + return + } + self.stdoutTxt += output + self.allOutputTxt += output + } + let stderrHandler = { (file: FileHandle!) -> Void in + let data = file.availableData + guard !data.isEmpty, let output = String(data: data, encoding: .utf8), !output.isEmpty + else { + return + } + self.stderrTxt += output + self.allOutputTxt += output + } + + let stdOut = Pipe() + stdOut.fileHandleForReading.readabilityHandler = stdoutHandler + + let stderr = Pipe() + stderr.fileHandleForReading.readabilityHandler = stderrHandler + + taskProcess = Process() + taskProcess.standardOutput = stdOut + taskProcess.standardError = stderr + taskProcess.currentDirectoryURL = workingDirUrl + + taskProcess.launchPath = "/usr/bin/swift" + } + + /** + @see https://github.com/apple/swift-package-manager/issues/6872 + */ + func fixCodesigning() { + setupProcess() + taskProcess.arguments = [ + "package", "plugin", "lint-source-code", + ] + try! taskProcess.run() + taskProcess.waitUntilExit() + + // Do not alter the codesigning if the bug in #6872 is not present. + if 0 == taskProcess.terminationStatus + || !allOutputTxt.split(separator: "\n").last!.contains("Build complete!") + { + return + } + + setupProcess() + taskProcess.launchPath = "/usr/bin/codesign" + taskProcess.arguments = [ + "-s", "-", workingDirUrl.path + "/.build/plugins/Lint Source Code/cache/Lint_Source_Code", + ] + try! taskProcess.run() + taskProcess.waitUntilExit() + + setupProcess() + taskProcess.arguments = [ + "package", "plugin", "lint-source-code", + ] + try! taskProcess.run() + taskProcess.waitUntilExit() + } + + /** + Confirm the plugin runs without any targets specified. + `swift package plugin lint-source-code` + @see https://github.com/apple/swift-format/issues/483 + "error: swift-format invocation failed: NSTaskTerminationReason(rawValue: 1):64" + */ + public func testPluginRun() { + + let processFinishExpectation = expectation(description: "process timeout") + + taskProcess.arguments = [ + "package", "plugin", "lint-source-code", + ] + do { + taskProcess.terminationHandler = { (process: Process) in + processFinishExpectation.fulfill() + } + + try taskProcess.run() + + waitForExpectations(timeout: 30) + } catch { + XCTFail(allOutputTxt) + } + + let errorMessage = "swift-format invocation failed: NSTaskTerminationReason(rawValue: 1):64" + if stderrTxt.split(separator: "\n").last == "error: \(errorMessage)" { + XCTFail("\(errorMessage)\nworkingdir: \(workingDirUrl.path)") + } + + XCTAssertEqual( + 0, Int(taskProcess.terminationStatus), "Non-zero exit code\nworkingdir: \(workingDirUrl.path)" + ) + } +} From 7bdd9ac269da88298debf861604e688fde08549d Mon Sep 17 00:00:00 2001 From: Brian Henry Date: Sun, 3 Sep 2023 11:20:21 -0700 Subject: [PATCH 113/332] Delete unused code (was being used for step-debug) --- Tests/SwiftFormatPluginTests/PluginRunTests.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Tests/SwiftFormatPluginTests/PluginRunTests.swift b/Tests/SwiftFormatPluginTests/PluginRunTests.swift index 6e8a87a5b..1bafd0f5c 100644 --- a/Tests/SwiftFormatPluginTests/PluginRunTests.swift +++ b/Tests/SwiftFormatPluginTests/PluginRunTests.swift @@ -226,13 +226,6 @@ final class PluginRunTests: XCTestCase { ] try! taskProcess.run() taskProcess.waitUntilExit() - - setupProcess() - taskProcess.arguments = [ - "package", "plugin", "lint-source-code", - ] - try! taskProcess.run() - taskProcess.waitUntilExit() } /** From c3d4e7ef5f1a5655a0d7ae19c44a85b4eb4b11c4 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 30 Aug 2023 20:40:45 -0400 Subject: [PATCH 114/332] Replace `with(...)` calls with in-place mutation. Also remove some extensions no longer in use, and rewrite the `DeclModifierListSyntax` helpers to take keywords instead of strings. --- .../Core/LegacyTriviaBehavior.swift | 5 +- .../Core/ModifierListSyntax+Convenience.swift | 44 ++++--- .../Core/TokenSyntax+Convenience.swift | 37 ------ .../SwiftFormat/Core/Trivia+Convenience.swift | 86 +------------ ...otocol.swift => WithSemicolonSyntax.swift} | 17 +-- .../PrettyPrint/TokenStreamCreator.swift | 23 ++-- ...lPublicDeclarationsHaveDocumentation.swift | 4 +- .../Rules/AlwaysUseLowerCamelCase.swift | 4 +- .../AmbiguousTrailingClosureOverload.swift | 2 +- .../Rules/DoNotUseSemicolons.swift | 4 +- .../DontRepeatTypeInStaticProperties.swift | 12 +- .../Rules/FileScopedDeclarationPrivacy.swift | 86 ++++++------- .../SwiftFormat/Rules/FullyIndirectEnum.swift | 21 +-- .../Rules/GroupNumericLiterals.swift | 7 +- .../SwiftFormat/Rules/NeverForceUnwrap.swift | 4 +- ...NeverUseImplicitlyUnwrappedOptionals.swift | 3 +- .../NoAccessLevelOnExtensionDeclaration.swift | 66 +++++----- .../Rules/NoAssignmentInExpressions.swift | 36 +++--- .../Rules/NoCasesWithOnlyFallthrough.swift | 18 ++- .../Rules/NoLabelsInCasePatterns.swift | 46 ++++--- .../Rules/NoParensAroundConditions.swift | 120 +++++++++++------- .../NoVoidReturnOnFunctionSignature.swift | 27 +++- .../Rules/OmitExplicitReturns.swift | 5 +- .../SwiftFormat/Rules/OneCasePerLine.swift | 34 +++-- .../Rules/OneVariableDeclarationPerLine.swift | 18 ++- .../SwiftFormat/Rules/OrderedImports.swift | 11 +- .../Rules/ReturnVoidInsteadOfEmptyTuple.swift | 12 +- .../Rules/UseShorthandTypeNames.swift | 24 ++-- .../Rules/UseSynthesizedInitializer.swift | 2 +- .../Rules/UseWhereClausesInForLoops.swift | 6 +- .../Rules/NoParensAroundConditionsTests.swift | 30 +++++ 31 files changed, 400 insertions(+), 414 deletions(-) delete mode 100644 Sources/SwiftFormat/Core/TokenSyntax+Convenience.swift rename Sources/SwiftFormat/Core/{SemicolonSyntaxProtocol.swift => WithSemicolonSyntax.swift} (59%) diff --git a/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift b/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift index 83b5e5ca7..bb4143fc3 100644 --- a/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift +++ b/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift @@ -18,15 +18,14 @@ private final class LegacyTriviaBehaviorRewriter: SyntaxRewriter { override func visit(_ token: TokenSyntax) -> TokenSyntax { var token = token if let pendingLeadingTrivia = pendingLeadingTrivia { - token = token.with(\.leadingTrivia, pendingLeadingTrivia + token.leadingTrivia) + token.leadingTrivia = pendingLeadingTrivia + token.leadingTrivia self.pendingLeadingTrivia = nil } if token.nextToken(viewMode: .sourceAccurate) != nil, let firstIndexToMove = token.trailingTrivia.firstIndex(where: shouldTriviaPieceBeMoved) { pendingLeadingTrivia = Trivia(pieces: Array(token.trailingTrivia[firstIndexToMove...])) - token = - token.with(\.trailingTrivia, Trivia(pieces: Array(token.trailingTrivia[.. Bool { - return contains { $0.name.text == modifier } - } - - func has(modifier: TokenKind) -> Bool { - return contains { $0.name.tokenKind == modifier } - } - /// Returns the declaration's access level modifier, if present. var accessLevelModifier: DeclModifierSyntax? { for modifier in self { @@ -35,16 +26,35 @@ extension DeclModifierListSyntax { return nil } - /// Returns modifier list without the given modifier. - func remove(name: String) -> DeclModifierListSyntax { - return filter { $0.name.text != name } + /// Returns true if the modifier list contains any of the keywords in the given set. + func contains(anyOf keywords: Set) -> Bool { + return contains { + switch $0.name.tokenKind { + case .keyword(let keyword): return keywords.contains(keyword) + default: return false + } + } + } + + /// Removes any of the modifiers in the given set from the modifier list, mutating it in-place. + mutating func remove(anyOf keywords: Set) { + self = filter { + switch $0.name.tokenKind { + case .keyword(let keyword): return !keywords.contains(keyword) + default: return true + } + } } - /// Returns a formatted declaration modifier token with the given name. - func createModifierToken(name: String) -> DeclModifierSyntax { - let id = TokenSyntax.identifier(name, trailingTrivia: .spaces(1)) - let newModifier = DeclModifierSyntax(name: id, detail: nil) - return newModifier + + /// Returns a copy of the modifier list with any of the modifiers in the given set removed. + func removing(anyOf keywords: Set) -> DeclModifierListSyntax { + return filter { + switch $0.name.tokenKind { + case .keyword(let keyword): return !keywords.contains(keyword) + default: return true + } + } } /// Inserts the given modifier into the list at a specific index. diff --git a/Sources/SwiftFormat/Core/TokenSyntax+Convenience.swift b/Sources/SwiftFormat/Core/TokenSyntax+Convenience.swift deleted file mode 100644 index c788b973d..000000000 --- a/Sources/SwiftFormat/Core/TokenSyntax+Convenience.swift +++ /dev/null @@ -1,37 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -extension TokenSyntax { - /// Returns this token with only one space at the end of its trailing trivia. - func withOneTrailingSpace() -> TokenSyntax { - return with(\.trailingTrivia, trailingTrivia.withOneTrailingSpace()) - } - - /// Returns this token with only one space at the beginning of its leading - /// trivia. - func withOneLeadingSpace() -> TokenSyntax { - return with(\.leadingTrivia, leadingTrivia.withOneLeadingSpace()) - } - - /// Returns this token with only one newline at the end of its leading trivia. - func withOneTrailingNewline() -> TokenSyntax { - return with(\.trailingTrivia, trailingTrivia.withOneTrailingNewline()) - } - - /// Returns this token with only one newline at the beginning of its leading - /// trivia. - func withOneLeadingNewline() -> TokenSyntax { - return with(\.leadingTrivia, leadingTrivia.withOneLeadingNewline()) - } -} diff --git a/Sources/SwiftFormat/Core/Trivia+Convenience.swift b/Sources/SwiftFormat/Core/Trivia+Convenience.swift index 36c5851b2..569e490aa 100644 --- a/Sources/SwiftFormat/Core/Trivia+Convenience.swift +++ b/Sources/SwiftFormat/Core/Trivia+Convenience.swift @@ -13,17 +13,15 @@ import SwiftSyntax extension Trivia { - var numberOfComments: Int { - var count = 0 - for piece in self { - switch piece { + var hasAnyComments: Bool { + return contains { + switch $0 { case .lineComment, .docLineComment, .blockComment, .docBlockComment: - count += 1 + return true default: - continue + return false } } - return count } /// Returns whether the trivia contains at least 1 `lineComment`. @@ -34,16 +32,6 @@ extension Trivia { } } - /// Returns this set of trivia, without any whitespace characters. - func withoutSpaces() -> Trivia { - return Trivia( - pieces: filter { - if case .spaces = $0 { return false } - if case .tabs = $0 { return false } - return true - }) - } - /// Returns this set of trivia, without any leading spaces. func withoutLeadingSpaces() -> Trivia { return Trivia( @@ -54,15 +42,6 @@ extension Trivia { })) } - /// Returns this set of trivia, without any newlines. - func withoutNewlines() -> Trivia { - return Trivia( - pieces: filter { - if case .newlines = $0 { return false } - return true - }) - } - /// Returns this trivia, excluding the last newline and anything following it. /// /// If there is no newline in the trivia, it is returned unmodified. @@ -80,61 +59,6 @@ extension Trivia { return Trivia(pieces: self.dropLast(self.count - lastNewlineOffset)) } - /// Returns this set of trivia, with all spaces removed except for one at the - /// end. - func withOneTrailingSpace() -> Trivia { - return withoutSpaces() + .spaces(1) - } - - /// Returns this set of trivia, with all spaces removed except for one at the - /// beginning. - func withOneLeadingSpace() -> Trivia { - return .spaces(1) + withoutSpaces() - } - - /// Returns this set of trivia, with all newlines removed except for one. - func withOneLeadingNewline() -> Trivia { - return .newlines(1) + withoutNewlines() - } - - /// Returns this set of trivia, with all newlines removed except for one. - func withOneTrailingNewline() -> Trivia { - return withoutNewlines() + .newlines(1) - } - - /// Walks through trivia looking for multiple separate trivia entities with - /// the same base kind, and condenses them. - /// `[.spaces(1), .spaces(2)]` becomes `[.spaces(3)]`. - func condensed() -> Trivia { - guard var prev = first else { return self } - var pieces = [TriviaPiece]() - for piece in dropFirst() { - switch (prev, piece) { - case (.spaces(let l), .spaces(let r)): - prev = .spaces(l + r) - case (.tabs(let l), .tabs(let r)): - prev = .tabs(l + r) - case (.newlines(let l), .newlines(let r)): - prev = .newlines(l + r) - case (.carriageReturns(let l), .carriageReturns(let r)): - prev = .carriageReturns(l + r) - case (.carriageReturnLineFeeds(let l), .carriageReturnLineFeeds(let r)): - prev = .carriageReturnLineFeeds(l + r) - case (.verticalTabs(let l), .verticalTabs(let r)): - prev = .verticalTabs(l + r) - case (.unexpectedText(let l), .unexpectedText(let r)): - prev = .unexpectedText(l + r) - case (.formfeeds(let l), .formfeeds(let r)): - prev = .formfeeds(l + r) - default: - pieces.append(prev) - prev = piece - } - } - pieces.append(prev) - return Trivia(pieces: pieces) - } - /// Returns `true` if this trivia contains any newlines. var containsNewlines: Bool { return contains( diff --git a/Sources/SwiftFormat/Core/SemicolonSyntaxProtocol.swift b/Sources/SwiftFormat/Core/WithSemicolonSyntax.swift similarity index 59% rename from Sources/SwiftFormat/Core/SemicolonSyntaxProtocol.swift rename to Sources/SwiftFormat/Core/WithSemicolonSyntax.swift index 61e2fa112..1f9a3d2d3 100644 --- a/Sources/SwiftFormat/Core/SemicolonSyntaxProtocol.swift +++ b/Sources/SwiftFormat/Core/WithSemicolonSyntax.swift @@ -13,19 +13,20 @@ import SwiftSyntax /// Protocol that declares support for accessing and modifying a token that represents a semicolon. -protocol SemicolonSyntaxProtocol: SyntaxProtocol { +protocol WithSemicolonSyntax: SyntaxProtocol { var semicolon: TokenSyntax? { get set } } -extension MemberBlockItemSyntax: SemicolonSyntaxProtocol {} -extension CodeBlockItemSyntax: SemicolonSyntaxProtocol {} +extension MemberBlockItemSyntax: WithSemicolonSyntax {} +extension CodeBlockItemSyntax: WithSemicolonSyntax {} -extension Syntax { - func asProtocol(_: SemicolonSyntaxProtocol.Protocol) -> SemicolonSyntaxProtocol? { - return self.asProtocol(SyntaxProtocol.self) as? SemicolonSyntaxProtocol +extension SyntaxProtocol { + func asProtocol(_: WithSemicolonSyntax.Protocol) -> WithSemicolonSyntax? { + return Syntax(self).asProtocol(SyntaxProtocol.self) as? WithSemicolonSyntax } - func isProtocol(_: SemicolonSyntaxProtocol.Protocol) -> Bool { - return self.asProtocol(SemicolonSyntaxProtocol.self) != nil + func isProtocol(_: WithSemicolonSyntax.Protocol) -> Bool { + return self.asProtocol(WithSemicolonSyntax.self) != nil } } + diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 568f62a7d..d6b09abb8 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -914,7 +914,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ArrayExprSyntax) -> SyntaxVisitorContinueKind { - if !node.elements.isEmpty || node.rightSquare.leadingTrivia.numberOfComments > 0 { + if !node.elements.isEmpty || node.rightSquare.leadingTrivia.hasAnyComments { after(node.leftSquare, tokens: .break(.open, size: 0), .open) before(node.rightSquare, tokens: .break(.close, size: 0), .close) } @@ -954,8 +954,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // The node's content is either a `DictionaryElementListSyntax` or a `TokenSyntax` for a colon // token (for an empty dictionary). if !(node.content.as(DictionaryElementListSyntax.self)?.isEmpty ?? true) - || node.content.leadingTrivia.numberOfComments > 0 - || node.rightSquare.leadingTrivia.numberOfComments > 0 + || node.content.leadingTrivia.hasAnyComments + || node.rightSquare.leadingTrivia.hasAnyComments { after(node.leftSquare, tokens: .break(.open, size: 0), .open) before(node.rightSquare, tokens: .break(.close, size: 0), .close) @@ -2775,7 +2775,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) -> Bool where BodyContents.Element: SyntaxProtocol { // If the collection is empty, then any comments that might be present in the block must be // leading trivia of the right brace. - let commentPrecedesRightBrace = node.rightBrace.leadingTrivia.numberOfComments > 0 + let commentPrecedesRightBrace = node.rightBrace.leadingTrivia.hasAnyComments // We can't use `count` here because it also includes missing children. Instead, we get an // iterator and check if it returns `nil` immediately. var contentsIterator = node[keyPath: contentsKeyPath].makeIterator() @@ -2796,7 +2796,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) -> Bool where BodyContents.Element == Syntax { // If the collection is empty, then any comments that might be present in the block must be // leading trivia of the right brace. - let commentPrecedesRightBrace = node.rightBrace.leadingTrivia.numberOfComments > 0 + let commentPrecedesRightBrace = node.rightBrace.leadingTrivia.hasAnyComments // We can't use `count` here because it also includes missing children. Instead, we get an // iterator and check if it returns `nil` immediately. var contentsIterator = node[keyPath: contentsKeyPath].makeIterator() @@ -2817,7 +2817,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) -> Bool where BodyContents.Element == DeclSyntax { // If the collection is empty, then any comments that might be present in the block must be // leading trivia of the right brace. - let commentPrecedesRightBrace = node.rightBrace.leadingTrivia.numberOfComments > 0 + let commentPrecedesRightBrace = node.rightBrace.leadingTrivia.hasAnyComments // We can't use `count` here because it also includes missing children. Instead, we get an // iterator and check if it returns `nil` immediately. var contentsIterator = node[keyPath: contentsKeyPath].makeIterator() @@ -2980,7 +2980,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func arrangeBracesAndContents(leftBrace: TokenSyntax, accessors: AccessorDeclListSyntax, rightBrace: TokenSyntax) { // If the collection is empty, then any comments that might be present in the block must be // leading trivia of the right brace. - let commentPrecedesRightBrace = rightBrace.leadingTrivia.numberOfComments > 0 + let commentPrecedesRightBrace = rightBrace.leadingTrivia.hasAnyComments // We can't use `count` here because it also includes missing children. Instead, we get an // iterator and check if it returns `nil` immediately. var accessorsIterator = accessors.makeIterator() @@ -3890,10 +3890,13 @@ class CommentMovingRewriter: SyntaxRewriter { } override func visit(_ token: TokenSyntax) -> TokenSyntax { - if let rewrittenTrivia = rewriteTokenTriviaMap[token] { - return token.with(\.leadingTrivia, rewrittenTrivia) + guard let rewrittenTrivia = rewriteTokenTriviaMap[token] else { + return token } - return token + + var result = token + result.leadingTrivia = rewrittenTrivia + return result } override func visit(_ node: InfixOperatorExprSyntax) -> ExprSyntax { diff --git a/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift index ebd25bd48..d3989ae59 100644 --- a/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift +++ b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift @@ -73,11 +73,11 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { private func diagnoseMissingDocComment( _ decl: DeclSyntax, name: String, - modifiers: DeclModifierListSyntax? + modifiers: DeclModifierListSyntax ) { guard DocumentationCommentText(extractedFrom: decl.leadingTrivia) == nil, - let mods = modifiers, mods.has(modifier: "public") && !mods.has(modifier: "override") + modifiers.contains(anyOf: [.public, .override]) else { return } diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift index 1d7493f26..918c54f75 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift @@ -46,7 +46,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { // Don't diagnose any issues when the variable is overriding, because this declaration can't // rename the variable. If the user analyzes the code where the variable is really declared, // then the diagnostic can be raised for just that location. - if node.modifiers.has(modifier: "override") { + if node.modifiers.contains(anyOf: [.override]) { return .visitChildren } @@ -114,7 +114,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { // Don't diagnose any issues when the function is overriding, because this declaration can't // rename the function. If the user analyzes the code where the function is really declared, // then the diagnostic can be raised for just that location. - if node.modifiers.has(modifier: "override") { + if node.modifiers.contains(anyOf: [.override]) { return .visitChildren } diff --git a/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift b/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift index d46a2ef36..6ab780d26 100644 --- a/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift +++ b/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift @@ -42,7 +42,7 @@ public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { let params = fn.signature.parameterClause.parameters guard let firstParam = params.firstAndOnly else { continue } guard firstParam.type.is(FunctionTypeSyntax.self) else { continue } - if fn.modifiers.has(modifier: "static") || fn.modifiers.has(modifier: "class") { + if fn.modifiers.contains(anyOf: [.class, .static]) { staticOverloads[fn.name.text, default: []].append(fn) } else { overloads[fn.name.text, default: []].append(fn) diff --git a/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift index 3d5fd530c..85d7cb4fb 100644 --- a/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift @@ -28,7 +28,7 @@ public final class DoNotUseSemicolons: SyntaxFormatRule { /// - node: A node that contains items which may have semicolons or nested code blocks. /// - nodeCreator: A closure that creates a new node given an array of items. private func nodeByRemovingSemicolons< - ItemType: SyntaxProtocol & SemicolonSyntaxProtocol & Equatable, NodeType: SyntaxCollection + ItemType: SyntaxProtocol & WithSemicolonSyntax & Equatable, NodeType: SyntaxCollection >(from node: NodeType, nodeCreator: ([ItemType]) -> NodeType) -> NodeType where NodeType.Element == ItemType { var newItems = Array(node) @@ -84,7 +84,7 @@ public final class DoNotUseSemicolons: SyntaxFormatRule { // whitespace, and the pretty printer adds any necessary spaces so it's safe to discard. // TODO: When we stop using the legacy trivia transform, we need to fix this to preserve // trailing comments. - newItem = newItem.with(\.semicolon, nil) + newItem.semicolon = nil // When emitting the finding, tell the user to move the next statement down if there is // another statement following this one. Otherwise, just tell them to remove the semicolon. diff --git a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift index 8a765876d..c66d1786a 100644 --- a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift @@ -69,15 +69,19 @@ public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { for member in members { guard let varDecl = member.decl.as(VariableDeclSyntax.self), - varDecl.modifiers.has(modifier: "static") || varDecl.modifiers.has(modifier: "class") + varDecl.modifiers.contains(anyOf: [.class, .static]) else { continue } let bareTypeName = removingPossibleNamespacePrefix(from: typeName) - for pattern in varDecl.identifiers { - let varName = pattern.identifier.text + for binding in varDecl.bindings { + guard let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self) else { + continue + } + + let varName = identifierPattern.identifier.text if varName.contains(bareTypeName) { - diagnose(.removeTypeFromName(name: varName, type: bareTypeName), on: pattern.identifier) + diagnose(.removeTypeFromName(name: varName, type: bareTypeName), on: identifierPattern) } } } diff --git a/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift b/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift index 7f2de8ae8..30d43f788 100644 --- a/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift +++ b/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift @@ -23,8 +23,9 @@ import SwiftSyntax @_spi(Rules) public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { public override func visit(_ node: SourceFileSyntax) -> SourceFileSyntax { - let newStatements = rewrittenCodeBlockItems(node.statements) - return node.with(\.statements, newStatements) + var result = node + result.statements = rewrittenCodeBlockItems(node.statements) + return result } /// Returns a list of code block items equivalent to the given list, but where any file-scoped @@ -40,7 +41,9 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { let newCodeBlockItems = codeBlockItems.map { codeBlockItem -> CodeBlockItemSyntax in switch codeBlockItem.item { case .decl(let decl): - return codeBlockItem.with(\.item, .decl(rewrittenDecl(decl))) + var result = codeBlockItem + result.item = .decl(rewrittenDecl(decl)) + return result default: return codeBlockItem } @@ -56,46 +59,25 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { return DeclSyntax(rewrittenIfConfigDecl(ifConfigDecl)) case .functionDecl(let functionDecl): - return DeclSyntax(rewrittenDecl( - functionDecl, - modifiers: functionDecl.modifiers, - factory: { functionDecl.with(\.modifiers, $0) })) + return DeclSyntax(rewrittenDecl(functionDecl)) case .variableDecl(let variableDecl): - return DeclSyntax(rewrittenDecl( - variableDecl, - modifiers: variableDecl.modifiers, - factory: { variableDecl.with(\.modifiers, $0) })) + return DeclSyntax(rewrittenDecl(variableDecl)) case .classDecl(let classDecl): - return DeclSyntax(rewrittenDecl( - classDecl, - modifiers: classDecl.modifiers, - factory: { classDecl.with(\.modifiers, $0) })) + return DeclSyntax(rewrittenDecl(classDecl)) case .structDecl(let structDecl): - return DeclSyntax(rewrittenDecl( - structDecl, - modifiers: structDecl.modifiers, - factory: { structDecl.with(\.modifiers, $0) })) + return DeclSyntax(rewrittenDecl(structDecl)) case .enumDecl(let enumDecl): - return DeclSyntax(rewrittenDecl( - enumDecl, - modifiers: enumDecl.modifiers, - factory: { enumDecl.with(\.modifiers, $0) })) + return DeclSyntax(rewrittenDecl(enumDecl)) case .protocolDecl(let protocolDecl): - return DeclSyntax(rewrittenDecl( - protocolDecl, - modifiers: protocolDecl.modifiers, - factory: { protocolDecl.with(\.modifiers, $0) })) + return DeclSyntax(rewrittenDecl(protocolDecl)) case .typeAliasDecl(let typealiasDecl): - return DeclSyntax(rewrittenDecl( - typealiasDecl, - modifiers: typealiasDecl.modifiers, - factory: { typealiasDecl.with(\.modifiers, $0) })) + return DeclSyntax(rewrittenDecl(typealiasDecl)) default: return decl @@ -113,12 +95,17 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { let newClauses = ifConfigDecl.clauses.map { clause -> IfConfigClauseSyntax in switch clause.elements { case .statements(let codeBlockItemList)?: - return clause.with(\.elements, .statements(rewrittenCodeBlockItems(codeBlockItemList))) + var result = clause + result.elements = .statements(rewrittenCodeBlockItems(codeBlockItemList)) + return result default: return clause } } - return ifConfigDecl.with(\.clauses, IfConfigClauseListSyntax(newClauses)) + + var result = ifConfigDecl + result.clauses = IfConfigClauseListSyntax(newClauses) + return result } /// Returns a rewritten version of the given declaration if its modifier list contains `private` @@ -133,39 +120,42 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { /// - factory: A reference to the `decl`'s `withModifiers` instance method that is called to /// rewrite the node if needed. /// - Returns: A new node if the modifiers were rewritten, or the original node if not. - private func rewrittenDecl( - _ decl: DeclType, - modifiers: DeclModifierListSyntax?, - factory: (DeclModifierListSyntax) -> DeclType + private func rewrittenDecl( + _ decl: DeclType ) -> DeclType { - let invalidAccess: TokenKind - let validAccess: TokenKind + let invalidAccess: Keyword + let validAccess: Keyword let diagnostic: Finding.Message switch context.configuration.fileScopedDeclarationPrivacy.accessLevel { case .private: - invalidAccess = .keyword(.fileprivate) - validAccess = .keyword(.private) + invalidAccess = .fileprivate + validAccess = .private diagnostic = .replaceFileprivateWithPrivate case .fileprivate: - invalidAccess = .keyword(.private) - validAccess = .keyword(.fileprivate) + invalidAccess = .private + validAccess = .fileprivate diagnostic = .replacePrivateWithFileprivate } - guard let modifiers = modifiers, modifiers.has(modifier: invalidAccess) else { + guard decl.modifiers.contains(anyOf: [invalidAccess]) else { return decl } - let newModifiers = modifiers.map { modifier -> DeclModifierSyntax in + let newModifiers = decl.modifiers.map { modifier -> DeclModifierSyntax in + var modifier = modifier + let name = modifier.name - if name.tokenKind == invalidAccess { + if case .keyword(invalidAccess) = name.tokenKind { diagnose(diagnostic, on: name) - return modifier.with(\.name, name.with(\.tokenKind, validAccess)) + modifier.name.tokenKind = .keyword(validAccess) } return modifier } - return factory(DeclModifierListSyntax(newModifiers)) + + var result = decl + result.modifiers = DeclModifierListSyntax(newModifiers) + return result } } diff --git a/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift index af190f1d7..5c5af4173 100644 --- a/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift @@ -24,7 +24,7 @@ public final class FullyIndirectEnum: SyntaxFormatRule { public override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { let enumMembers = node.memberBlock.members - guard !node.modifiers.has(modifier: "indirect"), + guard !node.modifiers.contains(anyOf: [.indirect]), case let indirectModifiers = indirectModifiersIfAllCasesIndirect(in: enumMembers), !indirectModifiers.isEmpty else { @@ -44,15 +44,18 @@ public final class FullyIndirectEnum: SyntaxFormatRule { let newMembers = enumMembers.map { (member: MemberBlockItemSyntax) -> MemberBlockItemSyntax in guard let caseMember = member.decl.as(EnumCaseDeclSyntax.self), - caseMember.modifiers.has(modifier: "indirect"), + caseMember.modifiers.contains(anyOf: [.indirect]), let firstModifier = caseMember.modifiers.first else { return member } - let newCase = caseMember.with(\.modifiers, caseMember.modifiers.remove(name: "indirect")) - let formattedCase = rearrangeLeadingTrivia(firstModifier.leadingTrivia, on: newCase) - return member.with(\.decl, DeclSyntax(formattedCase)) + var newCase = caseMember + newCase.modifiers.remove(anyOf: [.indirect]) + + var newMember = member + newMember.decl = DeclSyntax(rearrangeLeadingTrivia(firstModifier.leadingTrivia, on: newCase)) + return newMember } // If the `indirect` keyword being added would be the first token in the decl, we need to move @@ -73,11 +76,9 @@ public final class FullyIndirectEnum: SyntaxFormatRule { name: TokenSyntax.identifier( "indirect", leadingTrivia: leadingTrivia, trailingTrivia: .spaces(1)), detail: nil) - let newMemberBlock = node.memberBlock.with(\.members, MemberBlockItemListSyntax(newMembers)) - return DeclSyntax( - newEnumDecl - .with(\.modifiers, newEnumDecl.modifiers + [newModifier]) - .with(\.memberBlock, newMemberBlock)) + newEnumDecl.modifiers = newEnumDecl.modifiers + [newModifier] + newEnumDecl.memberBlock.members = MemberBlockItemListSyntax(newMembers) + return DeclSyntax(newEnumDecl) } /// Returns a value indicating whether all enum cases in the given list are indirect. diff --git a/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift b/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift index ad0d3f0dd..af5428cf2 100644 --- a/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift +++ b/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift @@ -59,11 +59,8 @@ public final class GroupNumericLiterals: SyntaxFormatRule { } newDigits = isNegative ? "-" + newDigits : newDigits - let result = node.with(\.literal, - TokenSyntax.integerLiteral( - newDigits, - leadingTrivia: node.literal.leadingTrivia, - trailingTrivia: node.literal.trailingTrivia)) + var result = node + result.literal.tokenKind = .integerLiteral(newDigits) return ExprSyntax(result) } diff --git a/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift index 22e8a3513..5c7c40912 100644 --- a/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift +++ b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift @@ -34,7 +34,7 @@ public final class NeverForceUnwrap: SyntaxLintRule { public override func visit(_ node: ForceUnwrapExprSyntax) -> SyntaxVisitorContinueKind { guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren } - diagnose(.doNotForceUnwrap(name: node.expression.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description), on: node) + diagnose(.doNotForceUnwrap(name: node.expression.trimmedDescription), on: node) return .skipChildren } @@ -44,7 +44,7 @@ public final class NeverForceUnwrap: SyntaxLintRule { guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren } guard let questionOrExclamation = node.questionOrExclamationMark else { return .skipChildren } guard questionOrExclamation.tokenKind == .exclamationMark else { return .skipChildren } - diagnose(.doNotForceCast(name: node.type.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description), on: node) + diagnose(.doNotForceCast(name: node.type.trimmedDescription), on: node) return .skipChildren } } diff --git a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift index 6977be832..fe9ad048d 100644 --- a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -56,8 +56,7 @@ public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule { private func diagnoseImplicitWrapViolation(_ type: TypeSyntax) { guard let violation = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) else { return } diagnose( - .doNotUseImplicitUnwrapping( - identifier: violation.wrappedType.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description), on: type) + .doNotUseImplicitUnwrapping(identifier: violation.wrappedType.trimmedDescription), on: type) } } diff --git a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift index e1dac51b2..a7eb2f7c2 100644 --- a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift @@ -22,60 +22,57 @@ import SwiftSyntax @_spi(Rules) public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { public override func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax { - guard !node.modifiers.isEmpty else { return DeclSyntax(node) } - guard let accessKeyword = node.modifiers.accessLevelModifier else { return DeclSyntax(node) } + guard + let accessKeyword = node.modifiers.accessLevelModifier, + case .keyword(let keyword) = accessKeyword.name.tokenKind + else { + return DeclSyntax(node) + } + + var result = node - let keywordKind = accessKeyword.name.tokenKind - switch keywordKind { + switch keyword { // Public, private, or fileprivate keywords need to be moved to members - case .keyword(.public), .keyword(.private), .keyword(.fileprivate): + case .public, .private, .fileprivate: // 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. - let accessKeywordToAdd: DeclModifierSyntax + var accessKeywordToAdd = accessKeyword let message: Finding.Message - if keywordKind == .keyword(.private) { - accessKeywordToAdd - = accessKeyword.with(\.name, accessKeyword.name.with(\.tokenKind, .keyword(.fileprivate))) + if keyword == .private { + accessKeywordToAdd.name.tokenKind = .keyword(.fileprivate) message = .moveAccessKeywordAndMakeFileprivate(keyword: accessKeyword.name.text) } else { - accessKeywordToAdd = accessKeyword message = .moveAccessKeyword(keyword: accessKeyword.name.text) } - let (newMemberBlock, notes) = addMemberAccessKeywords( - memDeclBlock: node.memberBlock, keyword: accessKeywordToAdd) + let (newMembers, notes) = + addMemberAccessKeyword(accessKeywordToAdd, toMembersIn: node.memberBlock) diagnose(message, on: accessKeyword, notes: notes) - let newMembers = MemberBlockSyntax( - leftBrace: node.memberBlock.leftBrace, - members: newMemberBlock, - rightBrace: node.memberBlock.rightBrace) - var newKeyword = node.extensionKeyword - newKeyword.leadingTrivia = accessKeyword.leadingTrivia - let result = node.with(\.memberBlock, newMembers) - .with(\.modifiers, node.modifiers.remove(name: accessKeyword.name.text)) - .with(\.extensionKeyword, newKeyword) + result.modifiers.remove(anyOf: [keyword]) + result.extensionKeyword.leadingTrivia = accessKeyword.leadingTrivia + result.memberBlock.members = newMembers return DeclSyntax(result) // Internal keyword redundant, delete - case .keyword(.internal): + case .internal: diagnose(.removeRedundantAccessKeyword, on: accessKeyword) - var newKeyword = node.extensionKeyword - newKeyword.leadingTrivia = accessKeyword.leadingTrivia - let result = node.with(\.modifiers, node.modifiers.remove(name: accessKeyword.name.text)) - .with(\.extensionKeyword, newKeyword) + + result.modifiers.remove(anyOf: [keyword]) + result.extensionKeyword.leadingTrivia = accessKeyword.leadingTrivia return DeclSyntax(result) default: break } - return DeclSyntax(node) + + return DeclSyntax(result) } // Adds given keyword to all members in declaration block - private func addMemberAccessKeywords( - memDeclBlock: MemberBlockSyntax, - keyword: DeclModifierSyntax + private func addMemberAccessKeyword( + _ keyword: DeclModifierSyntax, + toMembersIn memberBlock: MemberBlockSyntax ) -> (MemberBlockItemListSyntax, [Finding.Note]) { var newMembers: [MemberBlockItemSyntax] = [] var notes: [Finding.Note] = [] @@ -83,7 +80,7 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { var formattedKeyword = keyword formattedKeyword.leadingTrivia = [] - for memberItem in memDeclBlock.members { + for memberItem in memberBlock.members { let member = memberItem.decl guard let modifiers = member.asProtocol(WithModifiersSyntax.self)?.modifiers, @@ -95,13 +92,16 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { continue } - newMembers.append(memberItem.with(\.decl, newDecl)) + var newItem = memberItem + newItem.decl = newDecl + newMembers.append(newItem) // If it already had an explicit access modifier, don't leave a note. if modifiers.accessLevelModifier == nil { notes.append(Finding.Note( message: .addModifierToExtensionMember(keyword: formattedKeyword.name.text), - location: Finding.Location(member.startLocation(converter: context.sourceLocationConverter)) + location: + Finding.Location(member.startLocation(converter: context.sourceLocationConverter)) )) } } diff --git a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift index af760cfdb..3ef70a287 100644 --- a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift @@ -43,13 +43,13 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { for item in node { // Make sure to visit recursively so that any nested decls get processed first. - let newItem = visit(item) + let visitedItem = visit(item) // Rewrite any `return ` expressions as `return`. - switch newItem.item { + switch visitedItem.item { case .stmt(let stmt): guard - let returnStmt = stmt.as(ReturnStmtSyntax.self), + var returnStmt = stmt.as(ReturnStmtSyntax.self), let assignmentExpr = assignmentExpression(from: returnStmt) else { // Head to the default case where we just keep the original item. @@ -58,25 +58,21 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { // Move the leading trivia from the `return` statement to the new assignment statement, // since that's a more sensible place than between the two. - newItems.append( - CodeBlockItemSyntax( - item: .expr(ExprSyntax(assignmentExpr)), - semicolon: nil - ) - .with( - \.leadingTrivia, - (returnStmt.leadingTrivia) + (assignmentExpr.leadingTrivia)) - .with(\.trailingTrivia, [])) - newItems.append( - CodeBlockItemSyntax( - item: .stmt(StmtSyntax(returnStmt.with(\.expression, nil))), - semicolon: nil - ) - .with(\.leadingTrivia, [.newlines(1)]) - .with(\.trailingTrivia, returnStmt.trailingTrivia.withoutLeadingSpaces())) + var assignmentItem = CodeBlockItemSyntax(item: .expr(ExprSyntax(assignmentExpr))) + assignmentItem.leadingTrivia = returnStmt.leadingTrivia + assignmentExpr.leadingTrivia + assignmentItem.trailingTrivia = [] + + let trailingTrivia = returnStmt.trailingTrivia.withoutLeadingSpaces() + returnStmt.expression = nil + var returnItem = CodeBlockItemSyntax(item: .stmt(StmtSyntax(returnStmt))) + returnItem.leadingTrivia = [.newlines(1)] + returnItem.trailingTrivia = trailingTrivia + + newItems.append(assignmentItem) + newItems.append(returnItem) default: - newItems.append(newItem) + newItems.append(visitedItem) } } diff --git a/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift index f2cf72648..a7a48d21f 100644 --- a/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift +++ b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift @@ -181,19 +181,23 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { // comma. Then, we need to add a trailing comma to the last one, since it will be followed by // more items. newCaseItems.append(contentsOf: label.caseItems.dropLast()) - newCaseItems.append( - label.caseItems.last!.with( - \.trailingComma, - TokenSyntax.commaToken(trailingTrivia: .spaces(1)))) + + var lastItem = label.caseItems.last! + lastItem.trailingComma = TokenSyntax.commaToken(trailingTrivia: [.spaces(1)]) + newCaseItems.append(lastItem) } newCaseItems.append(contentsOf: labels.last!.caseItems) - let newCase = cases.last!.with(\.label, .case( - labels.last!.with(\.caseItems, SwitchCaseItemListSyntax(newCaseItems)))) + var lastLabel = labels.last! + lastLabel.caseItems = SwitchCaseItemListSyntax(newCaseItems) + + var lastCase = cases.last! + lastCase.label = .case(lastLabel) // Only the first violation case can have displaced trivia, because any non-whitespace // trivia in the other violation cases would've prevented collapsing. - return newCase.with(\.leadingTrivia, cases.first!.leadingTrivia.withoutLastLine() + newCase.leadingTrivia) + lastCase.leadingTrivia = cases.first!.leadingTrivia.withoutLastLine() + lastCase.leadingTrivia + return lastCase } } diff --git a/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift b/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift index 1bc5428a6..d0cd85d21 100644 --- a/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift +++ b/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift @@ -25,48 +25,52 @@ import SwiftSyntax public final class NoLabelsInCasePatterns: SyntaxFormatRule { public override func visit(_ node: SwitchCaseLabelSyntax) -> SwitchCaseLabelSyntax { var newCaseItems: [SwitchCaseItemSyntax] = [] + for item in node.caseItems { - guard let expPat = item.pattern.as(ExpressionPatternSyntax.self) else { - newCaseItems.append(item) - continue - } - guard let funcCall = expPat.expression.as(FunctionCallExprSyntax.self) else { + guard + var exprPattern = item.pattern.as(ExpressionPatternSyntax.self), + var funcCall = exprPattern.expression.as(FunctionCallExprSyntax.self) + else { newCaseItems.append(item) continue } // Search function call argument list for violations - var newArgs: [LabeledExprSyntax] = [] + var newArguments = LabeledExprListSyntax() for argument in funcCall.arguments { - guard let label = argument.label else { - newArgs.append(argument) - continue - } - guard let unresolvedPat = argument.expression.as(PatternExprSyntax.self), + guard + let label = argument.label, + let unresolvedPat = argument.expression.as(PatternExprSyntax.self), let valueBinding = unresolvedPat.pattern.as(ValueBindingPatternSyntax.self) else { - newArgs.append(argument) + newArguments.append(argument) continue } // Remove label if it's the same as the value identifier - let name = valueBinding.pattern.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description + let name = valueBinding.pattern.trimmedDescription guard name == label.text else { - newArgs.append(argument) + newArguments.append(argument) continue } diagnose(.removeRedundantLabel(name: name), on: label) - newArgs.append(argument.with(\.label, nil).with(\.colon, nil)) + + var newArgument = argument + newArgument.label = nil + newArgument.colon = nil + newArguments.append(newArgument) } - let newArgList = LabeledExprListSyntax(newArgs) - let newFuncCall = funcCall.with(\.arguments, newArgList) - let newExpPat = expPat.with(\.expression, ExprSyntax(newFuncCall)) - let newItem = item.with(\.pattern, PatternSyntax(newExpPat)) + var newItem = item + funcCall.arguments = newArguments + exprPattern.expression = ExprSyntax(funcCall) + newItem.pattern = PatternSyntax(exprPattern) newCaseItems.append(newItem) } - let newCaseItemList = SwitchCaseItemListSyntax(newCaseItems) - return node.with(\.caseItems, newCaseItemList) + + var result = node + result.caseItems = SwitchCaseItemListSyntax(newCaseItems) + return result } } diff --git a/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift index 75a334d98..08b1d4b4f 100644 --- a/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift @@ -26,75 +26,97 @@ import SwiftSyntax /// call with a trailing closure. @_spi(Rules) public final class NoParensAroundConditions: SyntaxFormatRule { - private func extractExpr(_ tuple: TupleExprSyntax) -> ExprSyntax { - assert(tuple.elements.count == 1) - let expr = tuple.elements.first!.expression - - // If the condition is a function with a trailing closure or if it's an immediately called - // closure, removing the outer set of parentheses introduces a parse ambiguity. - if let fnCall = expr.as(FunctionCallExprSyntax.self) { - if fnCall.trailingClosure != nil { - // Leave parentheses around call with trailing closure. - return ExprSyntax(tuple) - } else if fnCall.calledExpression.as(ClosureExprSyntax.self) != nil { - // Leave parentheses around immediately called closure. - return ExprSyntax(tuple) - } - } - - diagnose(.removeParensAroundExpression, on: tuple.leftParen) - - guard - let visitedTuple = visit(tuple).as(TupleExprSyntax.self), - var visitedExpr = visitedTuple.elements.first?.expression - else { - return expr - } - visitedExpr.leadingTrivia = visitedTuple.leftParen.leadingTrivia - visitedExpr.trailingTrivia = visitedTuple.rightParen.trailingTrivia - return visitedExpr - } - public override func visit(_ node: IfExprSyntax) -> ExprSyntax { - let conditions = visit(node.conditions) - var result = node.with(\.ifKeyword, node.ifKeyword.withOneTrailingSpace()) - .with(\.conditions, conditions) - .with(\.body, visit(node.body)) + var result = node + result.ifKeyword.trailingTrivia = [.spaces(1)] + result.conditions = visit(node.conditions) + result.body = visit(node.body) if let elseBody = node.elseBody { - result = result.with(\.elseBody, visit(elseBody)) + result.elseBody = visit(elseBody) } return ExprSyntax(result) } public override func visit(_ node: ConditionElementSyntax) -> ConditionElementSyntax { - guard let tup = node.condition.as(TupleExprSyntax.self), - tup.elements.firstAndOnly != nil + guard + case .expression(let condition) = node.condition, + let newExpr = minimalSingleExpression(condition) else { return super.visit(node) } - return node.with(\.condition, .expression(extractExpr(tup))) + + var result = node + result.condition = .expression(newExpr) + return result + } + + public override func visit(_ node: GuardStmtSyntax) -> StmtSyntax { + var result = node + result.guardKeyword.trailingTrivia = [.spaces(1)] + result.conditions = visit(node.conditions) + result.body = visit(node.body) + return StmtSyntax(result) } public override func visit(_ node: SwitchExprSyntax) -> ExprSyntax { - guard let tup = node.subject.as(TupleExprSyntax.self), - tup.elements.firstAndOnly != nil - else { + guard let newSubject = minimalSingleExpression(node.subject) else { return super.visit(node) } - return ExprSyntax( - node.with(\.subject, extractExpr(tup)).with(\.cases, visit(node.cases))) + + var result = node + result.switchKeyword.trailingTrivia = [.spaces(1)] + result.subject = newSubject + result.cases = visit(node.cases) + return ExprSyntax(result) } public override func visit(_ node: RepeatStmtSyntax) -> StmtSyntax { - guard let tup = node.condition.as(TupleExprSyntax.self), - tup.elements.firstAndOnly != nil - else { + guard let newCondition = minimalSingleExpression(node.condition) else { return super.visit(node) } - let newNode = node.with(\.condition, extractExpr(tup)) - .with(\.whileKeyword, node.whileKeyword.withOneTrailingSpace()) - .with(\.body, visit(node.body)) - return StmtSyntax(newNode) + + var result = node + result.whileKeyword.trailingTrivia = [.spaces(1)] + result.condition = newCondition + result.body = visit(node.body) + return StmtSyntax(result) + } + + public override func visit(_ node: WhileStmtSyntax) -> StmtSyntax { + var result = node + result.whileKeyword.trailingTrivia = [.spaces(1)] + result.conditions = visit(node.conditions) + result.body = visit(node.body) + return StmtSyntax(result) + } + + private func minimalSingleExpression(_ original: ExprSyntax) -> ExprSyntax? { + guard + let tuple = original.as(TupleExprSyntax.self), + tuple.elements.count == 1, + let expr = tuple.elements.first?.expression + else { + return nil + } + + // If the condition is a function with a trailing closure or if it's an immediately called + // closure, removing the outer set of parentheses introduces a parse ambiguity. + if let fnCall = expr.as(FunctionCallExprSyntax.self) { + if fnCall.trailingClosure != nil { + // Leave parentheses around call with trailing closure. + return ExprSyntax(tuple) + } else if fnCall.calledExpression.as(ClosureExprSyntax.self) != nil { + // Leave parentheses around immediately called closure. + return ExprSyntax(tuple) + } + } + + diagnose(.removeParensAroundExpression, on: tuple.leftParen) + + var visitedExpr = visit(expr) + visitedExpr.leadingTrivia = tuple.leftParen.leadingTrivia + visitedExpr.trailingTrivia = tuple.rightParen.trailingTrivia + return visitedExpr } } diff --git a/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift b/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift index cda2ef613..103dfaab6 100644 --- a/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift +++ b/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift @@ -24,16 +24,31 @@ public final class NoVoidReturnOnFunctionSignature: SyntaxFormatRule { /// it for closure signatures, because that may introduce an ambiguity when closure signatures /// are inferred. public override func visit(_ node: FunctionSignatureSyntax) -> FunctionSignatureSyntax { - if let returnType = node.returnClause?.type.as(IdentifierTypeSyntax.self), returnType.name.text == "Void" { - diagnose(.removeRedundantReturn("Void"), on: returnType) - return node.with(\.returnClause, nil) + guard let returnType = node.returnClause?.type else { return node } + + if let identifierType = returnType.as(IdentifierTypeSyntax.self), + identifierType.name.text == "Void", + identifierType.genericArgumentClause?.arguments.isEmpty ?? true + { + diagnose(.removeRedundantReturn("Void"), on: identifierType) + return removingReturnClause(from: node) } - if let tupleReturnType = node.returnClause?.type.as(TupleTypeSyntax.self), tupleReturnType.elements.isEmpty { - diagnose(.removeRedundantReturn("()"), on: tupleReturnType) - return node.with(\.returnClause, nil) + if let tupleType = returnType.as(TupleTypeSyntax.self), tupleType.elements.isEmpty { + diagnose(.removeRedundantReturn("()"), on: tupleType) + return removingReturnClause(from: node) } + return node } + + /// Returns a copy of the given function signature with the return clause removed. + private func removingReturnClause(from signature: FunctionSignatureSyntax) + -> FunctionSignatureSyntax + { + var result = signature + result.returnClause = nil + return result + } } extension Finding.Message { diff --git a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift index 31966cc0e..a73914916 100644 --- a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift +++ b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift @@ -82,7 +82,7 @@ public final class OmitExplicitReturns: SyntaxFormatRule { // We are assuming valid Swift code here where only // one `get { ... }` is allowed. switch accessorBlock.accessors { - case .accessors(let accessors): + case .accessors(var accessors): guard var getter = accessors.filter({ $0.accessorSpecifier.tokenKind == .keyword(.get) }).first else { @@ -104,8 +104,9 @@ public final class OmitExplicitReturns: SyntaxFormatRule { diagnose(.omitReturnStatement, on: returnStmt, severity: .refactoring) + accessors[getterAt] = getter var newBlock = accessorBlock - newBlock.accessors = .accessors(accessors.with(\.[getterAt], getter)) + newBlock.accessors = .accessors(accessors) return newBlock case .getter(let getter): diff --git a/Sources/SwiftFormat/Rules/OneCasePerLine.swift b/Sources/SwiftFormat/Rules/OneCasePerLine.swift index f67193cee..4b4778c34 100644 --- a/Sources/SwiftFormat/Rules/OneCasePerLine.swift +++ b/Sources/SwiftFormat/Rules/OneCasePerLine.swift @@ -56,12 +56,10 @@ public final class OneCasePerLine: SyntaxFormatRule { /// This will return nil if there are no elements collected since the last time this was called /// (or the collector was created). mutating func makeCaseDeclAndReset() -> EnumCaseDeclSyntax? { - guard let last = elements.last else { return nil } + guard !elements.isEmpty else { return nil } // Remove the trailing comma on the final element, if there was one. - if last.trailingComma != nil { - elements[elements.count - 1] = last.with(\.trailingComma, nil) - } + elements[elements.count - 1].trailingComma = nil defer { elements.removeAll() } return makeCaseDeclFromBasis(elements: elements) @@ -70,14 +68,15 @@ public final class OneCasePerLine: SyntaxFormatRule { /// Creates and returns a new `EnumCaseDeclSyntax` with the given elements, based on the current /// basis declaration, and updates the comment preserving state if needed. mutating func makeCaseDeclFromBasis(elements: [EnumCaseElementSyntax]) -> EnumCaseDeclSyntax { - let caseDecl = basis.with(\.elements, EnumCaseElementListSyntax(elements)) + var caseDecl = basis + caseDecl.elements = EnumCaseElementListSyntax(elements) if shouldKeepLeadingTrivia { shouldKeepLeadingTrivia = false // We don't bother preserving any indentation because the pretty printer will fix that up. // All we need to do here is ensure that there is a newline. - basis = basis.with(\.leadingTrivia, Trivia.newlines(1)) + basis.leadingTrivia = Trivia.newlines(1) } return caseDecl @@ -106,12 +105,18 @@ public final class OneCasePerLine: SyntaxFormatRule { diagnose(.moveAssociatedOrRawValueCase(name: element.name.text), on: element) if let caseDeclForCollectedElements = collector.makeCaseDeclAndReset() { - newMembers.append(member.with(\.decl, DeclSyntax(caseDeclForCollectedElements))) + var newMember = member + newMember.decl = DeclSyntax(caseDeclForCollectedElements) + newMembers.append(newMember) } - let separatedCaseDecl = - collector.makeCaseDeclFromBasis(elements: [element.with(\.trailingComma, nil)]) - newMembers.append(member.with(\.decl, DeclSyntax(separatedCaseDecl))) + var basisElement = element + basisElement.trailingComma = nil + let separatedCaseDecl = collector.makeCaseDeclFromBasis(elements: [basisElement]) + + var newMember = member + newMember.decl = DeclSyntax(separatedCaseDecl) + newMembers.append(newMember) } else { collector.addElement(element) } @@ -119,12 +124,15 @@ public final class OneCasePerLine: SyntaxFormatRule { // Make sure to emit any trailing collected elements. if let caseDeclForCollectedElements = collector.makeCaseDeclAndReset() { - newMembers.append(member.with(\.decl, DeclSyntax(caseDeclForCollectedElements))) + var newMember = member + newMember.decl = DeclSyntax(caseDeclForCollectedElements) + newMembers.append(newMember) } } - let newMemberBlock = node.memberBlock.with(\.members, MemberBlockItemListSyntax(newMembers)) - return DeclSyntax(node.with(\.memberBlock, newMemberBlock)) + var result = node + result.memberBlock.members = MemberBlockItemListSyntax(newMembers) + return DeclSyntax(result) } } diff --git a/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift index 002f2ab54..825ff232a 100644 --- a/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift +++ b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift @@ -141,7 +141,9 @@ private struct VariableDeclSplitter { // it's an initializer following other un-flushed lone identifier // bindings, that's not valid Swift. But in either case, we'll flush // them as a single decl. - bindingQueue.append(binding.with(\.trailingComma, nil)) + var newBinding = binding + newBinding.trailingComma = nil + bindingQueue.append(newBinding) flushRemaining() } else if let typeAnnotation = binding.typeAnnotation { bindingQueue.append(binding) @@ -172,8 +174,8 @@ private struct VariableDeclSplitter { private mutating func flushRemaining() { guard !bindingQueue.isEmpty else { return } - let newDecl = - varDecl.with(\.bindings, PatternBindingListSyntax(bindingQueue)) + var newDecl = varDecl! + newDecl.bindings = PatternBindingListSyntax(bindingQueue) nodes.append(generator(newDecl)) fixOriginalVarDeclTrivia() @@ -191,10 +193,12 @@ private struct VariableDeclSplitter { for binding in bindingQueue { assert(binding.initializer == nil) - let newBinding = - binding.with(\.trailingComma, nil).with(\.typeAnnotation, typeAnnotation) - let newDecl = - varDecl.with(\.bindings, PatternBindingListSyntax([newBinding])) + var newBinding = binding + newBinding.typeAnnotation = typeAnnotation + newBinding.trailingComma = nil + + var newDecl = varDecl! + newDecl.bindings = PatternBindingListSyntax([newBinding]) nodes.append(generator(newDecl)) fixOriginalVarDeclTrivia() diff --git a/Sources/SwiftFormat/Rules/OrderedImports.swift b/Sources/SwiftFormat/Rules/OrderedImports.swift index 55d1ffed8..969c18236 100644 --- a/Sources/SwiftFormat/Rules/OrderedImports.swift +++ b/Sources/SwiftFormat/Rules/OrderedImports.swift @@ -131,10 +131,8 @@ public final class OrderedImports: SyntaxFormatRule { formatAndAppend(linesSection: lines[lastSliceStartIndex.. ClosureSignatureSyntax { - guard let returnClause = node.returnClause, + guard + let returnClause = node.returnClause, let returnType = returnClause.type.as(TupleTypeSyntax.self), returnType.elements.count == 0 else { @@ -80,7 +81,14 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { closureParameterClause = node.parameterClause } let voidKeyword = makeVoidIdentifierType(toReplace: returnType) - return node.with(\.parameterClause, closureParameterClause).with(\.returnClause, returnClause.with(\.type, TypeSyntax(voidKeyword))) + + var newReturnClause = returnClause + newReturnClause.type = TypeSyntax(voidKeyword) + + var result = node + result.parameterClause = closureParameterClause + result.returnClause = newReturnClause + return result } /// Returns a value indicating whether the leading trivia of the given token contained any diff --git a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift index 4cf30d840..a66aafb17 100644 --- a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift @@ -93,17 +93,20 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // Even if we don't shorten this specific type that we're visiting, we may have rewritten // something in the generic argument list that we recursively visited, so return the original // node with that swapped out. - let result = node.with(\.genericArgumentClause, - genericArgumentClause.with(\.arguments, genericArgumentList)) + var newGenericArgumentClause = genericArgumentClause + newGenericArgumentClause.arguments = genericArgumentList + + var result = node + result.genericArgumentClause = newGenericArgumentClause return TypeSyntax(result) } public override func visit(_ node: GenericSpecializationExprSyntax) -> ExprSyntax { - // `SpecializeExpr`s are found in the syntax tree when a generic type is encountered in an - // expression context, such as `Array()`. In these situations, the corresponding array and - // dictionary shorthand nodes will be expression nodes, not type nodes, so we may need to - // translate the arguments inside the generic argument list---which are types---to the - // appropriate equivalent. + // `GenericSpecializationExprSyntax`s are found in the syntax tree when a generic type is + // encountered in an expression context, such as `Array()`. In these situations, the + // corresponding array and dictionary shorthand nodes will be expression nodes, not type nodes, + // so we may need to translate the arguments inside the generic argument list---which are + // types---to the appropriate equivalent. // Ignore nodes where the expression being specialized isn't a simple identifier. guard let expression = node.expression.as(DeclReferenceExprSyntax.self) else { @@ -124,8 +127,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // Ensure that all arguments in the clause are shortened and in the expected format by visiting // the argument list, first. - let genericArgumentList = - visit(node.genericArgumentClause.arguments) + let genericArgumentList = visit(node.genericArgumentClause.arguments) let (leadingTrivia, trailingTrivia) = boundaryTrivia(around: Syntax(node)) let newNode: ExprSyntax? @@ -178,8 +180,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { // Even if we don't shorten this specific expression that we're visiting, we may have // rewritten something in the generic argument list that we recursively visited, so return the // original node with that swapped out. - let result = node.with(\.genericArgumentClause, - node.genericArgumentClause.with(\.arguments, genericArgumentList)) + var result = node + result.genericArgumentClause.arguments = genericArgumentList return ExprSyntax(result) } diff --git a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift index 5175b6cf7..af46865c3 100644 --- a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift @@ -31,7 +31,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { let member = memberItem.decl // Collect all stored variables into a list if let varDecl = member.as(VariableDeclSyntax.self) { - guard !varDecl.modifiers.has(modifier: "static") else { continue } + guard !varDecl.modifiers.contains(anyOf: [.static]) else { continue } storedProperties.append(varDecl) // Collect any possible redundant initializers into a list } else if let initDecl = member.as(InitializerDeclSyntax.self) { diff --git a/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift index 1a5d1e2db..080aa1e2f 100644 --- a/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift @@ -115,8 +115,10 @@ fileprivate func updateWithWhereCondition( ) // Replace the where clause and extract the body from the IfStmt. - let newBody = node.body.with(\.statements, statements) - return node.with(\.whereClause, whereClause).with(\.body, newBody) + var result = node + result.whereClause = whereClause + result.body.statements = statements + return result } extension Finding.Message { diff --git a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift index e28fac949..770f2906d 100644 --- a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift @@ -235,4 +235,34 @@ final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { findings: [] ) } + + func testKeywordAlwaysHasTrailingSpace() { + assertFormatting( + NoParensAroundConditions.self, + input: """ + if1️⃣(x) {} + while2️⃣(x) {} + guard3️⃣(x),4️⃣(y),5️⃣(x == 3) else {} + repeat {} while6️⃣(x) + switch7️⃣(4) { default: break } + """, + expected: """ + if x {} + while x {} + guard x,y,x == 3 else {} + repeat {} while x + switch 4 { default: break } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the parentheses around this expression"), + FindingSpec("2️⃣", message: "remove the parentheses around this expression"), + FindingSpec("3️⃣", message: "remove the parentheses around this expression"), + FindingSpec("4️⃣", message: "remove the parentheses around this expression"), + FindingSpec("5️⃣", message: "remove the parentheses around this expression"), + FindingSpec("6️⃣", message: "remove the parentheses around this expression"), + FindingSpec("7️⃣", message: "remove the parentheses around this expression"), + ] + ) + + } } From 1c960a90ebed0ed623ac251949cac8b2df81a99d Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Sun, 3 Sep 2023 16:32:20 -0400 Subject: [PATCH 115/332] Consolidate some fairly specialized helpers. These were only used in one place and have implementations that are pretty specific to just the calling rule, so move them side-by-side with that rule and make them file-private. --- .../Core/AddModifierRewriter.swift | 183 ------------------ .../Core/ModifierListSyntax+Convenience.swift | 27 --- .../Core/VarDeclSyntax+Convenience.swift | 41 ---- .../NoAccessLevelOnExtensionDeclaration.swift | 99 ++++++++-- .../Rules/UseSynthesizedInitializer.swift | 29 +++ 5 files changed, 109 insertions(+), 270 deletions(-) delete mode 100644 Sources/SwiftFormat/Core/AddModifierRewriter.swift delete mode 100644 Sources/SwiftFormat/Core/VarDeclSyntax+Convenience.swift diff --git a/Sources/SwiftFormat/Core/AddModifierRewriter.swift b/Sources/SwiftFormat/Core/AddModifierRewriter.swift deleted file mode 100644 index b6319576e..000000000 --- a/Sources/SwiftFormat/Core/AddModifierRewriter.swift +++ /dev/null @@ -1,183 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -fileprivate final class AddModifierRewriter: SyntaxRewriter { - private let modifierKeyword: DeclModifierSyntax - - init(modifierKeyword: DeclModifierSyntax) { - self.modifierKeyword = modifierKeyword - } - - override func visit(_ node: VariableDeclSyntax) -> DeclSyntax { - // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced - // token. - guard !node.modifiers.isEmpty else { - let result = setOnlyModifier(in: node, keywordKeypath: \.bindingSpecifier) - return DeclSyntax(result) - } - var node = node - - // If variable already has an accessor keyword, skip (do not overwrite) - guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - - // Put accessor keyword before the first modifier keyword in the declaration - node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) - return DeclSyntax(node) - } - - override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax { - // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced - // token. - guard !node.modifiers.isEmpty else { - let result = setOnlyModifier(in: node, keywordKeypath: \.funcKeyword) - return DeclSyntax(result) - } - guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - var node = node - node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) - return DeclSyntax(node) - } - - override func visit(_ node: AssociatedTypeDeclSyntax) -> DeclSyntax { - // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced - // token. - guard !node.modifiers.isEmpty else { - let result = setOnlyModifier(in: node, keywordKeypath: \.associatedtypeKeyword) - return DeclSyntax(result) - } - guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - var node = node - node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) - return DeclSyntax(node) - } - - override func visit(_ node: ClassDeclSyntax) -> DeclSyntax { - // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced - // token. - guard !node.modifiers.isEmpty else { - let result = setOnlyModifier(in: node, keywordKeypath: \.classKeyword) - return DeclSyntax(result) - } - guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - var node = node - node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) - return DeclSyntax(node) - } - - override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { - // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced - // token. - guard !node.modifiers.isEmpty else { - let result = setOnlyModifier(in: node, keywordKeypath: \.enumKeyword) - return DeclSyntax(result) - } - guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - var node = node - node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) - return DeclSyntax(node) - } - - override func visit(_ node: ProtocolDeclSyntax) -> DeclSyntax { - // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced - // token. - - guard !node.modifiers.isEmpty else { - let result = setOnlyModifier(in: node, keywordKeypath: \.protocolKeyword) - return DeclSyntax(result) - } - guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - var node = node - node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) - return DeclSyntax(node) - } - - override func visit(_ node: StructDeclSyntax) -> DeclSyntax { - // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced - // token. - guard !node.modifiers.isEmpty else { - let result = setOnlyModifier(in: node, keywordKeypath: \.structKeyword) - return DeclSyntax(result) - } - guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - var node = node - node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) - return DeclSyntax(node) - } - - override func visit(_ node: TypeAliasDeclSyntax) -> DeclSyntax { - // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced - // token. - guard !node.modifiers.isEmpty else { - let result = setOnlyModifier(in: node, keywordKeypath: \.typealiasKeyword) - return DeclSyntax(result) - } - guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - var node = node - node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) - return DeclSyntax(node) - } - - override func visit(_ node: InitializerDeclSyntax) -> DeclSyntax { - // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced - // token. - guard !node.modifiers.isEmpty else { - let result = setOnlyModifier(in: node, keywordKeypath: \.initKeyword) - return DeclSyntax(result) - } - guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - var node = node - node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) - return DeclSyntax(node) - } - - override func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax { - // Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced - // token. - guard !node.modifiers.isEmpty else { - let result = setOnlyModifier(in: node, keywordKeypath: \.subscriptKeyword) - return DeclSyntax(result) - } - guard node.modifiers.accessLevelModifier == nil else { return DeclSyntax(node) } - var node = node - node.modifiers.triviaPreservingInsert(modifierKeyword, at: node.modifiers.startIndex) - return DeclSyntax(node) - } - - /// Moves trivia in the given node to correct the placement of potentially displaced trivia in the - /// node after the first modifier was added to the given node. The added modifier is assumed to be - /// the first and only modifier of the node. After the first modifier is added to a node, any - /// leading trivia on the token immediately after the modifier is considered displaced. This - /// method moves that displaced trivia onto the new modifier. When there is no displaced trivia, - /// this method does nothing and returns the given node as-is. - /// - Parameter node: A node that was updated to include a new modifier. - /// - Parameter modifiersProvider: A closure that returns all modifiers for the given node. - private func setOnlyModifier( - in node: NodeType, - keywordKeypath: WritableKeyPath - ) -> NodeType { - var node = node - var modifier = modifierKeyword - modifier.leadingTrivia = node[keyPath: keywordKeypath].leadingTrivia - node[keyPath: keywordKeypath].leadingTrivia = [] - node.modifiers = .init([modifier]) - return node - } -} - -func addModifier( - declaration: DeclSyntax, - modifierKeyword: DeclModifierSyntax -) -> Syntax { - return AddModifierRewriter(modifierKeyword: modifierKeyword).rewrite(Syntax(declaration)) -} diff --git a/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift b/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift index f63becd99..2a544b9d2 100644 --- a/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift +++ b/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift @@ -46,7 +46,6 @@ extension DeclModifierListSyntax { } } - /// Returns a copy of the modifier list with any of the modifiers in the given set removed. func removing(anyOf keywords: Set) -> DeclModifierListSyntax { return filter { @@ -56,30 +55,4 @@ extension DeclModifierListSyntax { } } } - - /// Inserts the given modifier into the list at a specific index. - /// - /// If the modifier is being inserted at the front of the list, the current front element's - /// leading trivia will be moved to the new element to preserve any leading comments and newlines. - mutating func triviaPreservingInsert( - _ modifier: DeclModifierSyntax, at index: SyntaxChildrenIndex - ) { - var modifier = modifier - modifier.trailingTrivia = [.spaces(1)] - - guard index == self.startIndex else { - self.insert(modifier, at: index) - return - } - guard var firstMod = first, let firstTok = firstMod.firstToken(viewMode: .sourceAccurate) else { - self.insert(modifier, at: index) - return - } - - modifier.leadingTrivia = firstTok.leadingTrivia - firstMod.leadingTrivia = [] - firstMod.trailingTrivia = [.spaces(1)] - self[self.startIndex] = firstMod - self.insert(modifier, at: self.startIndex) - } } diff --git a/Sources/SwiftFormat/Core/VarDeclSyntax+Convenience.swift b/Sources/SwiftFormat/Core/VarDeclSyntax+Convenience.swift deleted file mode 100644 index 9dddaa7e6..000000000 --- a/Sources/SwiftFormat/Core/VarDeclSyntax+Convenience.swift +++ /dev/null @@ -1,41 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -extension VariableDeclSyntax { - - /// Returns array of all identifiers listed in the declaration. - var identifiers: [IdentifierPatternSyntax] { - var ids: [IdentifierPatternSyntax] = [] - for binding in bindings { - guard let id = binding.pattern.as(IdentifierPatternSyntax.self) else { continue } - ids.append(id) - } - return ids - } - - /// Returns the first identifier. - var firstIdentifier: IdentifierPatternSyntax { - return identifiers[0] - } - - /// Returns the first type explicitly stated in the declaration, if present. - var firstType: TypeSyntax? { - return bindings.first?.typeAnnotation?.type - } - - /// Returns the first initializer clause, if present. - var firstInitializer: InitializerClauseSyntax? { - return bindings.first?.initializer - } -} diff --git a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift index a7eb2f7c2..b9be2483e 100644 --- a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift @@ -71,40 +71,35 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { // Adds given keyword to all members in declaration block private func addMemberAccessKeyword( - _ keyword: DeclModifierSyntax, + _ modifier: DeclModifierSyntax, toMembersIn memberBlock: MemberBlockSyntax ) -> (MemberBlockItemListSyntax, [Finding.Note]) { var newMembers: [MemberBlockItemSyntax] = [] var notes: [Finding.Note] = [] - var formattedKeyword = keyword - formattedKeyword.leadingTrivia = [] - for memberItem in memberBlock.members { - let member = memberItem.decl + let decl = memberItem.decl guard - let modifiers = member.asProtocol(WithModifiersSyntax.self)?.modifiers, - // addModifier relocates trivia for any token(s) displaced by the new modifier. - let newDecl = addModifier(declaration: member, modifierKeyword: formattedKeyword) - .as(DeclSyntax.self) + let modifiers = decl.asProtocol(WithModifiersSyntax.self)?.modifiers, + modifiers.accessLevelModifier == nil else { newMembers.append(memberItem) continue } + // Create a note associated with each declaration that needs to have an access level modifier + // added to it. + notes.append(Finding.Note( + message: .addModifierToExtensionMember(keyword: modifier.name.text), + location: + Finding.Location(decl.startLocation(converter: context.sourceLocationConverter)) + )) + var newItem = memberItem - newItem.decl = newDecl + newItem.decl = applyingAccessModifierIfNone(modifier, to: decl) newMembers.append(newItem) - - // If it already had an explicit access modifier, don't leave a note. - if modifiers.accessLevelModifier == nil { - notes.append(Finding.Note( - message: .addModifierToExtensionMember(keyword: formattedKeyword.name.text), - location: - Finding.Location(member.startLocation(converter: context.sourceLocationConverter)) - )) - } } + return (MemberBlockItemListSyntax(newMembers), notes) } } @@ -129,3 +124,69 @@ extension Finding.Message { "add '\(keyword)' access modifier to this declaration" } } + +/// Adds `modifier` to `decl` if it doesn't already have an explicit access level modifier and +/// returns the new declaration. +/// +/// If `decl` already has an access level modifier, it is returned unchanged. +private func applyingAccessModifierIfNone( + _ modifier: DeclModifierSyntax, + to decl: DeclSyntax +) -> DeclSyntax { + switch Syntax(decl).as(SyntaxEnum.self) { + case .actorDecl(let actorDecl): + return applyingAccessModifierIfNone(modifier, to: actorDecl, declKeywordKeyPath: \.actorKeyword) + case .classDecl(let classDecl): + return applyingAccessModifierIfNone(modifier, to: classDecl, declKeywordKeyPath: \.classKeyword) + case .enumDecl(let enumDecl): + return applyingAccessModifierIfNone(modifier, to: enumDecl, declKeywordKeyPath: \.enumKeyword) + case .initializerDecl(let initDecl): + return applyingAccessModifierIfNone(modifier, to: initDecl, declKeywordKeyPath: \.initKeyword) + case .functionDecl(let funcDecl): + return applyingAccessModifierIfNone(modifier, to: funcDecl, declKeywordKeyPath: \.funcKeyword) + case .structDecl(let structDecl): + return applyingAccessModifierIfNone( + modifier, to: structDecl, declKeywordKeyPath: \.structKeyword) + case .subscriptDecl(let subscriptDecl): + return applyingAccessModifierIfNone( + modifier, to: subscriptDecl, declKeywordKeyPath: \.subscriptKeyword) + case .typeAliasDecl(let typeAliasDecl): + return applyingAccessModifierIfNone( + modifier, to: typeAliasDecl, declKeywordKeyPath: \.typealiasKeyword) + case .variableDecl(let varDecl): + return applyingAccessModifierIfNone( + modifier, to: varDecl, declKeywordKeyPath: \.bindingSpecifier) + default: + return decl + } +} + +private func applyingAccessModifierIfNone( + _ modifier: DeclModifierSyntax, + to decl: Decl, + declKeywordKeyPath: WritableKeyPath +) -> DeclSyntax { + // If there's already an access modifier among the modifier list, bail out. + guard decl.modifiers.accessLevelModifier == nil else { return DeclSyntax(decl) } + + var result = decl + var modifier = modifier + modifier.trailingTrivia = [.spaces(1)] + + guard var firstModifier = decl.modifiers.first else { + // If there are no modifiers at all, add the one being requested, moving the leading trivia + // from the decl keyword to that modifier (to preserve leading comments, newlines, etc.). + modifier.leadingTrivia = decl[keyPath: declKeywordKeyPath].leadingTrivia + result[keyPath: declKeywordKeyPath].leadingTrivia = [] + result.modifiers = .init([modifier]) + return DeclSyntax(result) + } + + // Otherwise, insert the modifier at the front of the modifier list, moving the (original) first + // modifier's leading trivia to the new one (to preserve leading comments, newlines, etc.). + modifier.leadingTrivia = firstModifier.leadingTrivia + firstModifier.leadingTrivia = [] + result.modifiers[result.modifiers.startIndex] = firstModifier + result.modifiers.insert(modifier, at: result.modifiers.startIndex) + return DeclSyntax(result) +} diff --git a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift index af46865c3..52a2577ec 100644 --- a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift @@ -220,3 +220,32 @@ fileprivate func synthesizedInitAccessLevel(using properties: [VariableDeclSynta } return hasFileprivate ? .fileprivate : .internal } + +// FIXME: Stop using these extensions; they make assumptions about the structure of stored +// properties and may miss some valid cases, like tuple patterns. +extension VariableDeclSyntax { + /// Returns array of all identifiers listed in the declaration. + fileprivate var identifiers: [IdentifierPatternSyntax] { + var ids: [IdentifierPatternSyntax] = [] + for binding in bindings { + guard let id = binding.pattern.as(IdentifierPatternSyntax.self) else { continue } + ids.append(id) + } + return ids + } + + /// Returns the first identifier. + fileprivate var firstIdentifier: IdentifierPatternSyntax { + return identifiers[0] + } + + /// Returns the first type explicitly stated in the declaration, if present. + fileprivate var firstType: TypeSyntax? { + return bindings.first?.typeAnnotation?.type + } + + /// Returns the first initializer clause, if present. + fileprivate var firstInitializer: InitializerClauseSyntax? { + return bindings.first?.initializer + } +} From 8ad61f0bdb4d4c7da61e5de70ff4f251551d29bd Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 4 Sep 2023 12:37:36 -0400 Subject: [PATCH 116/332] Actually implement `NoPlaygroundLiterals` rule. This file has been stubbed out forever, so actually implement it. It was originally meant to be a format rule, but those replacements are either tricky or undesirable: - `#fileLiteral` is implemented as `Bundle.main.url(forResource:withExtension:)`, but I don't want to "endorse" `Bundle.main` since it's almost always better to use `Bundle(for: AnyClass)` so that code works when it's pulled into a framework target or when running unit tests. - `#colorLiteral` and `#imageLiteral` are platform-dependent, being implemented as either `{NS,UI}Color` and `{NS,UI}Image` respectively. Sometimes this can be determined by checking for imports of `AppKit/Cocoa` or `UIKit`, but other frameworks like `SwiftUI` can re-export those and thus the source file may not actually have an import that we can unambiguously determine the right replacement for. It can also be impossible to determine if there are multi-platform conditions that import all of those modules. So, we just make these literals lint warnings and give the user rough ideas about how to fix them. --- .../Core/Pipelines+Generated.swift | 15 ++++ .../Core/RuleNameCache+Generated.swift | 1 + .../Rules/NoPlaygroundLiterals.swift | 81 ++++++++++++++++--- .../RuleRegistry+Generated.swift | 1 + .../Rules/NoPlaygroundLiteralsTests.swift | 71 ++++++++++++++++ 5 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 Tests/SwiftFormatTests/Rules/NoPlaygroundLiteralsTests.swift diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 566d4a568..6603fcb72 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -183,6 +183,11 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } + override func visit(_ node: GuardStmtSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoParensAroundConditions.visit, for: node) + return .visitChildren + } + override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(IdentifiersMustBeASCII.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) @@ -217,6 +222,11 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } + override func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoPlaygroundLiterals.visit, for: node) + return .visitChildren + } + override func visit(_ node: MemberBlockItemListSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(DoNotUseSemicolons.visit, for: node) return .visitChildren @@ -335,6 +345,11 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + + override func visit(_ node: WhileStmtSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoParensAroundConditions.visit, for: node) + return .visitChildren + } } extension FormatPipeline { diff --git a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift index db36040ce..453f82940 100644 --- a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift @@ -36,6 +36,7 @@ public let ruleNameCache: [ObjectIdentifier: String] = [ ObjectIdentifier(NoLabelsInCasePatterns.self): "NoLabelsInCasePatterns", ObjectIdentifier(NoLeadingUnderscores.self): "NoLeadingUnderscores", ObjectIdentifier(NoParensAroundConditions.self): "NoParensAroundConditions", + ObjectIdentifier(NoPlaygroundLiterals.self): "NoPlaygroundLiterals", ObjectIdentifier(NoVoidReturnOnFunctionSignature.self): "NoVoidReturnOnFunctionSignature", ObjectIdentifier(OmitExplicitReturns.self): "OmitExplicitReturns", ObjectIdentifier(OneCasePerLine.self): "OneCasePerLine", diff --git a/Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift b/Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift index 63bf0a4ca..e9d5d653a 100644 --- a/Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift +++ b/Sources/SwiftFormat/Rules/NoPlaygroundLiterals.swift @@ -13,19 +13,76 @@ import Foundation import SwiftSyntax -/// Playground literals (e.g. `#colorLiteral`) are forbidden. +/// The playground literals (`#colorLiteral`, `#fileLiteral`, and `#imageLiteral`) are forbidden. /// -/// For the case of `#colorLiteral`, if `import AppKit` is present, `NSColor` will be used. -/// If `import UIKit` is present, `UIColor` will be used. -/// If neither `import` is present, `resolveAmbiguousColor` will be used to determine behavior. -/// -/// Lint: Using a playground literal will yield a lint error. -/// -/// Format: The playground literal will be replaced with the matching class; e.g. -/// `#colorLiteral(...)` becomes `UIColor(...)` -/// -/// Configuration: resolveAmbiguousColor +/// Lint: Using a playground literal will yield a lint error with a suggestion of an API to replace +/// it. @_spi(Rules) -public final class NoPlaygroundLiterals: SyntaxFormatRule { +public final class NoPlaygroundLiterals: SyntaxLintRule { + override public func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind { + switch node.macroName.text { + case "colorLiteral": + diagnosedColorLiteralMacroExpansion(node) + case "fileLiteral": + diagnosedFileLiteralMacroExpansion(node) + case "imageLiteral": + diagnosedImageLiteralMacroExpansion(node) + default: + break + } + return .visitChildren + } + + private func diagnosedColorLiteralMacroExpansion(_ node: MacroExpansionExprSyntax) { + guard isLiteralMacroCall(node, matchingLabels: ["red", "green", "blue", "alpha"]) else { + return + } + diagnose(.replaceColorLiteral, on: node) + } + + private func diagnosedFileLiteralMacroExpansion(_ node: MacroExpansionExprSyntax) { + guard isLiteralMacroCall(node, matchingLabels: ["resourceName"]) else { + return + } + diagnose(.replaceFileLiteral, on: node) + } + + private func diagnosedImageLiteralMacroExpansion(_ node: MacroExpansionExprSyntax) { + guard isLiteralMacroCall(node, matchingLabels: ["resourceName"]) else { + return + } + diagnose(.replaceImageLiteral, on: node) + } + + /// Returns true if the given macro expansion is a correctly constructed call with the given + /// argument labels and has no trailing closures or generic arguments. + private func isLiteralMacroCall( + _ node: MacroExpansionExprSyntax, + matchingLabels labels: [String] + ) -> Bool { + guard + node.genericArgumentClause == nil, + node.trailingClosure == nil, + node.additionalTrailingClosures.isEmpty, + node.arguments.count == labels.count + else { + return false + } + + for (actual, expected) in zip(node.arguments, labels) { + guard actual.label?.text == expected else { return false } + } + return true + } +} + +extension Finding.Message { + fileprivate static let replaceColorLiteral: Finding.Message = + "replace '#colorLiteral' with a call to an initializer on 'NSColor' or 'UIColor'" + + fileprivate static let replaceFileLiteral: Finding.Message = + "replace '#fileLiteral' with a call to a method such as 'Bundle.url(forResource:withExtension:)'" + fileprivate static let replaceImageLiteral: Finding.Message = + "replace '#imageLiteral' with a call to an initializer on 'NSImage' or 'UIImage'" } diff --git a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift index b7a17de2d..cd6c7ca45 100644 --- a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift @@ -35,6 +35,7 @@ enum RuleRegistry { "NoLabelsInCasePatterns": true, "NoLeadingUnderscores": false, "NoParensAroundConditions": true, + "NoPlaygroundLiterals": true, "NoVoidReturnOnFunctionSignature": true, "OmitExplicitReturns": false, "OneCasePerLine": true, diff --git a/Tests/SwiftFormatTests/Rules/NoPlaygroundLiteralsTests.swift b/Tests/SwiftFormatTests/Rules/NoPlaygroundLiteralsTests.swift new file mode 100644 index 000000000..91d659053 --- /dev/null +++ b/Tests/SwiftFormatTests/Rules/NoPlaygroundLiteralsTests.swift @@ -0,0 +1,71 @@ +import _SwiftFormatTestSupport + +@_spi(Rules) import SwiftFormat + +final class NoPlaygroundLiteralsTests: LintOrFormatRuleTestCase { + func testColorLiterals() { + assertLint( + NoPlaygroundLiterals.self, + """ + _ = 1️⃣#colorLiteral(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) + _ = #otherMacro(color: 2️⃣#colorLiteral(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)) + _ = #otherMacro { 3️⃣#colorLiteral(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) } + + // Ignore invalid expansions. + _ = #colorLiteral(1.0, 0.0, 0.0, 1.0) + _ = #colorLiteral(r: 1.0, g: 0.0, b: 0.0, a: 1.0) + _ = #colorLiteral(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) { trailingClosure() } + _ = #colorLiteral(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) + """, + findings: [ + FindingSpec("1️⃣", message: "replace '#colorLiteral' with a call to an initializer on 'NSColor' or 'UIColor'"), + FindingSpec("2️⃣", message: "replace '#colorLiteral' with a call to an initializer on 'NSColor' or 'UIColor'"), + FindingSpec("3️⃣", message: "replace '#colorLiteral' with a call to an initializer on 'NSColor' or 'UIColor'"), + ] + ) + } + + func testFileLiterals() { + assertLint( + NoPlaygroundLiterals.self, + """ + _ = 1️⃣#fileLiteral(resourceName: "secrets.json") + _ = #otherMacro(url: 2️⃣#fileLiteral(resourceName: "secrets.json")) + _ = #otherMacro { 3️⃣#fileLiteral(resourceName: "secrets.json") } + + // Ignore invalid expansions. + _ = #fileLiteral("secrets.json") + _ = #fileLiteral(name: "secrets.json") + _ = #fileLiteral(resourceName: "secrets.json") { trailingClosure() } + _ = #fileLiteral(resourceName: "secrets.json") + """, + findings: [ + FindingSpec("1️⃣", message: "replace '#fileLiteral' with a call to a method such as 'Bundle.url(forResource:withExtension:)'"), + FindingSpec("2️⃣", message: "replace '#fileLiteral' with a call to a method such as 'Bundle.url(forResource:withExtension:)'"), + FindingSpec("3️⃣", message: "replace '#fileLiteral' with a call to a method such as 'Bundle.url(forResource:withExtension:)'"), + ] + ) + } + + func testImageLiterals() { + assertLint( + NoPlaygroundLiterals.self, + """ + _ = 1️⃣#imageLiteral(resourceName: "image.png") + _ = #otherMacro(url: 2️⃣#imageLiteral(resourceName: "image.png")) + _ = #otherMacro { 3️⃣#imageLiteral(resourceName: "image.png") } + + // Ignore invalid expansions. + _ = #imageLiteral("image.png") + _ = #imageLiteral(name: "image.pngn") + _ = #imageLiteral(resourceName: "image.png") { trailingClosure() } + _ = #imageLiteral(resourceName: "image.png") + """, + findings: [ + FindingSpec("1️⃣", message: "replace '#imageLiteral' with a call to an initializer on 'NSImage' or 'UIImage'"), + FindingSpec("2️⃣", message: "replace '#imageLiteral' with a call to an initializer on 'NSImage' or 'UIImage'"), + FindingSpec("3️⃣", message: "replace '#imageLiteral' with a call to an initializer on 'NSImage' or 'UIImage'"), + ] + ) + } +} From d8161173f3b1e2086abae197a48fe818d816848f Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 4 Sep 2023 21:28:49 -0400 Subject: [PATCH 117/332] Move `Configuration` into the `SwiftFormat` module. The `SwiftFormatConfiguration` module still exists for compatibility, but now the dependency is inverted; it imports `SwiftFormat` and re-exports the symbols that used to live in `SwiftFormatConfiguration`. This compatibility layer will be deleted after the next major release. As of this change, only the `SwiftFormat` module remains as public API. --- Documentation/Development.md | 16 ++++------------ Package.swift | 15 ++++++--------- .../API}/Configuration+Default.swift | 0 .../API}/Configuration.swift | 0 .../API}/Indent.swift | 0 Sources/SwiftFormat/API/SwiftFormatter.swift | 1 - Sources/SwiftFormat/API/SwiftLinter.swift | 1 - Sources/SwiftFormat/Core/Context.swift | 1 - .../Core}/RuleRegistry+Generated.swift | 0 Sources/SwiftFormat/PrettyPrint/Comment.swift | 1 - .../PrettyPrint/Indent+Length.swift | 2 -- .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 1 - .../PrettyPrint/TokenStreamCreator.swift | 1 - .../SwiftFormat/PrettyPrint/Verbatim.swift | 1 - .../PrettyPrint/WhitespaceLinter.swift | 1 - .../Rules/NoAssignmentInExpressions.swift | 1 - .../Compatibility.swift | 19 +++++++++++++++++++ .../Configuration+Testing.swift | 2 +- .../DiagnosingTestCase.swift | 2 +- Sources/generate-pipeline/main.swift | 3 ++- .../Frontend/ConfigurationLoader.swift | 2 +- .../Frontend/FormatFrontend.swift | 1 - Sources/swift-format/Frontend/Frontend.swift | 1 - .../swift-format/Frontend/LintFrontend.swift | 1 - .../Subcommands/DumpConfiguration.swift | 2 +- .../API}/ConfigurationTests.swift | 2 +- .../PrettyPrint/AssignmentExprTests.swift | 2 +- .../PrettyPrint/AttributeTests.swift | 2 +- .../PrettyPrint/AwaitExprTests.swift | 2 +- .../PrettyPrint/BinaryOperatorExprTests.swift | 2 +- .../PrettyPrint/ClassDeclTests.swift | 2 +- .../PrettyPrint/ClosureExprTests.swift | 2 +- .../PrettyPrint/DeclNameArgumentTests.swift | 2 +- .../PrettyPrint/DoStmtTests.swift | 2 +- .../PrettyPrint/EnumDeclTests.swift | 2 +- .../PrettyPrint/ExtensionDeclTests.swift | 2 +- .../PrettyPrint/FunctionCallTests.swift | 2 +- .../PrettyPrint/FunctionDeclTests.swift | 2 +- .../PrettyPrint/IfConfigTests.swift | 2 +- .../PrettyPrint/IfStmtTests.swift | 2 +- .../PrettyPrint/InitializerDeclTests.swift | 2 +- .../PrettyPrint/MacroCallTests.swift | 2 +- .../PrettyPrint/MacroDeclTests.swift | 2 +- .../PrettyPrint/MemberAccessExprTests.swift | 2 +- .../PrettyPrint/ObjectLiteralExprTests.swift | 2 +- .../PrettyPrint/OperatorDeclTests.swift | 2 +- .../PrettyPrint/PatternBindingTests.swift | 2 +- .../PrettyPrint/PrettyPrintTestCase.swift | 2 +- .../PrettyPrint/ProtocolDeclTests.swift | 2 +- .../PrettyPrint/RepeatStmtTests.swift | 2 +- .../RespectsExistingLineBreaksTests.swift | 2 +- .../PrettyPrint/StructDeclTests.swift | 2 +- .../PrettyPrint/SubscriptDeclTests.swift | 2 +- .../SwitchCaseIndentConfigTests.swift | 2 +- .../PrettyPrint/SwitchStmtTests.swift | 2 +- .../PrettyPrint/TryCatchTests.swift | 2 +- .../PrettyPrint/WhitespaceLintTests.swift | 1 - .../PrettyPrint/WhitespaceTestCase.swift | 2 +- .../FileScopedDeclarationPrivacyTests.swift | 2 +- .../Rules/ImportsXCTestVisitorTests.swift | 2 +- .../Rules/LintOrFormatRuleTestCase.swift | 2 +- 61 files changed, 70 insertions(+), 76 deletions(-) rename Sources/{SwiftFormatConfiguration => SwiftFormat/API}/Configuration+Default.swift (100%) rename Sources/{SwiftFormatConfiguration => SwiftFormat/API}/Configuration.swift (100%) rename Sources/{SwiftFormatConfiguration => SwiftFormat/API}/Indent.swift (100%) rename Sources/{SwiftFormatConfiguration => SwiftFormat/Core}/RuleRegistry+Generated.swift (100%) create mode 100644 Sources/SwiftFormatConfiguration/Compatibility.swift rename Tests/{SwiftFormatConfigurationTests => SwiftFormatTests/API}/ConfigurationTests.swift (96%) diff --git a/Documentation/Development.md b/Documentation/Development.md index 8773f051f..5445d2ed4 100644 --- a/Documentation/Development.md +++ b/Documentation/Development.md @@ -4,7 +4,7 @@ Since Swift does not yet have a runtime reflection system, we use code generation to keep the linting/formatting pipeline up-to-date. If you add or -remove any rules from the `SwiftFormatRules` module, or if you add or remove +remove any rules from the `SwiftFormat` module, or if you add or remove any `visit` methods from an existing rule in that module, you must run the `generate-pipeline` tool update the pipeline and configuration sources. @@ -14,17 +14,9 @@ The easiest way to do this is to run the following command in your terminal: swift run generate-pipeline ``` -If successful, this tool will update -`Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift` and -`Sources/SwiftFormat/Pipelines+Generated.swift`. - -Likewise, you should keep the Linux XCTest manifests updated if you add or -remove any tests from `swift-format` by running the following command in your -terminal: - -```shell -swift test --generate-linuxmain -``` +If successful, this tool will update the files `Pipelines+Generated.swift`, +`RuleNameCache+Generated.swift`, and `RuleRegistry+Generated.swift` in +the `Sources/SwiftFormat/Core` directory. ## Command Line Options for Debugging diff --git a/Package.swift b/Package.swift index 6d7045c78..124672571 100644 --- a/Package.swift +++ b/Package.swift @@ -29,6 +29,7 @@ let package = Package( name: "SwiftFormat", targets: ["SwiftFormat", "SwiftFormatConfiguration"] ), + // TODO: Remove this product after the 509 release. .library( name: "SwiftFormatConfiguration", targets: ["SwiftFormatConfiguration"] @@ -49,7 +50,6 @@ let package = Package( .target( name: "SwiftFormat", dependencies: [ - "SwiftFormatConfiguration", .product(name: "Markdown", package: "swift-markdown"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftOperators", package: "swift-syntax"), @@ -57,14 +57,17 @@ let package = Package( .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), ] ), + // TODO: Remove this target after the 509 release. .target( - name: "SwiftFormatConfiguration" + name: "SwiftFormatConfiguration", + dependencies: [ + "SwiftFormat" + ] ), .target( name: "_SwiftFormatTestSupport", dependencies: [ "SwiftFormat", - "SwiftFormatConfiguration", .product(name: "SwiftOperators", package: "swift-syntax"), ] ), @@ -106,17 +109,12 @@ let package = Package( name: "swift-format", dependencies: [ "SwiftFormat", - "SwiftFormatConfiguration", .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), ] ), - .testTarget( - name: "SwiftFormatConfigurationTests", - dependencies: ["SwiftFormatConfiguration"] - ), .testTarget( name: "SwiftFormatPerformanceTests", dependencies: [ @@ -130,7 +128,6 @@ let package = Package( name: "SwiftFormatTests", dependencies: [ "SwiftFormat", - "SwiftFormatConfiguration", "_SwiftFormatTestSupport", .product(name: "Markdown", package: "swift-markdown"), .product(name: "SwiftOperators", package: "swift-syntax"), diff --git a/Sources/SwiftFormatConfiguration/Configuration+Default.swift b/Sources/SwiftFormat/API/Configuration+Default.swift similarity index 100% rename from Sources/SwiftFormatConfiguration/Configuration+Default.swift rename to Sources/SwiftFormat/API/Configuration+Default.swift diff --git a/Sources/SwiftFormatConfiguration/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift similarity index 100% rename from Sources/SwiftFormatConfiguration/Configuration.swift rename to Sources/SwiftFormat/API/Configuration.swift diff --git a/Sources/SwiftFormatConfiguration/Indent.swift b/Sources/SwiftFormat/API/Indent.swift similarity index 100% rename from Sources/SwiftFormatConfiguration/Indent.swift rename to Sources/SwiftFormat/API/Indent.swift diff --git a/Sources/SwiftFormat/API/SwiftFormatter.swift b/Sources/SwiftFormat/API/SwiftFormatter.swift index 8ab36f0f6..9230bdd8f 100644 --- a/Sources/SwiftFormat/API/SwiftFormatter.swift +++ b/Sources/SwiftFormat/API/SwiftFormatter.swift @@ -12,7 +12,6 @@ import Foundation import SwiftDiagnostics -import SwiftFormatConfiguration import SwiftOperators import SwiftSyntax diff --git a/Sources/SwiftFormat/API/SwiftLinter.swift b/Sources/SwiftFormat/API/SwiftLinter.swift index c891fff3a..4806f19df 100644 --- a/Sources/SwiftFormat/API/SwiftLinter.swift +++ b/Sources/SwiftFormat/API/SwiftLinter.swift @@ -12,7 +12,6 @@ import Foundation import SwiftDiagnostics -import SwiftFormatConfiguration import SwiftOperators import SwiftSyntax diff --git a/Sources/SwiftFormat/Core/Context.swift b/Sources/SwiftFormat/Core/Context.swift index 94cfc5a4b..8851a685c 100644 --- a/Sources/SwiftFormat/Core/Context.swift +++ b/Sources/SwiftFormat/Core/Context.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormatConfiguration import SwiftOperators import SwiftSyntax import SwiftParser diff --git a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift similarity index 100% rename from Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift rename to Sources/SwiftFormat/Core/RuleRegistry+Generated.swift diff --git a/Sources/SwiftFormat/PrettyPrint/Comment.swift b/Sources/SwiftFormat/PrettyPrint/Comment.swift index ee85cec48..8e119b819 100644 --- a/Sources/SwiftFormat/PrettyPrint/Comment.swift +++ b/Sources/SwiftFormat/PrettyPrint/Comment.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormatConfiguration import SwiftSyntax extension StringProtocol { diff --git a/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift b/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift index e326894fb..062d50a07 100644 --- a/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift +++ b/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift @@ -10,8 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatConfiguration - extension Indent { var character: Character { switch self { diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 6e91d821d..97a1c5fc6 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatConfiguration import SwiftSyntax /// PrettyPrinter takes a Syntax node and outputs a well-formatted, re-indented reproduction of the diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index d6b09abb8..5d00dd005 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormatConfiguration import SwiftOperators import SwiftSyntax diff --git a/Sources/SwiftFormat/PrettyPrint/Verbatim.swift b/Sources/SwiftFormat/PrettyPrint/Verbatim.swift index 81ff2749a..75be7b2ea 100644 --- a/Sources/SwiftFormat/PrettyPrint/Verbatim.swift +++ b/Sources/SwiftFormat/PrettyPrint/Verbatim.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormatConfiguration /// Describes options for behavior when applying the indentation of the current context when /// printing a verbatim token. diff --git a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift index 2afb0e5f6..088e7ac38 100644 --- a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift +++ b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatConfiguration import SwiftSyntax private let utf8Newline = UTF8.CodeUnit(ascii: "\n") diff --git a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift index 3ef70a287..b5996f831 100644 --- a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatConfiguration import SwiftSyntax /// Assignment expressions must be their own statements. diff --git a/Sources/SwiftFormatConfiguration/Compatibility.swift b/Sources/SwiftFormatConfiguration/Compatibility.swift new file mode 100644 index 000000000..3fc586541 --- /dev/null +++ b/Sources/SwiftFormatConfiguration/Compatibility.swift @@ -0,0 +1,19 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +// Make these symbols that used to live in `SwiftFormatConfiguration` available when that module is +// imported. +// TODO: Remove this after the 509 release. +@_exported import struct SwiftFormat.Configuration +@_exported import struct SwiftFormat.FileScopedDeclarationPrivacyConfiguration +@_exported import struct SwiftFormat.NoAssignmentInExpressionsConfiguration +@_exported import enum SwiftFormat.Indent diff --git a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift index 4ba83a450..36cd2971d 100644 --- a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift +++ b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -import SwiftFormatConfiguration +import SwiftFormat extension Configuration { /// The default configuration to be used during unit tests. diff --git a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift index 567f5dccf..f7a9b25a8 100644 --- a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift +++ b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat import SwiftSyntax import XCTest diff --git a/Sources/generate-pipeline/main.swift b/Sources/generate-pipeline/main.swift index fdf932031..fd3c337ca 100644 --- a/Sources/generate-pipeline/main.swift +++ b/Sources/generate-pipeline/main.swift @@ -24,7 +24,8 @@ let pipelineFile = sourcesDirectory .appendingPathComponent("Core") .appendingPathComponent("Pipelines+Generated.swift") let ruleRegistryFile = sourcesDirectory - .appendingPathComponent("SwiftFormatConfiguration") + .appendingPathComponent("SwiftFormat") + .appendingPathComponent("Core") .appendingPathComponent("RuleRegistry+Generated.swift") let ruleNameCacheFile = sourcesDirectory diff --git a/Sources/swift-format/Frontend/ConfigurationLoader.swift b/Sources/swift-format/Frontend/ConfigurationLoader.swift index 6948ac553..56154b090 100644 --- a/Sources/swift-format/Frontend/ConfigurationLoader.swift +++ b/Sources/swift-format/Frontend/ConfigurationLoader.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormatConfiguration +import SwiftFormat /// Loads formatter configurations, caching them in memory so that multiple operations in the same /// directory do not repeatedly hit the file system. diff --git a/Sources/swift-format/Frontend/FormatFrontend.swift b/Sources/swift-format/Frontend/FormatFrontend.swift index aac36856b..019eb40c0 100644 --- a/Sources/swift-format/Frontend/FormatFrontend.swift +++ b/Sources/swift-format/Frontend/FormatFrontend.swift @@ -13,7 +13,6 @@ import Foundation import SwiftDiagnostics import SwiftFormat -import SwiftFormatConfiguration import SwiftSyntax /// The frontend for formatting operations. diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index 0c8c4a50f..de62ff4ad 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -12,7 +12,6 @@ import Foundation import SwiftFormat -import SwiftFormatConfiguration import SwiftSyntax import SwiftParser diff --git a/Sources/swift-format/Frontend/LintFrontend.swift b/Sources/swift-format/Frontend/LintFrontend.swift index 789960177..4ef989826 100644 --- a/Sources/swift-format/Frontend/LintFrontend.swift +++ b/Sources/swift-format/Frontend/LintFrontend.swift @@ -13,7 +13,6 @@ import Foundation import SwiftDiagnostics import SwiftFormat -import SwiftFormatConfiguration import SwiftSyntax /// The frontend for linting operations. diff --git a/Sources/swift-format/Subcommands/DumpConfiguration.swift b/Sources/swift-format/Subcommands/DumpConfiguration.swift index b32b9c8fe..9e6f03c43 100644 --- a/Sources/swift-format/Subcommands/DumpConfiguration.swift +++ b/Sources/swift-format/Subcommands/DumpConfiguration.swift @@ -12,7 +12,7 @@ import ArgumentParser import Foundation -import SwiftFormatConfiguration +import SwiftFormat extension SwiftFormatCommand { /// Dumps the tool's default configuration in JSON format to standard output. diff --git a/Tests/SwiftFormatConfigurationTests/ConfigurationTests.swift b/Tests/SwiftFormatTests/API/ConfigurationTests.swift similarity index 96% rename from Tests/SwiftFormatConfigurationTests/ConfigurationTests.swift rename to Tests/SwiftFormatTests/API/ConfigurationTests.swift index 9d50ec388..6834e9f55 100644 --- a/Tests/SwiftFormatConfigurationTests/ConfigurationTests.swift +++ b/Tests/SwiftFormatTests/API/ConfigurationTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat import XCTest final class ConfigurationTests: XCTestCase { diff --git a/Tests/SwiftFormatTests/PrettyPrint/AssignmentExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AssignmentExprTests.swift index a1e9452a3..77d505be6 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/AssignmentExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/AssignmentExprTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class AssignmentExprTests: PrettyPrintTestCase { func testBasicAssignmentExprs() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift index d67bc73d3..57a12a3a7 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class AttributeTests: PrettyPrintTestCase { func testAttributeParamSpacing() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/AwaitExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AwaitExprTests.swift index 5730f7f49..1c890e42d 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/AwaitExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/AwaitExprTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class AwaitExprTests: PrettyPrintTestCase { func testBasicAwaits() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/BinaryOperatorExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/BinaryOperatorExprTests.swift index 54f12e008..50b283bd6 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/BinaryOperatorExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/BinaryOperatorExprTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class BinaryOperatorExprTests: PrettyPrintTestCase { func testNonRangeFormationOperatorsAreSurroundedByBreaks() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/ClassDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ClassDeclTests.swift index 86f47bf0f..a69ffa1f5 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ClassDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ClassDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class ClassDeclTests: PrettyPrintTestCase { func testBasicClassDeclarations() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/ClosureExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ClosureExprTests.swift index 41332f5d7..089aee1ba 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ClosureExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ClosureExprTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class ClosureExprTests: PrettyPrintTestCase { func testBasicFunctionClosures_noPackArguments() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/DeclNameArgumentTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DeclNameArgumentTests.swift index f4138d1d6..ea7dd2c4a 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/DeclNameArgumentTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/DeclNameArgumentTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class DeclNameArgumentTests: PrettyPrintTestCase { func testSelectors_noPackArguments() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift index f3c458869..fdb491adb 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class DoStmtTests: PrettyPrintTestCase { func testBasicDoStmt() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift index 42935e6e5..675d75eda 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class EnumDeclTests: PrettyPrintTestCase { func testBasicEnumDeclarations() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/ExtensionDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ExtensionDeclTests.swift index c8a030c84..f11a1eb5b 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ExtensionDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ExtensionDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class ExtensionDeclTests: PrettyPrintTestCase { func testBasicExtensionDeclarations() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/FunctionCallTests.swift b/Tests/SwiftFormatTests/PrettyPrint/FunctionCallTests.swift index 7ac04ac34..7f2d10e7e 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/FunctionCallTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/FunctionCallTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class FunctionCallTests: PrettyPrintTestCase { func testBasicFunctionCalls_noPackArguments() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/FunctionDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/FunctionDeclTests.swift index 2c1646c4d..5d94fa5f2 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/FunctionDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/FunctionDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class FunctionDeclTests: PrettyPrintTestCase { func testBasicFunctionDeclarations_noPackArguments() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift index 6558430e1..4f8026ab4 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class IfConfigTests: PrettyPrintTestCase { func testBasicIfConfig() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/IfStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IfStmtTests.swift index 6c5d74b00..cddaad53c 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/IfStmtTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/IfStmtTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat import XCTest final class IfStmtTests: PrettyPrintTestCase { diff --git a/Tests/SwiftFormatTests/PrettyPrint/InitializerDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/InitializerDeclTests.swift index 0582cfeec..d1227605a 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/InitializerDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/InitializerDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class InitializerDeclTests: PrettyPrintTestCase { func testBasicInitializerDeclarations_noPackArguments() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift b/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift index 413190a41..dffbef001 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class MacroCallTests: PrettyPrintTestCase { func testNoWhiteSpaceAfterMacroWithoutTrailingClosure() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/MacroDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/MacroDeclTests.swift index 1cbee43b7..2182b9dc4 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/MacroDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/MacroDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class MacroDeclTests: PrettyPrintTestCase { func testBasicMacroDeclarations_noPackArguments() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/MemberAccessExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/MemberAccessExprTests.swift index 3ba490308..6b0ed8cf5 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/MemberAccessExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/MemberAccessExprTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class MemberAccessExprTests: PrettyPrintTestCase { func testMemberAccess() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/ObjectLiteralExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ObjectLiteralExprTests.swift index d8aab9620..c735320b4 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ObjectLiteralExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ObjectLiteralExprTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class ObjectLiteralExprTests: PrettyPrintTestCase { func testColorLiteral_noPackArguments() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/OperatorDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/OperatorDeclTests.swift index 8272d4526..6849fbed2 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/OperatorDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/OperatorDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class OperatorDeclTests: PrettyPrintTestCase { func testOperatorDecl() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/PatternBindingTests.swift b/Tests/SwiftFormatTests/PrettyPrint/PatternBindingTests.swift index 264156c31..608825b96 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/PatternBindingTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/PatternBindingTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class PatternBindingTests: PrettyPrintTestCase { func testBindingIncludingTypeAnnotation() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift index 4bc4e1142..570a42a11 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat import SwiftOperators import SwiftSyntax import SwiftParser diff --git a/Tests/SwiftFormatTests/PrettyPrint/ProtocolDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ProtocolDeclTests.swift index d18864afe..d10db6c93 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ProtocolDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ProtocolDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class ProtocolDeclTests: PrettyPrintTestCase { func testBasicProtocolDeclarations() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/RepeatStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/RepeatStmtTests.swift index d337590ff..037c26f31 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/RepeatStmtTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/RepeatStmtTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class RepeatStmtTests: PrettyPrintTestCase { func testBasicRepeatTests_noBreakBeforeWhile() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift b/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift index 2bcff38f6..86c5e3cde 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat /// Sanity checks and regression tests for the `respectsExistingLineBreaks` configuration setting /// in both true and false states. diff --git a/Tests/SwiftFormatTests/PrettyPrint/StructDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/StructDeclTests.swift index 42534dae5..664fc59d6 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/StructDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/StructDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class StructDeclTests: PrettyPrintTestCase { func testBasicStructDeclarations() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/SubscriptDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SubscriptDeclTests.swift index dd6938629..9a06c0bab 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/SubscriptDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/SubscriptDeclTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class SubscriptDeclTests: PrettyPrintTestCase { func testBasicSubscriptDeclarations() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/SwitchCaseIndentConfigTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SwitchCaseIndentConfigTests.swift index 3ba650afe..b39e36e54 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/SwitchCaseIndentConfigTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/SwitchCaseIndentConfigTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat /// Tests the `indentSwitchCaseLabels` config option final class SwitchCaseIndentConfigTests: PrettyPrintTestCase { diff --git a/Tests/SwiftFormatTests/PrettyPrint/SwitchStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SwitchStmtTests.swift index 15bb33798..6928defd5 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/SwitchStmtTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/SwitchStmtTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class SwitchStmtTests: PrettyPrintTestCase { func testBasicSwitch() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/TryCatchTests.swift b/Tests/SwiftFormatTests/PrettyPrint/TryCatchTests.swift index 6c3acaeb8..b92a03338 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/TryCatchTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/TryCatchTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat final class TryCatchTests: PrettyPrintTestCase { func testBasicTries() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift index 9042cb6f5..0000be6ac 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift @@ -1,5 +1,4 @@ import SwiftFormat -import SwiftFormatConfiguration import _SwiftFormatTestSupport // A note about these tests: `WhitespaceLinter` *only* emits findings; it does not do any diff --git a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift index cd0aeed90..1add23434 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat import SwiftSyntax import SwiftParser import XCTest diff --git a/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift b/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift index 576f88811..e41130251 100644 --- a/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift +++ b/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat import SwiftSyntax import _SwiftFormatTestSupport diff --git a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift index 1bef38333..7e28f0958 100644 --- a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift +++ b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat import SwiftParser import XCTest diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index 523769d26..d249ae216 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -1,4 +1,4 @@ -import SwiftFormatConfiguration +import SwiftFormat import SwiftOperators import SwiftParser import SwiftSyntax From 1556e829ee3291f9d75fe7abe49d740abfa6738a Mon Sep 17 00:00:00 2001 From: Natik Gadzhi Date: Mon, 4 Sep 2023 21:53:49 -0700 Subject: [PATCH 118/332] Generate rule docs automatically - This commit adds a `generate-pipeline` generator `RuleDocumentationGenerator` that extracts each rule DocC comment, and writes it into `Documentation/RuleDocumentation.md`. - In `Documentation/Configuration.md`, I've added a paragraph on rules configuration that links to the new auto-generated docs file, and also tells users that they can run `swift-format dump-configuration` to check out the list of available rules. --- Documentation/Configuration.md | 12 + Documentation/RuleDocumentation.md | 478 ++++++++++++++++++ Sources/generate-pipeline/RuleCollector.swift | 13 +- .../RuleDocumentationGenerator.swift | 83 +++ Sources/generate-pipeline/main.swift | 10 + 5 files changed, 595 insertions(+), 1 deletion(-) create mode 100644 Documentation/RuleDocumentation.md create mode 100644 Sources/generate-pipeline/RuleDocumentationGenerator.swift diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 6f1092103..8eca259e4 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -103,6 +103,18 @@ An example `.swift-format` configuration file is shown below. } ``` +## Linter and Formatter Rules Configuration + +In the `rules` block of `.swift-format`, you can specify which rules to apply +when linting and formatting your project. Read the +[rules documentation](Documentation/RuleDocumentation.md) to see the list of all +supported linter and formatter rules, and their overview. + +You can also run this command to see the list of rules in the default +`swift-format` configuration: + + $ swift-format dump-configuration + ## API Configuration The `SwiftConfiguration` module contains a `Configuration` type that is diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md new file mode 100644 index 000000000..93aae0a5a --- /dev/null +++ b/Documentation/RuleDocumentation.md @@ -0,0 +1,478 @@ + + +# `swift-format` Lint and Format Rules + +Use the rules below in the `rules` block of your `.swift-format` +configuration file, as described in +[Configuration](Documentation/Configuration.md). All of these rules can be +applied in the linter, but only some of them can format your source code +automatically. + + +### AllPublicDeclarationsHaveDocumentation + +All public or open declarations must have a top-level documentation comment. + +Lint: If a public declaration is missing a documentation comment, a lint error is raised. + +`AllPublicDeclarationsHaveDocumentation` is a linter-only rule. + +### AlwaysUseLowerCamelCase + +All values should be written in lower camel-case (`lowerCamelCase`). +Underscores (except at the beginning of an identifier) are disallowed. + +This rule does not apply to test code, defined as code which: + * Contains the line `import XCTest` + +Lint: If an identifier contains underscores or begins with a capital letter, a lint error is + raised. + +`AlwaysUseLowerCamelCase` is a linter-only rule. + +### AmbiguousTrailingClosureOverload + +Overloads with only a closure argument should not be disambiguated by parameter labels. + +Lint: If two overloaded functions with one closure parameter appear in the same scope, a lint + error is raised. + +`AmbiguousTrailingClosureOverload` is a linter-only rule. + +### BeginDocumentationCommentWithOneLineSummary + +All documentation comments must begin with a one-line summary of the declaration. + +Lint: If a comment does not begin with a single-line summary, a lint error is raised. + +`BeginDocumentationCommentWithOneLineSummary` is a linter-only rule. + +### DoNotUseSemicolons + +Semicolons should not be present in Swift code. + +Lint: If a semicolon appears anywhere, a lint error is raised. + +Format: All semicolons will be replaced with line breaks. + +`DoNotUseSemicolons` rule can format your code automatically. + +### DontRepeatTypeInStaticProperties + +Static properties of a type that return that type should not include a reference to their type. + +"Reference to their type" means that the property name includes part, or all, of the type. If +the type contains a namespace (i.e. `UIColor`) the namespace is ignored; +`public class var redColor: UIColor` would trigger this rule. + +Lint: Static properties of a type that return that type will yield a lint error. + +`DontRepeatTypeInStaticProperties` is a linter-only rule. + +### FileScopedDeclarationPrivacy + +Declarations at file scope with effective private access should be consistently declared as +either `fileprivate` or `private`, determined by configuration. + +Lint: If a file-scoped declaration has formal access opposite to the desired access level in the + formatter's configuration, a lint error is raised. + +Format: File-scoped declarations that have formal access opposite to the desired access level in + the formatter's configuration will have their access level changed. + +`FileScopedDeclarationPrivacy` rule can format your code automatically. + +### FullyIndirectEnum + +If all cases of an enum are `indirect`, the entire enum should be marked `indirect`. + +Lint: If every case of an enum is `indirect`, but the enum itself is not, a lint error is + raised. + +Format: Enums where all cases are `indirect` will be rewritten such that the enum is marked + `indirect`, and each case is not. + +`FullyIndirectEnum` rule can format your code automatically. + +### GroupNumericLiterals + +Numeric literals should be grouped with `_`s to delimit common separators. + +Specifically, decimal numeric literals should be grouped every 3 numbers, hexadecimal every 4, +and binary every 8. + +Lint: If a numeric literal is too long and should be grouped, a lint error is raised. + +Format: All numeric literals that should be grouped will have `_`s inserted where appropriate. + +TODO: Minimum numeric literal length bounds and numeric groupings have been selected arbitrarily; +these could be reevaluated. +TODO: Handle floating point literals. + +`GroupNumericLiterals` rule can format your code automatically. + +### IdentifiersMustBeASCII + +All identifiers must be ASCII. + +Lint: If an identifier contains non-ASCII characters, a lint error is raised. + +`IdentifiersMustBeASCII` is a linter-only rule. + +### NeverForceUnwrap + +Force-unwraps are strongly discouraged and must be documented. + +This rule does not apply to test code, defined as code which: + * Contains the line `import XCTest` + +Lint: If a force unwrap is used, a lint warning is raised. + +`NeverForceUnwrap` is a linter-only rule. + +### NeverUseForceTry + +Force-try (`try!`) is forbidden. + +This rule does not apply to test code, defined as code which: + * Contains the line `import XCTest` + +Lint: Using `try!` results in a lint error. + +TODO: Create exception for NSRegularExpression + +`NeverUseForceTry` is a linter-only rule. + +### NeverUseImplicitlyUnwrappedOptionals + +Implicitly unwrapped optionals (e.g. `var s: String!`) are forbidden. + +Certain properties (e.g. `@IBOutlet`) tied to the UI lifecycle are ignored. + +This rule does not apply to test code, defined as code which: + * Contains the line `import XCTest` + +TODO: Create exceptions for other UI elements (ex: viewDidLoad) + +Lint: Declaring a property with an implicitly unwrapped type yields a lint error. + +`NeverUseImplicitlyUnwrappedOptionals` is a linter-only rule. + +### NoAccessLevelOnExtensionDeclaration + +Specifying an access level for an extension declaration is forbidden. + +Lint: Specifying an access level for an extension declaration yields a lint error. + +Format: The access level is removed from the extension declaration and is added to each + declaration in the extension; declarations with redundant access levels (e.g. + `internal`, as that is the default access level) have the explicit access level removed. + +`NoAccessLevelOnExtensionDeclaration` rule can format your code automatically. + +### NoAssignmentInExpressions + +Assignment expressions must be their own statements. + +Assignment should not be used in an expression context that expects a `Void` value. For example, +assigning a variable within a `return` statement existing a `Void` function is prohibited. + +Lint: If an assignment expression is found in a position other than a standalone statement, a + lint finding is emitted. + +Format: A `return` statement containing an assignment expression is expanded into two separate + statements. + +`NoAssignmentInExpressions` rule can format your code automatically. + +### NoBlockComments + +Block comments should be avoided in favor of line comments. + +Lint: If a block comment appears, a lint error is raised. + +`NoBlockComments` is a linter-only rule. + +### NoCasesWithOnlyFallthrough + +Cases that contain only the `fallthrough` statement are forbidden. + +Lint: Cases containing only the `fallthrough` statement yield a lint error. + +Format: The fallthrough `case` is added as a prefix to the next case unless the next case is + `default`; in that case, the fallthrough `case` is deleted. + +`NoCasesWithOnlyFallthrough` rule can format your code automatically. + +### NoEmptyTrailingClosureParentheses + +Function calls with no arguments and a trailing closure should not have empty parentheses. + +Lint: If a function call with a trailing closure has an empty argument list with parentheses, + a lint error is raised. + +Format: Empty parentheses in function calls with trailing closures will be removed. + +`NoEmptyTrailingClosureParentheses` rule can format your code automatically. + +### NoLabelsInCasePatterns + +Redundant labels are forbidden in case patterns. + +In practice, *all* case pattern labels should be redundant. + +Lint: Using a label in a case statement yields a lint error unless the label does not match the + binding identifier. + +Format: Redundant labels in case patterns are removed. + +`NoLabelsInCasePatterns` rule can format your code automatically. + +### NoLeadingUnderscores + +Identifiers in declarations and patterns should not have leading underscores. + +This is intended to avoid certain antipatterns; `self.member = member` should be preferred to +`member = _member` and the leading underscore should not be used to signal access level. + +This rule intentionally checks only the parameter variable names of a function declaration, not +the parameter labels. It also only checks identifiers at the declaration site, not at usage +sites. + +Lint: Declaring an identifier with a leading underscore yields a lint error. + +`NoLeadingUnderscores` is a linter-only rule. + +### NoParensAroundConditions + +Enforces rules around parentheses in conditions or matched expressions. + +Parentheses are not used around any condition of an `if`, `guard`, or `while` statement, or +around the matched expression in a `switch` statement. + +Lint: If a top-most expression in a `switch`, `if`, `guard`, or `while` statement is surrounded + by parentheses, and it does not include a function call with a trailing closure, a lint + error is raised. + +Format: Parentheses around such expressions are removed, if they do not cause a parse ambiguity. + Specifically, parentheses are allowed if and only if the expression contains a function + call with a trailing closure. + +`NoParensAroundConditions` rule can format your code automatically. + +### NoVoidReturnOnFunctionSignature + +Functions that return `()` or `Void` should omit the return signature. + +Lint: Function declarations that explicitly return `()` or `Void` will yield a lint error. + +Format: Function declarations with explicit returns of `()` or `Void` will have their return + signature stripped. + +`NoVoidReturnOnFunctionSignature` rule can format your code automatically. + +### OmitExplicitReturns + +Single-expression functions, closures, subscripts can omit `return` statement. + +Lint: `func () { return ... }` and similar single expression constructs will yield a lint error. + +Format: `func () { return ... }` constructs will be replaced with + equivalent `func () { ... }` constructs. + +`OmitExplicitReturns` rule can format your code automatically. + +### OneCasePerLine + +Each enum case with associated values or a raw value should appear in its own case declaration. + +Lint: If a single `case` declaration declares multiple cases, and any of them have associated + values or raw values, a lint error is raised. + +Format: All case declarations with associated values or raw values will be moved to their own + case declarations. + +`OneCasePerLine` rule can format your code automatically. + +### OneVariableDeclarationPerLine + +Each variable declaration, with the exception of tuple destructuring, should +declare 1 variable. + +Lint: If a variable declaration declares multiple variables, a lint error is +raised. + +Format: If a variable declaration declares multiple variables, it will be +split into multiple declarations, each declaring one of the variables, as +long as the result would still be syntactically valid. + +`OneVariableDeclarationPerLine` rule can format your code automatically. + +### OnlyOneTrailingClosureArgument + +Function calls should never mix normal closure arguments and trailing closures. + +Lint: If a function call with a trailing closure also contains a non-trailing closure argument, + a lint error is raised. + +`OnlyOneTrailingClosureArgument` is a linter-only rule. + +### OrderedImports + +Imports must be lexicographically ordered and logically grouped at the top of each source file. +The order of the import groups is 1) regular imports, 2) declaration imports, and 3) @testable +imports. These groups are separated by a single blank line. Blank lines in between the import +declarations are removed. + +Lint: If an import appears anywhere other than the beginning of the file it resides in, + not lexicographically ordered, or not in the appropriate import group, a lint error is + raised. + +Format: Imports will be reordered and grouped at the top of the file. + +`OrderedImports` rule can format your code automatically. + +### ReplaceForEachWithForLoop + +Replace `forEach` with `for-in` loop unless its argument is a function reference. + +Lint: invalid use of `forEach` yield will yield a lint error. + +`ReplaceForEachWithForLoop` is a linter-only rule. + +### ReturnVoidInsteadOfEmptyTuple + +Return `Void`, not `()`, in signatures. + +Note that this rule does *not* apply to function declaration signatures in order to avoid +conflicting with `NoVoidReturnOnFunctionSignature`. + +Lint: Returning `()` in a signature yields a lint error. + +Format: `-> ()` is replaced with `-> Void` + +`ReturnVoidInsteadOfEmptyTuple` rule can format your code automatically. + +### TypeNamesShouldBeCapitalized + +`struct`, `class`, `enum` and `protocol` declarations should have a capitalized name. + +Lint: Types with un-capitalized names will yield a lint error. + +`TypeNamesShouldBeCapitalized` is a linter-only rule. + +### UseEarlyExits + +Early exits should be used whenever possible. + +This means that `if ... else { return/throw/break/continue }` constructs should be replaced by +`guard ... else { return/throw/break/continue }` constructs in order to keep indentation levels +low. Specifically, code of the following form: + +```swift +if condition { + trueBlock +} else { + falseBlock + return/throw/break/continue +} +``` + +will be transformed into: + +```swift +guard condition else { + falseBlock + return/throw/break/continue +} +trueBlock +``` + +Lint: `if ... else { return/throw/break/continue }` constructs will yield a lint error. + +Format: `if ... else { return/throw/break/continue }` constructs will be replaced with + equivalent `guard ... else { return/throw/break/continue }` constructs. + +`UseEarlyExits` rule can format your code automatically. + +### UseLetInEveryBoundCaseVariable + +Every variable bound in a `case` pattern must have its own `let/var`. + +For example, `case let .identifier(x, y)` is forbidden. Use +`case .identifier(let x, let y)` instead. + +Lint: `case let .identifier(...)` will yield a lint error. + +`UseLetInEveryBoundCaseVariable` is a linter-only rule. + +### UseShorthandTypeNames + +Shorthand type forms must be used wherever possible. + +Lint: Using a non-shorthand form (e.g. `Array`) yields a lint error unless the long + form is necessary (e.g. `Array.Index` cannot be shortened today.) + +Format: Where possible, shorthand types replace long form types; e.g. `Array` is + converted to `[Element]`. + +`UseShorthandTypeNames` rule can format your code automatically. + +### UseSingleLinePropertyGetter + +Read-only computed properties must use implicit `get` blocks. + +Lint: Read-only computed properties with explicit `get` blocks yield a lint error. + +Format: Explicit `get` blocks are rendered implicit by removing the `get`. + +`UseSingleLinePropertyGetter` rule can format your code automatically. + +### UseSynthesizedInitializer + +When possible, the synthesized `struct` initializer should be used. + +This means the creation of a (non-public) memberwise initializer with the same structure as the +synthesized initializer is forbidden. + +Lint: (Non-public) memberwise initializers with the same structure as the synthesized + initializer will yield a lint error. + +`UseSynthesizedInitializer` is a linter-only rule. + +### UseTripleSlashForDocumentationComments + +Documentation comments must use the `///` form. + +This is similar to `NoBlockComments` but is meant to prevent documentation block comments. + +Lint: If a doc block comment appears, a lint error is raised. + +Format: If a doc block comment appears on its own on a line, or if a doc block comment spans + multiple lines without appearing on the same line as code, it will be replaced with + multiple doc line comments. + +`UseTripleSlashForDocumentationComments` rule can format your code automatically. + +### UseWhereClausesInForLoops + +`for` loops that consist of a single `if` statement must use `where` clauses instead. + +Lint: `for` loops that consist of a single `if` statement yield a lint error. + +Format: `for` loops that consist of a single `if` statement have the conditional of that + statement factored out to a `where` clause. + +`UseWhereClausesInForLoops` rule can format your code automatically. + +### ValidateDocumentationComments + +Documentation comments must be complete and valid. + +"Command + Option + /" in Xcode produces a minimal valid documentation comment. + +Lint: Documentation comments that are incomplete (e.g. missing parameter documentation) or + invalid (uses `Parameters` when there is only one parameter) will yield a lint error. + +`ValidateDocumentationComments` is a linter-only rule. diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index 186757f32..cc4e4c81e 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -20,6 +20,11 @@ import SwiftParser final class RuleCollector { /// Information about a detected rule. struct DetectedRule: Hashable { + + /// The DocC comments of the rule, + /// presumably in the leading trivia of the rule declaration. + let doccComment: String + /// The type name of the rule. let typeName: String @@ -86,12 +91,15 @@ final class RuleCollector { let typeName: String let members: MemberBlockItemListSyntax let maybeInheritanceClause: InheritanceClauseSyntax? + let doccComment: String if let classDecl = statement.item.as(ClassDeclSyntax.self) { + doccComment = classDecl.leadingTrivia.description typeName = classDecl.name.text members = classDecl.memberBlock.members maybeInheritanceClause = classDecl.inheritanceClause } else if let structDecl = statement.item.as(StructDeclSyntax.self) { + doccComment = structDecl.leadingTrivia.description typeName = structDecl.name.text members = structDecl.memberBlock.members maybeInheritanceClause = structDecl.inheritanceClause @@ -140,7 +148,10 @@ final class RuleCollector { preconditionFailure("Failed to find type for rule named \(typeName)") } return DetectedRule( - typeName: typeName, visitedNodes: visitedNodes, canFormat: canFormat, + doccComment: doccComment, + typeName: typeName, + visitedNodes: visitedNodes, + canFormat: canFormat, isOptIn: ruleType.isOptIn) } diff --git a/Sources/generate-pipeline/RuleDocumentationGenerator.swift b/Sources/generate-pipeline/RuleDocumentationGenerator.swift new file mode 100644 index 000000000..1930f0e47 --- /dev/null +++ b/Sources/generate-pipeline/RuleDocumentationGenerator.swift @@ -0,0 +1,83 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// Generates the markdown file with extended documenation on the available rules. +final class RuleDocumentationGenerator: FileGenerator { + + /// The rules collected by scanning the formatter source code. + let ruleCollector: RuleCollector + + /// Creates a new rule registry generator. + init(ruleCollector: RuleCollector) { + self.ruleCollector = ruleCollector + } + + func write(into handle: FileHandle) throws { + handle.write( + """ + + + # `swift-format` Lint and Format Rules + + Use the rules below in the `rules` block of your `.swift-format` + configuration file, as described in + [Configuration](Documentation/Configuration.md). All of these rules can be + applied in the linter, but only some of them can format your source code + automatically. + + + """ + ) + + for detectedRule in ruleCollector.allLinters.sorted(by: { $0.typeName < $1.typeName }) { + handle.write(""" + + ### \(detectedRule.typeName) + + \(ruleDescription(for: detectedRule)) + + \(ruleFormatSupportDescription(for: detectedRule)) + + """) + } + } + + private func ruleFormatSupportDescription(for rule: RuleCollector.DetectedRule) -> String { + return rule.canFormat ? + "`\(rule.typeName)` rule can format your code automatically." : + "`\(rule.typeName)` is a linter-only rule." + } + + /// Takes the DocC comment of the rule and strip `///` from the beginning of each line. + /// Also removes empty lines with under 4 characters. + private func ruleDescription(for rule: RuleCollector.DetectedRule) -> String { + let described = rule.doccComment.split(whereSeparator: \.isNewline) + .compactMap { line in + // Remove the first 4 characters, i.e. `/// ` + if line.count >= 4 { + let index = line.index(line.startIndex, offsetBy: 4) + return line.suffix(from: index) + } else { + // For lines that have less than 4 characters, emit an empty line. + return "" + } + } + // not great, but that shoves multiple lines into the single + // table cell in markdown + .joined(separator: "\n") + + return described + } +} diff --git a/Sources/generate-pipeline/main.swift b/Sources/generate-pipeline/main.swift index fd3c337ca..75df61f9e 100644 --- a/Sources/generate-pipeline/main.swift +++ b/Sources/generate-pipeline/main.swift @@ -33,6 +33,11 @@ let ruleNameCacheFile = sourcesDirectory .appendingPathComponent("Core") .appendingPathComponent("RuleNameCache+Generated.swift") +let ruleDocumentationFile = sourcesDirectory + .appendingPathComponent("..") + .appendingPathComponent("Documentation") + .appendingPathComponent("RuleDocumentation.md") + var ruleCollector = RuleCollector() try ruleCollector.collect(from: rulesDirectory) @@ -47,3 +52,8 @@ try registryGenerator.generateFile(at: ruleRegistryFile) // Generate the rule name cache. let ruleNameCacheGenerator = RuleNameCacheGenerator(ruleCollector: ruleCollector) try ruleNameCacheGenerator.generateFile(at: ruleNameCacheFile) + +// Generate the Documentation/RuleDocumentation.md file with rule descriptions. +// This uses DocC comments from rule implementations. +let ruleDocumentationGenerator = RuleDocumentationGenerator(ruleCollector: ruleCollector) +try ruleDocumentationGenerator.generateFile(at: ruleDocumentationFile) From b21d330cdc8e13bfe27dbabc7bbb7afad65af0b4 Mon Sep 17 00:00:00 2001 From: Brian Henry Date: Wed, 6 Sep 2023 10:03:40 -0700 Subject: [PATCH 119/332] Remove tests --- Package.swift | 3 - .../PluginRunTests.swift | 265 ------------------ 2 files changed, 268 deletions(-) delete mode 100644 Tests/SwiftFormatPluginTests/PluginRunTests.swift diff --git a/Package.swift b/Package.swift index ad7647ce2..6d7045c78 100644 --- a/Package.swift +++ b/Package.swift @@ -139,9 +139,6 @@ let package = Package( .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ] ), - .testTarget( - name: "SwiftFormatPluginTests" - ), ] ) diff --git a/Tests/SwiftFormatPluginTests/PluginRunTests.swift b/Tests/SwiftFormatPluginTests/PluginRunTests.swift deleted file mode 100644 index 1bafd0f5c..000000000 --- a/Tests/SwiftFormatPluginTests/PluginRunTests.swift +++ /dev/null @@ -1,265 +0,0 @@ -import Foundation -import XCTest - -final class PluginRunTests: XCTestCase { - - var workingDirUrl: URL! - var taskProcess: Process! - - var allOutputTxt: String = "" - var stdoutTxt: String = "" - var stderrTxt: String = "" - - override func setUp() { - setupProject() - fixCodesigning() - setupProcess() - } - - // In a tmp dir, create a SPM project which depends on this project, and build it. - // Project contains targets: executable, library, test, plugin. - func setupProject() { - - // currentDirectoryPath: /path/to/swift-format/.build/arm64-apple-macosx/debug - let swiftFormatProjectDirPath = URL( - fileURLWithPath: FileManager.default.currentDirectoryPath, - isDirectory: true - ) - .deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().path - - let packageTxt = """ - // swift-tools-version: 5.9 - - import PackageDescription - - let package = Package( - name: "Swift-Format-Plugin-Test", - products: [ - .library( - name: "LibraryTarget", - targets: ["LibraryTarget"] - ), - .plugin( - name: "PluginTarget", - targets: ["PluginTarget"] - ) - ], - dependencies: [ - .package(path: "\(swiftFormatProjectDirPath)"), - ], - targets: [ - .executableTarget( - name: "ExecutableTarget", - dependencies: [ - "LibraryTarget", - ], - path: "Sources/ExecutableTarget" - ), - .target( - name: "LibraryTarget", - path: "Sources/LibraryTarget" - ), - .plugin( - name: "PluginTarget", - capability: .command( - intent: .custom( - verb: "test-plugin", - description: "A test plugin" - ), - permissions: [ - .writeToPackageDirectory(reason: "This command generates files") - ] - ), - dependencies: [ - "ExecutableTarget" - ], - path: "Plugins/PluginTarget" - ), - .testTarget( - name: "TestTarget", - dependencies: [ - "LibraryTarget" - ] - ) - ] - ) - """ - - let tempDirUrl = FileManager.default.temporaryDirectory.appendingPathComponent( - UUID().uuidString) - - do { - try FileManager.default.createDirectory(at: tempDirUrl, withIntermediateDirectories: false) - - FileManager.default.createFile( - atPath: tempDirUrl.appendingPathComponent("package.swift").path, - contents: packageTxt.data(using: .utf8) - ) - - try FileManager.default.createDirectory( - at: tempDirUrl.appendingPathComponent("Sources").appendingPathComponent("ExecutableTarget"), - withIntermediateDirectories: true) - FileManager.default.createFile( - atPath: tempDirUrl.appendingPathComponent("Sources").appendingPathComponent( - "ExecutableTarget" - ).appendingPathComponent("maain.swift").path, - contents: "".data(using: .utf8) - ) - - try FileManager.default.createDirectory( - at: tempDirUrl.appendingPathComponent("Sources").appendingPathComponent("LibraryTarget"), - withIntermediateDirectories: true) - FileManager.default.createFile( - atPath: tempDirUrl.appendingPathComponent("Sources").appendingPathComponent("LibraryTarget") - .appendingPathComponent("library.swift").path, - contents: "".data(using: .utf8) - ) - - try FileManager.default.createDirectory( - at: tempDirUrl.appendingPathComponent("Tests").appendingPathComponent("TestTarget"), - withIntermediateDirectories: true) - FileManager.default.createFile( - atPath: tempDirUrl.appendingPathComponent("Tests").appendingPathComponent("TestTarget") - .appendingPathComponent("test.swift").path, - contents: "".data(using: .utf8) - ) - - let pluginTxt = """ - import Foundation - import PackagePlugin - - @main - struct TestPlugin: CommandPlugin { - func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { - print("TestPlugin is working.") - } - } - """ - - try FileManager.default.createDirectory( - at: tempDirUrl.appendingPathComponent("Plugins").appendingPathComponent("PluginTarget"), - withIntermediateDirectories: true) - FileManager.default.createFile( - atPath: tempDirUrl.appendingPathComponent("Plugins").appendingPathComponent("PluginTarget") - .appendingPathComponent("plugin.swift").path, - contents: pluginTxt.data(using: .utf8) - ) - - setupProcess() - taskProcess.arguments = [ - "build" - ] - try taskProcess.run() - taskProcess.waitUntilExit() - - XCTAssertTrue(stdoutTxt.contains("Build complete!")) - - workingDirUrl = tempDirUrl - } catch { - XCTFail("Error setting up test fixture project at: \(tempDirUrl.path)") - } - } - - // Prepare a new NSTask/Process with output recorded to string variables. - func setupProcess() { - - allOutputTxt = "" - stdoutTxt = "" - stderrTxt = "" - - let stdoutHandler = { (file: FileHandle!) -> Void in - let data = file.availableData - - guard !data.isEmpty, let output = String(data: data, encoding: .utf8), !output.isEmpty - else { - return - } - self.stdoutTxt += output - self.allOutputTxt += output - } - let stderrHandler = { (file: FileHandle!) -> Void in - let data = file.availableData - guard !data.isEmpty, let output = String(data: data, encoding: .utf8), !output.isEmpty - else { - return - } - self.stderrTxt += output - self.allOutputTxt += output - } - - let stdOut = Pipe() - stdOut.fileHandleForReading.readabilityHandler = stdoutHandler - - let stderr = Pipe() - stderr.fileHandleForReading.readabilityHandler = stderrHandler - - taskProcess = Process() - taskProcess.standardOutput = stdOut - taskProcess.standardError = stderr - taskProcess.currentDirectoryURL = workingDirUrl - - taskProcess.launchPath = "/usr/bin/swift" - } - - /** - @see https://github.com/apple/swift-package-manager/issues/6872 - */ - func fixCodesigning() { - setupProcess() - taskProcess.arguments = [ - "package", "plugin", "lint-source-code", - ] - try! taskProcess.run() - taskProcess.waitUntilExit() - - // Do not alter the codesigning if the bug in #6872 is not present. - if 0 == taskProcess.terminationStatus - || !allOutputTxt.split(separator: "\n").last!.contains("Build complete!") - { - return - } - - setupProcess() - taskProcess.launchPath = "/usr/bin/codesign" - taskProcess.arguments = [ - "-s", "-", workingDirUrl.path + "/.build/plugins/Lint Source Code/cache/Lint_Source_Code", - ] - try! taskProcess.run() - taskProcess.waitUntilExit() - } - - /** - Confirm the plugin runs without any targets specified. - `swift package plugin lint-source-code` - @see https://github.com/apple/swift-format/issues/483 - "error: swift-format invocation failed: NSTaskTerminationReason(rawValue: 1):64" - */ - public func testPluginRun() { - - let processFinishExpectation = expectation(description: "process timeout") - - taskProcess.arguments = [ - "package", "plugin", "lint-source-code", - ] - do { - taskProcess.terminationHandler = { (process: Process) in - processFinishExpectation.fulfill() - } - - try taskProcess.run() - - waitForExpectations(timeout: 30) - } catch { - XCTFail(allOutputTxt) - } - - let errorMessage = "swift-format invocation failed: NSTaskTerminationReason(rawValue: 1):64" - if stderrTxt.split(separator: "\n").last == "error: \(errorMessage)" { - XCTFail("\(errorMessage)\nworkingdir: \(workingDirUrl.path)") - } - - XCTAssertEqual( - 0, Int(taskProcess.terminationStatus), "Non-zero exit code\nworkingdir: \(workingDirUrl.path)" - ) - } -} From 60306f89ab95b87d5a61a445fdee5812e1bb8331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ba=CC=A8k?= Date: Wed, 6 Sep 2023 22:36:07 +0200 Subject: [PATCH 120/332] Remove unnecessary casting --- Sources/SwiftFormat/Rules/OmitExplicitReturns.swift | 2 +- Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift index a73914916..21f9833f6 100644 --- a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift +++ b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift @@ -123,7 +123,7 @@ public final class OmitExplicitReturns: SyntaxFormatRule { } private func containsSingleReturn(_ body: CodeBlockItemListSyntax) -> ReturnStmtSyntax? { - guard let element = body.firstAndOnly?.as(CodeBlockItemSyntax.self), + guard let element = body.firstAndOnly, let returnStmt = element.item.as(ReturnStmtSyntax.self) else { return nil diff --git a/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift b/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift index 927070df5..01de282c0 100644 --- a/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift +++ b/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift @@ -29,8 +29,8 @@ public final class ReplaceForEachWithForLoop : SyntaxLintRule { return .visitChildren } - guard let memberName = member.declName.baseName.as(TokenSyntax.self), - memberName.text == "forEach" else { + let memberName = member.declName.baseName + guard memberName.text == "forEach" else { return .visitChildren } From 5d45493b8f54041ead10ced7f60b2f7feaf1eb11 Mon Sep 17 00:00:00 2001 From: Natik Gadzhi Date: Sun, 3 Sep 2023 18:17:56 -0700 Subject: [PATCH 121/332] Output a warning for unknown config rules **Summary**: - Helps developers surface any errors and typos in their `.swift-format` config files by showing a warning when running `swift-format` if the configuration includes invalid rules. - The actual check is in `Frontend.swift`. - The dictionary of rules for verification is in `RuleRegistry`, which is made `public` to be able to import it in `swift-format` target. **Motivation**: - Closes #607 Apply suggestions from code review Co-authored-by: Tony Allevato Cleaning up based on PR review --- .../Core/RuleRegistry+Generated.swift | 4 ++-- .../RuleRegistryGenerator.swift | 4 ++-- Sources/swift-format/Frontend/Frontend.swift | 23 ++++++++++++++++--- .../Utilities/DiagnosticsEngine.swift | 14 +++++++++++ 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift index cd6c7ca45..7a0de7315 100644 --- a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift @@ -12,8 +12,8 @@ // This file is automatically generated with generate-pipeline. Do Not Edit! -enum RuleRegistry { - static let rules: [String: Bool] = [ +@_spi(Internal) public enum RuleRegistry { + public static let rules: [String: Bool] = [ "AllPublicDeclarationsHaveDocumentation": false, "AlwaysUseLowerCamelCase": true, "AmbiguousTrailingClosureOverload": true, diff --git a/Sources/generate-pipeline/RuleRegistryGenerator.swift b/Sources/generate-pipeline/RuleRegistryGenerator.swift index 7a5644202..8e4c66ff5 100644 --- a/Sources/generate-pipeline/RuleRegistryGenerator.swift +++ b/Sources/generate-pipeline/RuleRegistryGenerator.swift @@ -40,8 +40,8 @@ final class RuleRegistryGenerator: FileGenerator { // This file is automatically generated with generate-pipeline. Do Not Edit! - enum RuleRegistry { - static let rules: [String: Bool] = [ + @_spi(Internal) public enum RuleRegistry { + public static let rules: [String: Bool] = [ """ ) diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index de62ff4ad..28391a192 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftFormat +@_spi(Internal) import SwiftFormat import SwiftSyntax import SwiftParser @@ -161,17 +161,19 @@ class Frontend { } /// Returns the configuration that applies to the given `.swift` source file, when an explicit - /// configuration path is also perhaps provided. + /// configuration path is also perhaps provided. Checks for unrecognized rules within the configuration. /// /// - Parameters: /// - configurationFilePath: The path to a configuration file that will be loaded, or `nil` to /// try to infer it from `swiftFilePath`. /// - swiftFilePath: The path to a `.swift` file, which will be used to infer the path to the /// configuration file if `configurationFilePath` is nil. + /// /// - Returns: If successful, the returned configuration is the one loaded from /// `configurationFilePath` if it was provided, or by searching in paths inferred by /// `swiftFilePath` if one exists, or the default configuration otherwise. If an error occurred /// when reading the configuration, a diagnostic is emitted and `nil` is returned. + /// if neither `configurationFilePath` nor `swiftFilePath` were provided, a default `Configuration()` will be returned. private func configuration( at configurationFileURL: URL?, orInferredFromSwiftFileAt swiftFileURL: URL? @@ -180,7 +182,9 @@ class Frontend { // loaded. (Do not try to fall back to a path inferred from the source file path.) if let configurationFileURL = configurationFileURL { do { - return try configurationLoader.configuration(at: configurationFileURL) + let configuration = try configurationLoader.configuration(at: configurationFileURL) + self.checkForUnrecognizedRules(in: configuration) + return configuration } catch { diagnosticsEngine.emitError("Unable to read configuration: \(error.localizedDescription)") return nil @@ -192,6 +196,7 @@ class Frontend { if let swiftFileURL = swiftFileURL { do { if let configuration = try configurationLoader.configuration(forSwiftFileAt: swiftFileURL) { + self.checkForUnrecognizedRules(in: configuration) return configuration } // Fall through to the default return at the end of the function. @@ -207,4 +212,16 @@ class Frontend { // default configuration. return Configuration() } + + /// Checks if all the rules in the given configuration are supported by the registry. + /// If there are any rules that are not supported, they are emitted as a warning. + private func checkForUnrecognizedRules(in configuration: Configuration) { + // If any rules in the decoded configuration are not supported by the registry, + // emit them into the diagnosticsEngine as warnings. + // That way they will be printed out, but we'll continue execution on the valid rules. + let invalidRules = configuration.rules.filter { !RuleRegistry.rules.keys.contains($0.key) } + for rule in invalidRules { + diagnosticsEngine.emitWarning("Configuration contains an unrecognized rule: \(rule.key)", location: nil) + } + } } diff --git a/Sources/swift-format/Utilities/DiagnosticsEngine.swift b/Sources/swift-format/Utilities/DiagnosticsEngine.swift index c2353b196..a6cd6978f 100644 --- a/Sources/swift-format/Utilities/DiagnosticsEngine.swift +++ b/Sources/swift-format/Utilities/DiagnosticsEngine.swift @@ -65,6 +65,20 @@ final class DiagnosticsEngine { message: message)) } + /// Emits a generic warning message. + /// + /// - Parameters: + /// - message: The message associated with the error. + /// - location: The location in the source code associated with the error, or nil if there is no + /// location associated with the error. + func emitWarning(_ message: String, location: SourceLocation? = nil) { + emit( + Diagnostic( + severity: .warning, + location: location.map(Diagnostic.Location.init), + message: message)) + } + /// Emits a finding from the linter and any of its associated notes as diagnostics. /// /// - Parameter finding: The finding that should be emitted. From e350bded948eeea14cc976539e9e3141a49fabd0 Mon Sep 17 00:00:00 2001 From: Natik Gadzhi Date: Wed, 6 Sep 2023 15:29:00 -0700 Subject: [PATCH 122/332] Update Sources/generate-pipeline/RuleCollector.swift --- Sources/generate-pipeline/RuleCollector.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index cc4e4c81e..bad5365a5 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -22,7 +22,7 @@ final class RuleCollector { struct DetectedRule: Hashable { /// The DocC comments of the rule, - /// presumably in the leading trivia of the rule declaration. + /// extracted from the .leadingTrivia of the rule class or struct. let doccComment: String /// The type name of the rule. From 284ed056b2219bd91af38dd1eaa22ccf34a637bb Mon Sep 17 00:00:00 2001 From: Natik Gadzhi Date: Wed, 6 Sep 2023 15:56:58 -0700 Subject: [PATCH 123/332] Improvements to auto-generated rule docs - Using `DocumentationCommentText` via `@_spi(Rules)` instead of our own implementation of rule description extracted from trivia. - Added table of contents with all the available rules linked to their respective blocks. --- Documentation/RuleDocumentation.md | 50 +++++++++++++++++++ .../Core/DocumentationCommentText.swift | 1 + Sources/generate-pipeline/RuleCollector.swift | 15 +++--- .../RuleDocumentationGenerator.swift | 34 ++++--------- 4 files changed, 68 insertions(+), 32 deletions(-) diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index 93aae0a5a..f1d68b89f 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -9,6 +9,47 @@ configuration file, as described in applied in the linter, but only some of them can format your source code automatically. +Here's the list of available rules: + +- [AllPublicDeclarationsHaveDocumentation](#AllPublicDeclarationsHaveDocumentation) +- [AlwaysUseLowerCamelCase](#AlwaysUseLowerCamelCase) +- [AmbiguousTrailingClosureOverload](#AmbiguousTrailingClosureOverload) +- [BeginDocumentationCommentWithOneLineSummary](#BeginDocumentationCommentWithOneLineSummary) +- [DoNotUseSemicolons](#DoNotUseSemicolons) +- [DontRepeatTypeInStaticProperties](#DontRepeatTypeInStaticProperties) +- [FileScopedDeclarationPrivacy](#FileScopedDeclarationPrivacy) +- [FullyIndirectEnum](#FullyIndirectEnum) +- [GroupNumericLiterals](#GroupNumericLiterals) +- [IdentifiersMustBeASCII](#IdentifiersMustBeASCII) +- [NeverForceUnwrap](#NeverForceUnwrap) +- [NeverUseForceTry](#NeverUseForceTry) +- [NeverUseImplicitlyUnwrappedOptionals](#NeverUseImplicitlyUnwrappedOptionals) +- [NoAccessLevelOnExtensionDeclaration](#NoAccessLevelOnExtensionDeclaration) +- [NoAssignmentInExpressions](#NoAssignmentInExpressions) +- [NoBlockComments](#NoBlockComments) +- [NoCasesWithOnlyFallthrough](#NoCasesWithOnlyFallthrough) +- [NoEmptyTrailingClosureParentheses](#NoEmptyTrailingClosureParentheses) +- [NoLabelsInCasePatterns](#NoLabelsInCasePatterns) +- [NoLeadingUnderscores](#NoLeadingUnderscores) +- [NoParensAroundConditions](#NoParensAroundConditions) +- [NoPlaygroundLiterals](#NoPlaygroundLiterals) +- [NoVoidReturnOnFunctionSignature](#NoVoidReturnOnFunctionSignature) +- [OmitExplicitReturns](#OmitExplicitReturns) +- [OneCasePerLine](#OneCasePerLine) +- [OneVariableDeclarationPerLine](#OneVariableDeclarationPerLine) +- [OnlyOneTrailingClosureArgument](#OnlyOneTrailingClosureArgument) +- [OrderedImports](#OrderedImports) +- [ReplaceForEachWithForLoop](#ReplaceForEachWithForLoop) +- [ReturnVoidInsteadOfEmptyTuple](#ReturnVoidInsteadOfEmptyTuple) +- [TypeNamesShouldBeCapitalized](#TypeNamesShouldBeCapitalized) +- [UseEarlyExits](#UseEarlyExits) +- [UseLetInEveryBoundCaseVariable](#UseLetInEveryBoundCaseVariable) +- [UseShorthandTypeNames](#UseShorthandTypeNames) +- [UseSingleLinePropertyGetter](#UseSingleLinePropertyGetter) +- [UseSynthesizedInitializer](#UseSynthesizedInitializer) +- [UseTripleSlashForDocumentationComments](#UseTripleSlashForDocumentationComments) +- [UseWhereClausesInForLoops](#UseWhereClausesInForLoops) +- [ValidateDocumentationComments](#ValidateDocumentationComments) ### AllPublicDeclarationsHaveDocumentation @@ -261,6 +302,15 @@ Format: Parentheses around such expressions are removed, if they do not cause a `NoParensAroundConditions` rule can format your code automatically. +### NoPlaygroundLiterals + +The playground literals (`#colorLiteral`, `#fileLiteral`, and `#imageLiteral`) are forbidden. + +Lint: Using a playground literal will yield a lint error with a suggestion of an API to replace +it. + +`NoPlaygroundLiterals` is a linter-only rule. + ### NoVoidReturnOnFunctionSignature Functions that return `()` or `Void` should omit the return signature. diff --git a/Sources/SwiftFormat/Core/DocumentationCommentText.swift b/Sources/SwiftFormat/Core/DocumentationCommentText.swift index 335bbbab8..44ef1a61a 100644 --- a/Sources/SwiftFormat/Core/DocumentationCommentText.swift +++ b/Sources/SwiftFormat/Core/DocumentationCommentText.swift @@ -18,6 +18,7 @@ import SwiftSyntax /// structural organization. It automatically handles trimming leading indentation from comments as /// well as "ASCII art" in block comments (i.e., leading asterisks on each line). @_spi(Testing) +@_spi(Rules) public struct DocumentationCommentText { /// Denotes the kind of punctuation used to introduce the comment. public enum Introducer { diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index bad5365a5..d3564f11c 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -20,14 +20,13 @@ import SwiftParser final class RuleCollector { /// Information about a detected rule. struct DetectedRule: Hashable { - - /// The DocC comments of the rule, - /// extracted from the .leadingTrivia of the rule class or struct. - let doccComment: String - /// The type name of the rule. let typeName: String + /// The description of the rule, extracted from the rule class or struct DocC comment + /// with `DocumentationCommentText(extractedFrom:)` + let description: String? + /// The syntax node types visited by the rule type. let visitedNodes: [String] @@ -91,15 +90,13 @@ final class RuleCollector { let typeName: String let members: MemberBlockItemListSyntax let maybeInheritanceClause: InheritanceClauseSyntax? - let doccComment: String + let description = DocumentationCommentText(extractedFrom: statement.item.leadingTrivia) if let classDecl = statement.item.as(ClassDeclSyntax.self) { - doccComment = classDecl.leadingTrivia.description typeName = classDecl.name.text members = classDecl.memberBlock.members maybeInheritanceClause = classDecl.inheritanceClause } else if let structDecl = statement.item.as(StructDeclSyntax.self) { - doccComment = structDecl.leadingTrivia.description typeName = structDecl.name.text members = structDecl.memberBlock.members maybeInheritanceClause = structDecl.inheritanceClause @@ -148,8 +145,8 @@ final class RuleCollector { preconditionFailure("Failed to find type for rule named \(typeName)") } return DetectedRule( - doccComment: doccComment, typeName: typeName, + description: description?.text, visitedNodes: visitedNodes, canFormat: canFormat, isOptIn: ruleType.isOptIn) diff --git a/Sources/generate-pipeline/RuleDocumentationGenerator.swift b/Sources/generate-pipeline/RuleDocumentationGenerator.swift index 1930f0e47..fccddee4f 100644 --- a/Sources/generate-pipeline/RuleDocumentationGenerator.swift +++ b/Sources/generate-pipeline/RuleDocumentationGenerator.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation +import SwiftFormat /// Generates the markdown file with extended documenation on the available rules. final class RuleDocumentationGenerator: FileGenerator { @@ -37,17 +38,25 @@ final class RuleDocumentationGenerator: FileGenerator { applied in the linter, but only some of them can format your source code automatically. + Here's the list of available rules: + """ ) for detectedRule in ruleCollector.allLinters.sorted(by: { $0.typeName < $1.typeName }) { handle.write(""" + - [\(detectedRule.typeName)](#\(detectedRule.typeName)) - ### \(detectedRule.typeName) + """) + } + + for detectedRule in ruleCollector.allLinters.sorted(by: { $0.typeName < $1.typeName }) { + handle.write(""" - \(ruleDescription(for: detectedRule)) + ### \(detectedRule.typeName) + \(detectedRule.description ?? "") \(ruleFormatSupportDescription(for: detectedRule)) """) @@ -59,25 +68,4 @@ final class RuleDocumentationGenerator: FileGenerator { "`\(rule.typeName)` rule can format your code automatically." : "`\(rule.typeName)` is a linter-only rule." } - - /// Takes the DocC comment of the rule and strip `///` from the beginning of each line. - /// Also removes empty lines with under 4 characters. - private func ruleDescription(for rule: RuleCollector.DetectedRule) -> String { - let described = rule.doccComment.split(whereSeparator: \.isNewline) - .compactMap { line in - // Remove the first 4 characters, i.e. `/// ` - if line.count >= 4 { - let index = line.index(line.startIndex, offsetBy: 4) - return line.suffix(from: index) - } else { - // For lines that have less than 4 characters, emit an empty line. - return "" - } - } - // not great, but that shoves multiple lines into the single - // table cell in markdown - .joined(separator: "\n") - - return described - } } From ff93f4966c88716ef401471f9d5758daff6ba815 Mon Sep 17 00:00:00 2001 From: Marc Lavergne Date: Wed, 6 Sep 2023 19:27:01 -0400 Subject: [PATCH 124/332] Add option to disable trailing commas on multi-line collections Extends the configuration options to allow disabling the addtion of trailing commas on multi-line collections. --- Documentation/Configuration.md | 3 + .../API/Configuration+Default.swift | 1 + Sources/SwiftFormat/API/Configuration.swift | 9 ++ .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 2 +- .../Configuration+Testing.swift | 1 + .../PrettyPrint/CommaTests.swift | 145 ++++++++++++++++++ 6 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 6f1092103..86819b7a9 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -82,6 +82,9 @@ top-level keys and values: * `spacesAroundRangeFormationOperators` _(boolean)_: Determines whether whitespace should be forced before and after the range formation operators `...` and `..<`. +* `multilineCollectionTrailingCommas` _(boolean)_: Determines whether multi-line collections should have trailing commas. + Defaults to `true`. + > TODO: Add support for enabling/disabling specific syntax transformations in > the pipeline. diff --git a/Sources/SwiftFormat/API/Configuration+Default.swift b/Sources/SwiftFormat/API/Configuration+Default.swift index 61ba05c34..89025aa54 100644 --- a/Sources/SwiftFormat/API/Configuration+Default.swift +++ b/Sources/SwiftFormat/API/Configuration+Default.swift @@ -37,5 +37,6 @@ extension Configuration { self.indentSwitchCaseLabels = false self.spacesAroundRangeFormationOperators = false self.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() + self.multilineCollectionTrailingCommas = true } } diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 13064c6e7..96feef365 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -42,6 +42,7 @@ public struct Configuration: Codable, Equatable { case rules case spacesAroundRangeFormationOperators case noAssignmentInExpressions + case multilineCollectionTrailingCommas } /// A dictionary containing the default enabled/disabled states of rules, keyed by the rules' @@ -162,6 +163,9 @@ public struct Configuration: Codable, Equatable { /// Contains exceptions for the `NoAssignmentInExpressions` rule. public var noAssignmentInExpressions: NoAssignmentInExpressionsConfiguration + /// Determines whether multi-line list initializers should have trailing commas + public var multilineCollectionTrailingCommas: Bool + /// Constructs a Configuration by loading it from a configuration file. public init(contentsOf url: URL) throws { let data = try Data(contentsOf: url) @@ -239,6 +243,10 @@ public struct Configuration: Codable, Equatable { try container.decodeIfPresent( NoAssignmentInExpressionsConfiguration.self, forKey: .noAssignmentInExpressions) ?? defaults.noAssignmentInExpressions + self.multilineCollectionTrailingCommas = + try container.decodeIfPresent( + Bool.self, forKey: .multilineCollectionTrailingCommas) + ?? defaults.multilineCollectionTrailingCommas // If the `rules` key is not present at all, default it to the built-in set // so that the behavior is the same as if the configuration had been @@ -271,6 +279,7 @@ public struct Configuration: Codable, Equatable { try container.encode(fileScopedDeclarationPrivacy, forKey: .fileScopedDeclarationPrivacy) try container.encode(indentSwitchCaseLabels, forKey: .indentSwitchCaseLabels) try container.encode(noAssignmentInExpressions, forKey: .noAssignmentInExpressions) + try container.encode(multilineCollectionTrailingCommas, forKey: .multilineCollectionTrailingCommas) try container.encode(rules, forKey: .rules) } diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 97a1c5fc6..ba962184a 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -557,7 +557,7 @@ public class PrettyPrinter { // We never want to add a trailing comma in an initializer so we disable trailing commas on // single element collections. let shouldHaveTrailingComma = - startLineNumber != openCloseBreakCompensatingLineNumber && !isSingleElement + startLineNumber != openCloseBreakCompensatingLineNumber && !isSingleElement && configuration.multilineCollectionTrailingCommas if shouldHaveTrailingComma && !hasTrailingComma { diagnose(.addTrailingComma, category: .trailingComma) } else if !shouldHaveTrailingComma && hasTrailingComma { diff --git a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift index 36cd2971d..0d560e48c 100644 --- a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift +++ b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift @@ -40,6 +40,7 @@ extension Configuration { config.indentSwitchCaseLabels = false config.spacesAroundRangeFormationOperators = false config.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() + config.multilineCollectionTrailingCommas = true return config } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift new file mode 100644 index 000000000..4df645713 --- /dev/null +++ b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift @@ -0,0 +1,145 @@ +import SwiftFormat + +final class CommaTests: PrettyPrintTestCase { + func testCommasAbsentEnabled() { + let input = + """ + let MyList = [ + 1, + 2, + 3 + ] + + """ + + let expected = + """ + let MyList = [ + 1, + 2, + 3, + ] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) + } + + func testCommasAbsentDisabled() { + let input = + """ + let MyList = [ + 1, + 2, + 3 + ] + + """ + + let expected = + """ + let MyList = [ + 1, + 2, + 3 + ] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) + } + + func testCommasPresentEnabled() { + let input = + """ + let MyList = [ + 1, + 2, + 3, + ] + + """ + + let expected = + """ + let MyList = [ + 1, + 2, + 3, + ] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) + } + + func testCommasPresentDisabled() { + let input = + """ + let MyList = [ + 1, + 2, + 3, + ] + + """ + + let expected = + """ + let MyList = [ + 1, + 2, + 3 + ] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) + } + + func testCommasPresentSingleLineDisabled() { + let input = + """ + let MyList = [1, 2, 3,] + + """ + + // no effect expected + let expected = + """ + let MyList = [1, 2, 3] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) + } + + func testCommasPresentSingleLineEnabled() { + let input = + """ + let MyList = [1, 2, 3,] + + """ + + // no effect expected + let expected = + """ + let MyList = [1, 2, 3] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) + } +} From da79b036abbf0536c594472381df5a5b8d7f4143 Mon Sep 17 00:00:00 2001 From: Marc Lavergne Date: Thu, 7 Sep 2023 16:13:02 -0400 Subject: [PATCH 125/332] - adopt PR recommendations --- Sources/SwiftFormat/API/Configuration.swift | 2 +- .../PrettyPrint/CommaTests.swift | 180 ++++++++++++++++-- 2 files changed, 163 insertions(+), 19 deletions(-) diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 96feef365..6ab0f7541 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -163,7 +163,7 @@ public struct Configuration: Codable, Equatable { /// Contains exceptions for the `NoAssignmentInExpressions` rule. public var noAssignmentInExpressions: NoAssignmentInExpressionsConfiguration - /// Determines whether multi-line list initializers should have trailing commas + /// Determines whether multi-line list initializers should have trailing commas. public var multilineCollectionTrailingCommas: Bool /// Constructs a Configuration by loading it from a configuration file. diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift index 4df645713..a030912db 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift @@ -1,10 +1,10 @@ import SwiftFormat final class CommaTests: PrettyPrintTestCase { - func testCommasAbsentEnabled() { + func testArrayCommasAbsentEnabled() { let input = """ - let MyList = [ + let MyCollection = [ 1, 2, 3 @@ -14,7 +14,7 @@ final class CommaTests: PrettyPrintTestCase { let expected = """ - let MyList = [ + let MyCollection = [ 1, 2, 3, @@ -27,10 +27,10 @@ final class CommaTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - func testCommasAbsentDisabled() { + func testArrayCommasAbsentDisabled() { let input = """ - let MyList = [ + let MyCollection = [ 1, 2, 3 @@ -40,7 +40,7 @@ final class CommaTests: PrettyPrintTestCase { let expected = """ - let MyList = [ + let MyCollection = [ 1, 2, 3 @@ -53,10 +53,10 @@ final class CommaTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - func testCommasPresentEnabled() { + func testArrayCommasPresentEnabled() { let input = """ - let MyList = [ + let MyCollection = [ 1, 2, 3, @@ -66,7 +66,7 @@ final class CommaTests: PrettyPrintTestCase { let expected = """ - let MyList = [ + let MyCollection = [ 1, 2, 3, @@ -79,10 +79,10 @@ final class CommaTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - func testCommasPresentDisabled() { + func testArrayCommasPresentDisabled() { let input = """ - let MyList = [ + let MyCollection = [ 1, 2, 3, @@ -92,7 +92,7 @@ final class CommaTests: PrettyPrintTestCase { let expected = """ - let MyList = [ + let MyCollection = [ 1, 2, 3 @@ -105,17 +105,17 @@ final class CommaTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - func testCommasPresentSingleLineDisabled() { + func testArraySingleLineCommasPresentDisabled() { let input = """ - let MyList = [1, 2, 3,] + let MyCollection = [1, 2, 3,] """ // no effect expected let expected = """ - let MyList = [1, 2, 3] + let MyCollection = [1, 2, 3] """ @@ -124,17 +124,161 @@ final class CommaTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } - func testCommasPresentSingleLineEnabled() { + func testArraySingleLineCommasPresentEnabled() { let input = """ - let MyList = [1, 2, 3,] + let MyCollection = [1, 2, 3,] """ // no effect expected let expected = """ - let MyList = [1, 2, 3] + let MyCollection = [1, 2, 3] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) + } + + func testDictionaryCommasAbsentEnabled() { + let input = + """ + let MyCollection = [ + "a": 1, + "b": 2, + "c": 3 + ] + + """ + + let expected = + """ + let MyCollection = [ + "a": 1, + "b": 2, + "c": 3, + ] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) + } + + func testDictionaryCommasAbsentDisabled() { + let input = + """ + let MyCollection = [ + "a": 1, + "b": 2, + "c": 3 + ] + + """ + + let expected = + """ + let MyCollection = [ + "a": 1, + "b": 2, + "c": 3 + ] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) + } + + func testDictionaryCommasPresentEnabled() { + let input = + """ + let MyCollection = [ + "a": 1, + "b": 2, + "c": 3, + ] + + """ + + let expected = + """ + let MyCollection = [ + "a": 1, + "b": 2, + "c": 3, + ] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) + } + + func testDictionaryCommasPresentDisabled() { + let input = + """ + let MyCollection = [ + "a": 1, + "b": 2, + "c": 3, + ] + + """ + + let expected = + """ + let MyCollection = [ + "a": 1, + "b": 2, + "c": 3 + ] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) + } + + func testDictionarySingleLineCommasPresentDisabled() { + let input = + """ + let MyCollection = ["a": 1, "b": 2, "c": 3,] + + """ + + let expected = + """ + let MyCollection = [ + "a": 1, "b": 2, "c": 3, + ] + + """ + + var configuration = Configuration.forTesting + configuration.multilineCollectionTrailingCommas = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) + } + + func testDictionarySingleLineCommasPresentEnabled() { + let input = + """ + let MyCollection = ["a": 1, "b": 2, "c": 3,] + + """ + + let expected = + """ + let MyCollection = [ + "a": 1, "b": 2, "c": 3 + ] """ From f7140e9355ea3e54dfe9b58c9145444a0af1e5b4 Mon Sep 17 00:00:00 2001 From: Marc Lavergne Date: Sat, 9 Sep 2023 12:23:58 -0400 Subject: [PATCH 126/332] - PR setting renamed to multiElementCollectionTrailingCommas and use literal rather than initializer --- Documentation/Configuration.md | 2 +- .../API/Configuration+Default.swift | 2 +- Sources/SwiftFormat/API/Configuration.swift | 14 +++++------ .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 2 +- .../Configuration+Testing.swift | 2 +- .../PrettyPrint/CommaTests.swift | 24 +++++++++---------- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 86819b7a9..774eea217 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -82,7 +82,7 @@ top-level keys and values: * `spacesAroundRangeFormationOperators` _(boolean)_: Determines whether whitespace should be forced before and after the range formation operators `...` and `..<`. -* `multilineCollectionTrailingCommas` _(boolean)_: Determines whether multi-line collections should have trailing commas. +* `multiElementCollectionTrailingCommas` _(boolean)_: Determines whether multi-element collection literals should have trailing commas. Defaults to `true`. > TODO: Add support for enabling/disabling specific syntax transformations in diff --git a/Sources/SwiftFormat/API/Configuration+Default.swift b/Sources/SwiftFormat/API/Configuration+Default.swift index 89025aa54..3f0123fb4 100644 --- a/Sources/SwiftFormat/API/Configuration+Default.swift +++ b/Sources/SwiftFormat/API/Configuration+Default.swift @@ -37,6 +37,6 @@ extension Configuration { self.indentSwitchCaseLabels = false self.spacesAroundRangeFormationOperators = false self.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() - self.multilineCollectionTrailingCommas = true + self.multiElementCollectionTrailingCommas = true } } diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 6ab0f7541..56de515f0 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -42,7 +42,7 @@ public struct Configuration: Codable, Equatable { case rules case spacesAroundRangeFormationOperators case noAssignmentInExpressions - case multilineCollectionTrailingCommas + case multiElementCollectionTrailingCommas } /// A dictionary containing the default enabled/disabled states of rules, keyed by the rules' @@ -163,8 +163,8 @@ public struct Configuration: Codable, Equatable { /// Contains exceptions for the `NoAssignmentInExpressions` rule. public var noAssignmentInExpressions: NoAssignmentInExpressionsConfiguration - /// Determines whether multi-line list initializers should have trailing commas. - public var multilineCollectionTrailingCommas: Bool + /// Determines whether multi-element collection literals should have trailing commas. + public var multiElementCollectionTrailingCommas: Bool /// Constructs a Configuration by loading it from a configuration file. public init(contentsOf url: URL) throws { @@ -243,10 +243,10 @@ public struct Configuration: Codable, Equatable { try container.decodeIfPresent( NoAssignmentInExpressionsConfiguration.self, forKey: .noAssignmentInExpressions) ?? defaults.noAssignmentInExpressions - self.multilineCollectionTrailingCommas = + self.multiElementCollectionTrailingCommas = try container.decodeIfPresent( - Bool.self, forKey: .multilineCollectionTrailingCommas) - ?? defaults.multilineCollectionTrailingCommas + Bool.self, forKey: .multiElementCollectionTrailingCommas) + ?? defaults.multiElementCollectionTrailingCommas // If the `rules` key is not present at all, default it to the built-in set // so that the behavior is the same as if the configuration had been @@ -279,7 +279,7 @@ public struct Configuration: Codable, Equatable { try container.encode(fileScopedDeclarationPrivacy, forKey: .fileScopedDeclarationPrivacy) try container.encode(indentSwitchCaseLabels, forKey: .indentSwitchCaseLabels) try container.encode(noAssignmentInExpressions, forKey: .noAssignmentInExpressions) - try container.encode(multilineCollectionTrailingCommas, forKey: .multilineCollectionTrailingCommas) + try container.encode(multiElementCollectionTrailingCommas, forKey: .multiElementCollectionTrailingCommas) try container.encode(rules, forKey: .rules) } diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index ba962184a..8447ff8d2 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -557,7 +557,7 @@ public class PrettyPrinter { // We never want to add a trailing comma in an initializer so we disable trailing commas on // single element collections. let shouldHaveTrailingComma = - startLineNumber != openCloseBreakCompensatingLineNumber && !isSingleElement && configuration.multilineCollectionTrailingCommas + startLineNumber != openCloseBreakCompensatingLineNumber && !isSingleElement && configuration.multiElementCollectionTrailingCommas if shouldHaveTrailingComma && !hasTrailingComma { diagnose(.addTrailingComma, category: .trailingComma) } else if !shouldHaveTrailingComma && hasTrailingComma { diff --git a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift index 0d560e48c..e9ab39c34 100644 --- a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift +++ b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift @@ -40,7 +40,7 @@ extension Configuration { config.indentSwitchCaseLabels = false config.spacesAroundRangeFormationOperators = false config.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() - config.multilineCollectionTrailingCommas = true + config.multiElementCollectionTrailingCommas = true return config } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift index a030912db..24bd0238e 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift @@ -23,7 +23,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = true + configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } @@ -49,7 +49,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = false + configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } @@ -75,7 +75,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = true + configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } @@ -101,7 +101,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = false + configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } @@ -120,7 +120,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = true + configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } @@ -139,7 +139,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = false + configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } @@ -165,7 +165,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = true + configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } @@ -191,7 +191,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = false + configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } @@ -217,7 +217,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = true + configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } @@ -243,7 +243,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = false + configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } @@ -263,7 +263,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = true + configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } @@ -283,7 +283,7 @@ final class CommaTests: PrettyPrintTestCase { """ var configuration = Configuration.forTesting - configuration.multilineCollectionTrailingCommas = false + configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } } From 1454d469ac5195e7f6343a193d28a78f990ba133 Mon Sep 17 00:00:00 2001 From: Marc Lavergne Date: Sun, 10 Sep 2023 12:10:21 -0400 Subject: [PATCH 127/332] - PR add DocC header for configuration setting --- Sources/SwiftFormat/API/Configuration.swift | 22 ++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 56de515f0..6ef959695 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -163,7 +163,27 @@ public struct Configuration: Codable, Equatable { /// Contains exceptions for the `NoAssignmentInExpressions` rule. public var noAssignmentInExpressions: NoAssignmentInExpressionsConfiguration - /// Determines whether multi-element collection literals should have trailing commas. + /// Determines if multi-element collection literals should have trailing commas. + /// + /// When `true` (default), the correct form is: + /// ```swift + /// let MyCollection = [1, 2,] + /// ... + /// let MyCollection = [ + /// "a": 1, + /// "b": 2, + /// ] + /// ``` + /// + /// When `false`, the correct form is: + /// ```swift + /// let MyCollection = [1, 2] + /// ... + /// let MyCollection = [ + /// "a": 1, + /// "b": 2 + /// ] + /// ``` public var multiElementCollectionTrailingCommas: Bool /// Constructs a Configuration by loading it from a configuration file. From 5a48d08e7351da68d9fef1bd682d561c9b2780ba Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 6 Sep 2023 11:24:27 -0700 Subject: [PATCH 128/332] [Format/Lint] Add a rule to detect and transform `[]()` into literal array init Instead of using an archaic initializer call syntax, let's replace it with an empty array literal with a type annotation (if there isn't one). --- .../Core/Pipelines+Generated.swift | 2 + .../Core/RuleNameCache+Generated.swift | 1 + .../Core/RuleRegistry+Generated.swift | 1 + .../AlwaysUseLiteralForEmptyArrayInit.swift | 81 +++++++++++++++++++ ...waysUseLiteralForEmptyArrayInitTests.swift | 48 +++++++++++ 5 files changed, 133 insertions(+) create mode 100644 Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift create mode 100644 Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyArrayInitTests.swift diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 6603fcb72..eabb3bc84 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -243,6 +243,7 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: PatternBindingSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AlwaysUseLiteralForEmptyArrayInit.visit, for: node) visitIfEnabled(OmitExplicitReturns.visit, for: node) visitIfEnabled(UseSingleLinePropertyGetter.visit, for: node) return .visitChildren @@ -356,6 +357,7 @@ extension FormatPipeline { func rewrite(_ node: Syntax) -> Syntax { var node = node + node = AlwaysUseLiteralForEmptyArrayInit(context: context).rewrite(node) node = DoNotUseSemicolons(context: context).rewrite(node) node = FileScopedDeclarationPrivacy(context: context).rewrite(node) node = FullyIndirectEnum(context: context).rewrite(node) diff --git a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift index 453f82940..d1372da5a 100644 --- a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift @@ -16,6 +16,7 @@ @_spi(Testing) public let ruleNameCache: [ObjectIdentifier: String] = [ ObjectIdentifier(AllPublicDeclarationsHaveDocumentation.self): "AllPublicDeclarationsHaveDocumentation", + ObjectIdentifier(AlwaysUseLiteralForEmptyArrayInit.self): "AlwaysUseLiteralForEmptyArrayInit", ObjectIdentifier(AlwaysUseLowerCamelCase.self): "AlwaysUseLowerCamelCase", ObjectIdentifier(AmbiguousTrailingClosureOverload.self): "AmbiguousTrailingClosureOverload", ObjectIdentifier(BeginDocumentationCommentWithOneLineSummary.self): "BeginDocumentationCommentWithOneLineSummary", diff --git a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift index 7a0de7315..ef0204471 100644 --- a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift @@ -15,6 +15,7 @@ @_spi(Internal) public enum RuleRegistry { public static let rules: [String: Bool] = [ "AllPublicDeclarationsHaveDocumentation": false, + "AlwaysUseLiteralForEmptyArrayInit": true, "AlwaysUseLowerCamelCase": true, "AmbiguousTrailingClosureOverload": true, "BeginDocumentationCommentWithOneLineSummary": false, diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift new file mode 100644 index 000000000..cc9a2a03b --- /dev/null +++ b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation +import SwiftSyntax +import SwiftParser + +/// Never use `[]()` syntax. In call sites that should be replaced with `[]`, +/// for initializations use explicit type combined with empty array literal `let _: [] = []` +/// Static properties of a type that return that type should not include a reference to their type. +/// +/// Lint: Non-literal empty array initialization will yield a lint error. +/// Format: All invalid use sites would be related with empty literal (with or without explicit type annotation). +@_spi(Rules) +public final class AlwaysUseLiteralForEmptyArrayInit : SyntaxFormatRule { + public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax { + guard let initializer = node.initializer else { + return node + } + + // Check whether the initializer is `[]()` + guard let initCall = initializer.value.as(FunctionCallExprSyntax.self), + var arrayLiteral = initCall.calledExpression.as(ArrayExprSyntax.self), + initCall.arguments.isEmpty else { + return node + } + + guard let elementType = getElementType(arrayLiteral) else { + return node + } + + var replacement = node + + var withFixIt = "[]" + if replacement.typeAnnotation == nil { + withFixIt = ": [\(elementType)] = []" + } + + diagnose(.refactorEmptyArrayInit(replace: "\(initCall)", with: withFixIt), on: initCall) + + if replacement.typeAnnotation == nil { + // Drop trailing trivia after pattern because ':' has to appear connected to it. + replacement.pattern = node.pattern.with(\.trailingTrivia, []) + // Add explicit type annotiation: ': []` + replacement.typeAnnotation = .init(type: ArrayTypeSyntax(leadingTrivia: .space, + element: elementType, + trailingTrivia: .space)) + } + + // Replace initializer call with empty array literal: `[]()` -> `[]` + arrayLiteral.elements = ArrayElementListSyntax.init([]) + replacement.initializer = initializer.with(\.value, ExprSyntax(arrayLiteral)) + + return replacement + } + + private func getElementType(_ arrayLiteral: ArrayExprSyntax) -> TypeSyntax? { + guard let elementExpr = arrayLiteral.elements.firstAndOnly?.as(ArrayElementSyntax.self) else { + return nil + } + + var parser = Parser(elementExpr.description) + let elementType = TypeSyntax.parse(from: &parser) + return elementType.hasError ? nil : elementType + } +} + +extension Finding.Message { + public static func refactorEmptyArrayInit(replace: String, with: String) -> Finding.Message { + "replace '\(replace)' with '\(with)'" + } +} diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyArrayInitTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyArrayInitTests.swift new file mode 100644 index 000000000..3503b4805 --- /dev/null +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyArrayInitTests.swift @@ -0,0 +1,48 @@ +import _SwiftFormatTestSupport + +@_spi(Rules) import SwiftFormat + +final class AlwaysUseLiteralForEmptyArrayInitTests: LintOrFormatRuleTestCase { + func testPatternBindings() { + assertFormatting( + AlwaysUseLiteralForEmptyArrayInit.self, + input: """ + public struct Test { + var value1 = 1️⃣[Int]() + + func test(v: [Double] = [Double]()) { + let _ = 2️⃣[String]() + } + } + + var _: [Category] = 3️⃣[Category]() + let _ = 4️⃣[(Int, Array)]() + let _: [(String, Int, Float)] = 5️⃣[(String, Int, Float)]() + + let _ = [(1, 2, String)]() + """, + expected: """ + public struct Test { + var value1: [Int] = [] + + func test(v: [Double] = [Double]()) { + let _: [String] = [] + } + } + + var _: [Category] = [] + let _: [(Int, Array)] = [] + let _: [(String, Int, Float)] = [] + + let _ = [(1, 2, String)]() + """, + findings: [ + FindingSpec("1️⃣", message: "replace '[Int]()' with ': [Int] = []'"), + FindingSpec("2️⃣", message: "replace '[String]()' with ': [String] = []'"), + FindingSpec("3️⃣", message: "replace '[Category]()' with '[]'"), + FindingSpec("4️⃣", message: "replace '[(Int, Array)]()' with ': [(Int, Array)] = []'"), + FindingSpec("5️⃣", message: "replace '[(String, Int, Float)]()' with '[]'"), + ] + ) + } +} From cef2c6e436a15c905e0deeaf5161db13377dc52e Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 6 Sep 2023 13:19:56 -0700 Subject: [PATCH 129/332] Make AlwaysUseLiteralForEmptyArrayInit rule opt-in and disabled by default --- Sources/SwiftFormat/Core/RuleRegistry+Generated.swift | 2 +- .../SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift index ef0204471..4cc83beec 100644 --- a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift @@ -15,7 +15,7 @@ @_spi(Internal) public enum RuleRegistry { public static let rules: [String: Bool] = [ "AllPublicDeclarationsHaveDocumentation": false, - "AlwaysUseLiteralForEmptyArrayInit": true, + "AlwaysUseLiteralForEmptyArrayInit": false, "AlwaysUseLowerCamelCase": true, "AmbiguousTrailingClosureOverload": true, "BeginDocumentationCommentWithOneLineSummary": false, diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift index cc9a2a03b..09f98b67c 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift @@ -22,6 +22,8 @@ import SwiftParser /// Format: All invalid use sites would be related with empty literal (with or without explicit type annotation). @_spi(Rules) public final class AlwaysUseLiteralForEmptyArrayInit : SyntaxFormatRule { + public override class var isOptIn: Bool { return true } + public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax { guard let initializer = node.initializer else { return node From afaa91dedd8f2ea94bb4981fa43ecad6c1001d6f Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 8 Sep 2023 13:02:24 -0700 Subject: [PATCH 130/332] [Lint/Format] Extend empty literal initialization rule to support dictionaries --- .../AlwaysUseLiteralForEmptyArrayInit.swift | 101 ++++++++++++++---- ...waysUseLiteralForEmptyArrayInitTests.swift | 45 +++++++- 2 files changed, 123 insertions(+), 23 deletions(-) diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift index 09f98b67c..f8cd40c38 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift @@ -25,59 +25,116 @@ public final class AlwaysUseLiteralForEmptyArrayInit : SyntaxFormatRule { public override class var isOptIn: Bool { return true } public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax { - guard let initializer = node.initializer else { - return node - } - // Check whether the initializer is `[]()` - guard let initCall = initializer.value.as(FunctionCallExprSyntax.self), - var arrayLiteral = initCall.calledExpression.as(ArrayExprSyntax.self), + guard let initializer = node.initializer, + let initCall = initializer.value.as(FunctionCallExprSyntax.self), initCall.arguments.isEmpty else { return node } - guard let elementType = getElementType(arrayLiteral) else { - return node + if let arrayLiteral = initCall.calledExpression.as(ArrayExprSyntax.self), + let type = getLiteralType(arrayLiteral) { + return rewrite(node, type: type) + } + + if let dictLiteral = initCall.calledExpression.as(DictionaryExprSyntax.self), + let type = getLiteralType(dictLiteral) { + return rewrite(node, type: type) } + return node + } + + private func rewrite(_ node: PatternBindingSyntax, + type: ArrayTypeSyntax) -> PatternBindingSyntax { var replacement = node - var withFixIt = "[]" + diagnose(node, type: type) + if replacement.typeAnnotation == nil { - withFixIt = ": [\(elementType)] = []" + // Drop trailing trivia after pattern because ':' has to appear connected to it. + replacement.pattern = node.pattern.with(\.trailingTrivia, []) + // Add explicit type annotiation: ': []` + replacement.typeAnnotation = .init(type: type.with(\.leadingTrivia, .space) + .with(\.trailingTrivia, .space)) } - diagnose(.refactorEmptyArrayInit(replace: "\(initCall)", with: withFixIt), on: initCall) + let initializer = node.initializer! + let emptyArrayExpr = ArrayExprSyntax(elements: ArrayElementListSyntax.init([])) + + // Replace initializer call with empty array literal: `[]()` -> `[]` + replacement.initializer = initializer.with(\.value, ExprSyntax(emptyArrayExpr)) + + return replacement + } + + private func rewrite(_ node: PatternBindingSyntax, + type: DictionaryTypeSyntax) -> PatternBindingSyntax { + var replacement = node + + diagnose(node, type: type) if replacement.typeAnnotation == nil { // Drop trailing trivia after pattern because ':' has to appear connected to it. replacement.pattern = node.pattern.with(\.trailingTrivia, []) // Add explicit type annotiation: ': []` - replacement.typeAnnotation = .init(type: ArrayTypeSyntax(leadingTrivia: .space, - element: elementType, - trailingTrivia: .space)) + replacement.typeAnnotation = .init(type: type.with(\.leadingTrivia, .space) + .with(\.trailingTrivia, .space)) } - // Replace initializer call with empty array literal: `[]()` -> `[]` - arrayLiteral.elements = ArrayElementListSyntax.init([]) - replacement.initializer = initializer.with(\.value, ExprSyntax(arrayLiteral)) + let initializer = node.initializer! + let emptyDictExpr = DictionaryExprSyntax(content: .colon(.colonToken())) + + // Replace initializer call with empty dictionary literal: `[]()` -> `[]` + replacement.initializer = initializer.with(\.value, ExprSyntax(emptyDictExpr)) return replacement } - private func getElementType(_ arrayLiteral: ArrayExprSyntax) -> TypeSyntax? { - guard let elementExpr = arrayLiteral.elements.firstAndOnly?.as(ArrayElementSyntax.self) else { + private func diagnose(_ node: PatternBindingSyntax, type: ArrayTypeSyntax) { + var withFixIt = "[]" + if node.typeAnnotation == nil { + withFixIt = ": \(type) = []" + } + + let initCall = node.initializer!.value + emitDiagnostic(replace: "\(initCall)", with: withFixIt, on: initCall) + } + + private func diagnose(_ node: PatternBindingSyntax, type: DictionaryTypeSyntax) { + var withFixIt = "[:]" + if node.typeAnnotation == nil { + withFixIt = ": \(type) = [:]" + } + + let initCall = node.initializer!.value + emitDiagnostic(replace: "\(initCall)", with: withFixIt, on: initCall) + } + + private func emitDiagnostic(replace: String, with fixIt: String, on: ExprSyntax?) { + diagnose(.refactorIntoEmptyLiteral(replace: replace, with: fixIt), on: on) + } + + private func getLiteralType(_ arrayLiteral: ArrayExprSyntax) -> ArrayTypeSyntax? { + guard let elementExpr = arrayLiteral.elements.firstAndOnly, + elementExpr.is(ArrayElementSyntax.self) else { return nil } - var parser = Parser(elementExpr.description) + var parser = Parser(arrayLiteral.description) + let elementType = TypeSyntax.parse(from: &parser) + return elementType.hasError ? nil : elementType.as(ArrayTypeSyntax.self) + } + + private func getLiteralType(_ dictLiteral: DictionaryExprSyntax) -> DictionaryTypeSyntax? { + var parser = Parser(dictLiteral.description) let elementType = TypeSyntax.parse(from: &parser) - return elementType.hasError ? nil : elementType + return elementType.hasError ? nil : elementType.as(DictionaryTypeSyntax.self) } } extension Finding.Message { - public static func refactorEmptyArrayInit(replace: String, with: String) -> Finding.Message { + public static func refactorIntoEmptyLiteral(replace: String, with: String) -> Finding.Message { "replace '\(replace)' with '\(with)'" } } diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyArrayInitTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyArrayInitTests.swift index 3503b4805..388cd2054 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyArrayInitTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyArrayInitTests.swift @@ -3,7 +3,7 @@ import _SwiftFormatTestSupport @_spi(Rules) import SwiftFormat final class AlwaysUseLiteralForEmptyArrayInitTests: LintOrFormatRuleTestCase { - func testPatternBindings() { + func testArray() { assertFormatting( AlwaysUseLiteralForEmptyArrayInit.self, input: """ @@ -45,4 +45,47 @@ final class AlwaysUseLiteralForEmptyArrayInitTests: LintOrFormatRuleTestCase { ] ) } + + func testDictionary() { + assertFormatting( + AlwaysUseLiteralForEmptyArrayInit.self, + input: """ + public struct Test { + var value1 = 1️⃣[Int: String]() + + func test(v: [Double: Int] = [Double: Int]()) { + let _ = 2️⃣[String: Int]() + } + } + + var _: [Category: String] = 3️⃣[Category: String]() + let _ = 4️⃣[(Int, Array): Int]() + let _: [String: (String, Int, Float)] = 5️⃣[String: (String, Int, Float)]() + + let _ = [String: (1, 2, String)]() + """, + expected: """ + public struct Test { + var value1: [Int: String] = [:] + + func test(v: [Double: Int] = [Double: Int]()) { + let _: [String: Int] = [:] + } + } + + var _: [Category: String] = [:] + let _: [(Int, Array): Int] = [:] + let _: [String: (String, Int, Float)] = [:] + + let _ = [String: (1, 2, String)]() + """, + findings: [ + FindingSpec("1️⃣", message: "replace '[Int: String]()' with ': [Int: String] = [:]'"), + FindingSpec("2️⃣", message: "replace '[String: Int]()' with ': [String: Int] = [:]'"), + FindingSpec("3️⃣", message: "replace '[Category: String]()' with '[:]'"), + FindingSpec("4️⃣", message: "replace '[(Int, Array): Int]()' with ': [(Int, Array): Int] = [:]'"), + FindingSpec("5️⃣", message: "replace '[String: (String, Int, Float)]()' with '[:]'"), + ] + ) + } } From 7463405ed17ca51522ffc0613f6d1401eb19a5ef Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 8 Sep 2023 13:05:54 -0700 Subject: [PATCH 131/332] [Lint/Format] Rename `AlwaysUseLiteralForEmptyArrayInit` into `AlwaysUseLiteralForEmptyCollectionInit` --- Sources/SwiftFormat/Core/Pipelines+Generated.swift | 4 ++-- Sources/SwiftFormat/Core/RuleNameCache+Generated.swift | 2 +- Sources/SwiftFormat/Core/RuleRegistry+Generated.swift | 2 +- ...t.swift => AlwaysUseLiteralForEmptyCollectionInit.swift} | 2 +- ...ft => AlwaysUseLiteralForEmptyCollectionInitTests.swift} | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) rename Sources/SwiftFormat/Rules/{AlwaysUseLiteralForEmptyArrayInit.swift => AlwaysUseLiteralForEmptyCollectionInit.swift} (98%) rename Tests/SwiftFormatTests/Rules/{AlwaysUseLiteralForEmptyArrayInitTests.swift => AlwaysUseLiteralForEmptyCollectionInitTests.swift} (93%) diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index eabb3bc84..4ce9d8176 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -243,7 +243,7 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: PatternBindingSyntax) -> SyntaxVisitorContinueKind { - visitIfEnabled(AlwaysUseLiteralForEmptyArrayInit.visit, for: node) + visitIfEnabled(AlwaysUseLiteralForEmptyCollectionInit.visit, for: node) visitIfEnabled(OmitExplicitReturns.visit, for: node) visitIfEnabled(UseSingleLinePropertyGetter.visit, for: node) return .visitChildren @@ -357,7 +357,7 @@ extension FormatPipeline { func rewrite(_ node: Syntax) -> Syntax { var node = node - node = AlwaysUseLiteralForEmptyArrayInit(context: context).rewrite(node) + node = AlwaysUseLiteralForEmptyCollectionInit(context: context).rewrite(node) node = DoNotUseSemicolons(context: context).rewrite(node) node = FileScopedDeclarationPrivacy(context: context).rewrite(node) node = FullyIndirectEnum(context: context).rewrite(node) diff --git a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift index d1372da5a..b4eb96438 100644 --- a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift @@ -16,7 +16,7 @@ @_spi(Testing) public let ruleNameCache: [ObjectIdentifier: String] = [ ObjectIdentifier(AllPublicDeclarationsHaveDocumentation.self): "AllPublicDeclarationsHaveDocumentation", - ObjectIdentifier(AlwaysUseLiteralForEmptyArrayInit.self): "AlwaysUseLiteralForEmptyArrayInit", + ObjectIdentifier(AlwaysUseLiteralForEmptyCollectionInit.self): "AlwaysUseLiteralForEmptyCollectionInit", ObjectIdentifier(AlwaysUseLowerCamelCase.self): "AlwaysUseLowerCamelCase", ObjectIdentifier(AmbiguousTrailingClosureOverload.self): "AmbiguousTrailingClosureOverload", ObjectIdentifier(BeginDocumentationCommentWithOneLineSummary.self): "BeginDocumentationCommentWithOneLineSummary", diff --git a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift index 4cc83beec..e3ad66c12 100644 --- a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift @@ -15,7 +15,7 @@ @_spi(Internal) public enum RuleRegistry { public static let rules: [String: Bool] = [ "AllPublicDeclarationsHaveDocumentation": false, - "AlwaysUseLiteralForEmptyArrayInit": false, + "AlwaysUseLiteralForEmptyCollectionInit": false, "AlwaysUseLowerCamelCase": true, "AmbiguousTrailingClosureOverload": true, "BeginDocumentationCommentWithOneLineSummary": false, diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift similarity index 98% rename from Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift rename to Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift index f8cd40c38..ee949536c 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyArrayInit.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift @@ -21,7 +21,7 @@ import SwiftParser /// Lint: Non-literal empty array initialization will yield a lint error. /// Format: All invalid use sites would be related with empty literal (with or without explicit type annotation). @_spi(Rules) -public final class AlwaysUseLiteralForEmptyArrayInit : SyntaxFormatRule { +public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { public override class var isOptIn: Bool { return true } public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax { diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyArrayInitTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift similarity index 93% rename from Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyArrayInitTests.swift rename to Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift index 388cd2054..cc8bb1bcd 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyArrayInitTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift @@ -2,10 +2,10 @@ import _SwiftFormatTestSupport @_spi(Rules) import SwiftFormat -final class AlwaysUseLiteralForEmptyArrayInitTests: LintOrFormatRuleTestCase { +final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCase { func testArray() { assertFormatting( - AlwaysUseLiteralForEmptyArrayInit.self, + AlwaysUseLiteralForEmptyCollectionInit.self, input: """ public struct Test { var value1 = 1️⃣[Int]() @@ -48,7 +48,7 @@ final class AlwaysUseLiteralForEmptyArrayInitTests: LintOrFormatRuleTestCase { func testDictionary() { assertFormatting( - AlwaysUseLiteralForEmptyArrayInit.self, + AlwaysUseLiteralForEmptyCollectionInit.self, input: """ public struct Test { var value1 = 1️⃣[Int: String]() From 2ca2c467cf6ab1bcc3ce59ea3ddaa9f55190f2af Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 8 Sep 2023 15:10:32 -0700 Subject: [PATCH 132/332] [Lint/Format] Extend empty literal rewritting rule to support parameter with default values --- .../Core/Pipelines+Generated.swift | 1 + ...waysUseLiteralForEmptyCollectionInit.swift | 96 ++++++++++++++++--- ...seLiteralForEmptyCollectionInitTests.swift | 66 +++++++++---- 3 files changed, 129 insertions(+), 34 deletions(-) diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 4ce9d8176..01d2b1aaa 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -159,6 +159,7 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AlwaysUseLiteralForEmptyCollectionInit.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift index ee949536c..49b850a50 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift @@ -25,26 +25,58 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { public override class var isOptIn: Bool { return true } public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax { - // Check whether the initializer is `[]()` guard let initializer = node.initializer, - let initCall = initializer.value.as(FunctionCallExprSyntax.self), - initCall.arguments.isEmpty else { + let type = isRewritable(initializer) else { return node } - if let arrayLiteral = initCall.calledExpression.as(ArrayExprSyntax.self), - let type = getLiteralType(arrayLiteral) { + if let type = type.as(ArrayTypeSyntax.self) { return rewrite(node, type: type) } - if let dictLiteral = initCall.calledExpression.as(DictionaryExprSyntax.self), - let type = getLiteralType(dictLiteral) { + if let type = type.as(DictionaryTypeSyntax.self) { return rewrite(node, type: type) } return node } + public override func visit(_ param: FunctionParameterSyntax) -> FunctionParameterSyntax { + guard let initializer = param.defaultValue, + let type = isRewritable(initializer) else { + return param + } + + if let type = type.as(ArrayTypeSyntax.self) { + return rewrite(param, type: type) + } + + if let type = type.as(DictionaryTypeSyntax.self) { + return rewrite(param, type: type) + } + + return param + } + + /// Check whether the initializer is `[]()` and, if so, it could be rewritten to use an empty collection literal. + /// Return a type of the collection. + public func isRewritable(_ initializer: InitializerClauseSyntax) -> TypeSyntax? { + guard let initCall = initializer.value.as(FunctionCallExprSyntax.self), + initCall.arguments.isEmpty else { + return nil + } + + if let arrayLiteral = initCall.calledExpression.as(ArrayExprSyntax.self) { + return getLiteralType(arrayLiteral) + } + + if let dictLiteral = initCall.calledExpression.as(DictionaryExprSyntax.self) { + return getLiteralType(dictLiteral) + } + + return nil + } + private func rewrite(_ node: PatternBindingSyntax, type: ArrayTypeSyntax) -> PatternBindingSyntax { var replacement = node @@ -83,14 +115,32 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { } let initializer = node.initializer! - let emptyDictExpr = DictionaryExprSyntax(content: .colon(.colonToken())) - // Replace initializer call with empty dictionary literal: `[]()` -> `[]` - replacement.initializer = initializer.with(\.value, ExprSyntax(emptyDictExpr)) + replacement.initializer = initializer.with(\.value, ExprSyntax(getEmptyDictionaryLiteral())) return replacement } + private func rewrite(_ param: FunctionParameterSyntax, + type: ArrayTypeSyntax) -> FunctionParameterSyntax { + guard let initializer = param.defaultValue else { + return param + } + + emitDiagnostic(replace: "\(initializer.value)", with: "[]", on: initializer.value) + return param.with(\.defaultValue, initializer.with(\.value, getEmptyArrayLiteral())) + } + + private func rewrite(_ param: FunctionParameterSyntax, + type: DictionaryTypeSyntax) -> FunctionParameterSyntax { + guard let initializer = param.defaultValue else { + return param + } + + emitDiagnostic(replace: "\(initializer.value)", with: "[:]", on: initializer.value) + return param.with(\.defaultValue, initializer.with(\.value, getEmptyDictionaryLiteral())) + } + private func diagnose(_ node: PatternBindingSyntax, type: ArrayTypeSyntax) { var withFixIt = "[]" if node.typeAnnotation == nil { @@ -115,7 +165,7 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { diagnose(.refactorIntoEmptyLiteral(replace: replace, with: fixIt), on: on) } - private func getLiteralType(_ arrayLiteral: ArrayExprSyntax) -> ArrayTypeSyntax? { + private func getLiteralType(_ arrayLiteral: ArrayExprSyntax) -> TypeSyntax? { guard let elementExpr = arrayLiteral.elements.firstAndOnly, elementExpr.is(ArrayElementSyntax.self) else { return nil @@ -123,13 +173,31 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { var parser = Parser(arrayLiteral.description) let elementType = TypeSyntax.parse(from: &parser) - return elementType.hasError ? nil : elementType.as(ArrayTypeSyntax.self) + + guard !elementType.hasError, elementType.is(ArrayTypeSyntax.self) else { + return nil + } + + return elementType } - private func getLiteralType(_ dictLiteral: DictionaryExprSyntax) -> DictionaryTypeSyntax? { + private func getLiteralType(_ dictLiteral: DictionaryExprSyntax) -> TypeSyntax? { var parser = Parser(dictLiteral.description) let elementType = TypeSyntax.parse(from: &parser) - return elementType.hasError ? nil : elementType.as(DictionaryTypeSyntax.self) + + guard !elementType.hasError, elementType.is(DictionaryTypeSyntax.self) else { + return nil + } + + return elementType + } + + private func getEmptyArrayLiteral() -> ExprSyntax { + ExprSyntax(ArrayExprSyntax(elements: ArrayElementListSyntax.init([]))) + } + + private func getEmptyDictionaryLiteral() -> ExprSyntax { + ExprSyntax(DictionaryExprSyntax(content: .colon(.colonToken()))) } } diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift index cc8bb1bcd..598d0320c 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift @@ -10,22 +10,27 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas public struct Test { var value1 = 1️⃣[Int]() - func test(v: [Double] = [Double]()) { - let _ = 2️⃣[String]() + func test(v: [Double] = 2️⃣[Double]()) { + let _ = 3️⃣[String]() } } - var _: [Category] = 3️⃣[Category]() - let _ = 4️⃣[(Int, Array)]() - let _: [(String, Int, Float)] = 5️⃣[(String, Int, Float)]() + var _: [Category] = 4️⃣[Category]() + let _ = 5️⃣[(Int, Array)]() + let _: [(String, Int, Float)] = 6️⃣[(String, Int, Float)]() let _ = [(1, 2, String)]() + + class TestSubscript { + subscript(_: [A] = 7️⃣[A](), x: [(Int, B)] = 8️⃣[(Int, B)]()) { + } + } """, expected: """ public struct Test { var value1: [Int] = [] - func test(v: [Double] = [Double]()) { + func test(v: [Double] = []) { let _: [String] = [] } } @@ -35,13 +40,21 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas let _: [(String, Int, Float)] = [] let _ = [(1, 2, String)]() + + class TestSubscript { + subscript(_: [A] = [], x: [(Int, B)] = []) { + } + } """, findings: [ FindingSpec("1️⃣", message: "replace '[Int]()' with ': [Int] = []'"), - FindingSpec("2️⃣", message: "replace '[String]()' with ': [String] = []'"), - FindingSpec("3️⃣", message: "replace '[Category]()' with '[]'"), - FindingSpec("4️⃣", message: "replace '[(Int, Array)]()' with ': [(Int, Array)] = []'"), - FindingSpec("5️⃣", message: "replace '[(String, Int, Float)]()' with '[]'"), + FindingSpec("2️⃣", message: "replace '[Double]()' with '[]'"), + FindingSpec("3️⃣", message: "replace '[String]()' with ': [String] = []'"), + FindingSpec("4️⃣", message: "replace '[Category]()' with '[]'"), + FindingSpec("5️⃣", message: "replace '[(Int, Array)]()' with ': [(Int, Array)] = []'"), + FindingSpec("6️⃣", message: "replace '[(String, Int, Float)]()' with '[]'"), + FindingSpec("7️⃣", message: "replace '[A]()' with '[]'"), + FindingSpec("8️⃣", message: "replace '[(Int, B)]()' with '[]'"), ] ) } @@ -53,22 +66,27 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas public struct Test { var value1 = 1️⃣[Int: String]() - func test(v: [Double: Int] = [Double: Int]()) { - let _ = 2️⃣[String: Int]() + func test(v: [Double: Int] = 2️⃣[Double: Int]()) { + let _ = 3️⃣[String: Int]() } } - var _: [Category: String] = 3️⃣[Category: String]() - let _ = 4️⃣[(Int, Array): Int]() - let _: [String: (String, Int, Float)] = 5️⃣[String: (String, Int, Float)]() + var _: [Category: String] = 4️⃣[Category: String]() + let _ = 5️⃣[(Int, Array): Int]() + let _: [String: (String, Int, Float)] = 6️⃣[String: (String, Int, Float)]() let _ = [String: (1, 2, String)]() + + class TestSubscript { + subscript(_: [A: Int] = 7️⃣[A: Int](), x: [(Int, B): String] = 8️⃣[(Int, B): String]()) { + } + } """, expected: """ public struct Test { var value1: [Int: String] = [:] - func test(v: [Double: Int] = [Double: Int]()) { + func test(v: [Double: Int] = [:]) { let _: [String: Int] = [:] } } @@ -78,13 +96,21 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas let _: [String: (String, Int, Float)] = [:] let _ = [String: (1, 2, String)]() + + class TestSubscript { + subscript(_: [A: Int] = [:], x: [(Int, B): String] = [:]) { + } + } """, findings: [ FindingSpec("1️⃣", message: "replace '[Int: String]()' with ': [Int: String] = [:]'"), - FindingSpec("2️⃣", message: "replace '[String: Int]()' with ': [String: Int] = [:]'"), - FindingSpec("3️⃣", message: "replace '[Category: String]()' with '[:]'"), - FindingSpec("4️⃣", message: "replace '[(Int, Array): Int]()' with ': [(Int, Array): Int] = [:]'"), - FindingSpec("5️⃣", message: "replace '[String: (String, Int, Float)]()' with '[:]'"), + FindingSpec("2️⃣", message: "replace '[Double: Int]()' with '[:]'"), + FindingSpec("3️⃣", message: "replace '[String: Int]()' with ': [String: Int] = [:]'"), + FindingSpec("4️⃣", message: "replace '[Category: String]()' with '[:]'"), + FindingSpec("5️⃣", message: "replace '[(Int, Array): Int]()' with ': [(Int, Array): Int] = [:]'"), + FindingSpec("6️⃣", message: "replace '[String: (String, Int, Float)]()' with '[:]'"), + FindingSpec("7️⃣", message: "replace '[A: Int]()' with '[:]'"), + FindingSpec("8️⃣", message: "replace '[(Int, B): String]()' with '[:]'"), ] ) } From 9ab323e186369c30054f8255b4f38a2c602665e8 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 14 Sep 2023 15:06:12 +0100 Subject: [PATCH 133/332] [Tests] Add a few tests to make sure that empty collection init rule doesn't affect other cases --- ...seLiteralForEmptyCollectionInitTests.swift | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift index 598d0320c..c6b6267ce 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift @@ -25,6 +25,20 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas subscript(_: [A] = 7️⃣[A](), x: [(Int, B)] = 8️⃣[(Int, B)]()) { } } + + // All of the examples in this block could be re-written to use leading-dot syntax: `.init(...)` + do { + let _ = [Int](repeating: 0, count: 10) + let _: [Int] = [Int](repeating: 0, count: 10) + + func testDefault(_ x: [String] = [String](repeating: "a", count: 42)) { + } + + class TestSubscript { + subscript(_: Int = 42, x: [(Int, B)] = [(Int, B)](repeating: (0, B()), count: 1)) { + } + } + } """, expected: """ public struct Test { @@ -45,6 +59,20 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas subscript(_: [A] = [], x: [(Int, B)] = []) { } } + + // All of the examples in this block could be re-written to use leading-dot syntax: `.init(...)` + do { + let _ = [Int](repeating: 0, count: 10) + let _: [Int] = [Int](repeating: 0, count: 10) + + func testDefault(_ x: [String] = [String](repeating: "a", count: 42)) { + } + + class TestSubscript { + subscript(_: Int = 42, x: [(Int, B)] = [(Int, B)](repeating: (0, B()), count: 1)) { + } + } + } """, findings: [ FindingSpec("1️⃣", message: "replace '[Int]()' with ': [Int] = []'"), @@ -81,6 +109,20 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas subscript(_: [A: Int] = 7️⃣[A: Int](), x: [(Int, B): String] = 8️⃣[(Int, B): String]()) { } } + + // All of the examples in this block could be re-written to use leading-dot syntax: `.init(...)` + do { + let _ = [String: Int](minimumCapacity: 42) + let _: [String: Int] = [String: Int](minimumCapacity: 42) + + func testDefault(_ x: [Int: String] = [String](minimumCapacity: 1)) { + } + + class TestSubscript { + subscript(_: Int = 42, x: [String: (Int, B)] = [String: (Int, B)](minimumCapacity: 2)) { + } + } + } """, expected: """ public struct Test { @@ -101,6 +143,20 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas subscript(_: [A: Int] = [:], x: [(Int, B): String] = [:]) { } } + + // All of the examples in this block could be re-written to use leading-dot syntax: `.init(...)` + do { + let _ = [String: Int](minimumCapacity: 42) + let _: [String: Int] = [String: Int](minimumCapacity: 42) + + func testDefault(_ x: [Int: String] = [String](minimumCapacity: 1)) { + } + + class TestSubscript { + subscript(_: Int = 42, x: [String: (Int, B)] = [String: (Int, B)](minimumCapacity: 2)) { + } + } + } """, findings: [ FindingSpec("1️⃣", message: "replace '[Int: String]()' with ': [Int: String] = [:]'"), From 1ade941ee762084a8941e5c6075f093b4dc23149 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 14 Sep 2023 15:40:01 -0400 Subject: [PATCH 134/332] 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 135/332] 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 136/332] 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 137/332] 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 138/332] 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 139/332] 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, From ea542bc33735d0d2b454f9853d8cb31273191f38 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 14 Sep 2023 14:07:04 -0700 Subject: [PATCH 140/332] Disable testable imports when testing swift-format Enabling testable imports caused us to re-compile swift-syntax and swift-format for testing, which takes quite a while in release mode. This should reduce CI time by ~5 minutes. --- build-script-helper.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build-script-helper.py b/build-script-helper.py index 5b3f83516..747ea1163 100755 --- a/build-script-helper.py +++ b/build-script-helper.py @@ -189,7 +189,10 @@ def invoke_swift_single_product(package_path, swift_exec, action, product, build args = [swift_exec, action] args += get_swiftpm_options(package_path, build_path, multiroot_data_file, configuration, verbose) if action == 'test': - args += ['--test-product', product] + args += [ + '--test-product', product, + '--disable-testable-imports' + ] else: args += ['--product', product] From 39ee8dfafe2f73c97e7354e48b63a6bc9fbe9a44 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 14 Sep 2023 16:01:58 -0700 Subject: [PATCH 141/332] Add option to print number of instructions executed by `swift-format` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’m planning to use this option to measure the performance of `swift-format` periodically on a fixed set of project to detect any performance regressions. --- Package.swift | 5 +++ .../include/InstructionsExecuted.h | 17 +++++++++ .../src/InstructionsExecuted.c | 38 +++++++++++++++++++ Sources/swift-format/Subcommands/Format.swift | 11 ++++-- Sources/swift-format/Subcommands/Lint.swift | 15 +++++--- .../Subcommands/PerformanceMeasurement.swift | 33 ++++++++++++++++ 6 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 Sources/_InstructionCounter/include/InstructionsExecuted.h create mode 100644 Sources/_InstructionCounter/src/InstructionsExecuted.c create mode 100644 Sources/swift-format/Subcommands/PerformanceMeasurement.swift diff --git a/Package.swift b/Package.swift index 124672571..04ee3d89c 100644 --- a/Package.swift +++ b/Package.swift @@ -47,6 +47,10 @@ let package = Package( // See the "Dependencies" section below. ], targets: [ + .target( + name: "_InstructionCounter" + ), + .target( name: "SwiftFormat", dependencies: [ @@ -108,6 +112,7 @@ let package = Package( .executableTarget( name: "swift-format", dependencies: [ + "_InstructionCounter", "SwiftFormat", .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SwiftSyntax", package: "swift-syntax"), diff --git a/Sources/_InstructionCounter/include/InstructionsExecuted.h b/Sources/_InstructionCounter/include/InstructionsExecuted.h new file mode 100644 index 000000000..af9d9a415 --- /dev/null +++ b/Sources/_InstructionCounter/include/InstructionsExecuted.h @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include + +/// On macOS returns the number of instructions the process has executed since +/// it was launched, on all other platforms returns 0. +uint64_t getInstructionsExecuted(); diff --git a/Sources/_InstructionCounter/src/InstructionsExecuted.c b/Sources/_InstructionCounter/src/InstructionsExecuted.c new file mode 100644 index 000000000..4df6fa632 --- /dev/null +++ b/Sources/_InstructionCounter/src/InstructionsExecuted.c @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if __APPLE__ +#include +#if TARGET_OS_MAC && !TARGET_OS_IPHONE +#define TARGET_IS_MACOS 1 +#endif +#endif + +#include "InstructionsExecuted.h" + +#ifdef TARGET_IS_MACOS +#include +#include +#include + +uint64_t getInstructionsExecuted() { + struct rusage_info_v4 ru; + if (proc_pid_rusage(getpid(), RUSAGE_INFO_V4, (rusage_info_t *)&ru) == 0) { + return ru.ri_instructions; + } + return 0; +} +#else +uint64_t getInstructionsExecuted() { + return 0; +} +#endif diff --git a/Sources/swift-format/Subcommands/Format.swift b/Sources/swift-format/Subcommands/Format.swift index 1a7f89d23..7a0378612 100644 --- a/Sources/swift-format/Subcommands/Format.swift +++ b/Sources/swift-format/Subcommands/Format.swift @@ -30,6 +30,9 @@ extension SwiftFormatCommand { @OptionGroup() var formatOptions: LintFormatOptions + @OptionGroup(visibility: .hidden) + var performanceMeasurementOptions: PerformanceMeasurementsOptions + func validate() throws { if inPlace && formatOptions.paths.isEmpty { throw ValidationError("'--in-place' is only valid when formatting files") @@ -37,9 +40,11 @@ extension SwiftFormatCommand { } func run() throws { - let frontend = FormatFrontend(lintFormatOptions: formatOptions, inPlace: inPlace) - frontend.run() - if frontend.diagnosticsEngine.hasErrors { throw ExitCode.failure } + try performanceMeasurementOptions.countingInstructionsIfRequested { + let frontend = FormatFrontend(lintFormatOptions: formatOptions, inPlace: inPlace) + frontend.run() + if frontend.diagnosticsEngine.hasErrors { throw ExitCode.failure } + } } } } diff --git a/Sources/swift-format/Subcommands/Lint.swift b/Sources/swift-format/Subcommands/Lint.swift index 46f28f7c3..985100647 100644 --- a/Sources/swift-format/Subcommands/Lint.swift +++ b/Sources/swift-format/Subcommands/Lint.swift @@ -28,12 +28,17 @@ extension SwiftFormatCommand { ) var strict: Bool = false - func run() throws { - let frontend = LintFrontend(lintFormatOptions: lintOptions) - frontend.run() + @OptionGroup(visibility: .hidden) + var performanceMeasurementOptions: PerformanceMeasurementsOptions - if frontend.diagnosticsEngine.hasErrors || strict && frontend.diagnosticsEngine.hasWarnings { - throw ExitCode.failure + func run() throws { + try performanceMeasurementOptions.countingInstructionsIfRequested { + let frontend = LintFrontend(lintFormatOptions: lintOptions) + frontend.run() + + if frontend.diagnosticsEngine.hasErrors || strict && frontend.diagnosticsEngine.hasWarnings { + throw ExitCode.failure + } } } } diff --git a/Sources/swift-format/Subcommands/PerformanceMeasurement.swift b/Sources/swift-format/Subcommands/PerformanceMeasurement.swift new file mode 100644 index 000000000..388009a93 --- /dev/null +++ b/Sources/swift-format/Subcommands/PerformanceMeasurement.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import ArgumentParser +import _InstructionCounter + +struct PerformanceMeasurementsOptions: ParsableArguments { + @Flag(help: "Measure number of instructions executed by swift-format") + var measureInstructions = false + + /// If `measureInstructions` is set, execute `body` and print the number of instructions + /// executed by it. Otherwise, just execute `body` + func printingInstructionCountIfRequested(_ body: () throws -> T) rethrows -> T { + if !measureInstructions { + return try body() + } else { + let startInstructions = getInstructionsExecuted() + defer { + print("Instructions executed: \(getInstructionsExecuted() - startInstructions)") + } + return try body() + } + } +} From 9718f586bf0091a5cbbd4932b5670d4fde1536c1 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Fri, 15 Sep 2023 11:27:44 -0400 Subject: [PATCH 142/332] Add the `UseExplicitNilCheckInConditions` rule. This rule diagnoses and replaces occurrences of patterns like `if let _ = x` with `if x != nil` (likewise for conditions in `guard` and `while` statements). The rationale for this is that explicitly testing for `nil` is clearer about intent than binding and immediately discarding the wrapped value. --- Documentation/RuleDocumentation.md | 27 ++++++ Package.swift | 1 + .../Core/Pipelines+Generated.swift | 2 + .../Core/RuleNameCache+Generated.swift | 1 + .../Core/RuleRegistry+Generated.swift | 1 + .../UseExplicitNilCheckInConditions.swift | 67 +++++++++++++++ ...UseExplicitNilCheckInConditionsTests.swift | 82 +++++++++++++++++++ 7 files changed, 181 insertions(+) create mode 100644 Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift create mode 100644 Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index f1d68b89f..adedccd21 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -12,6 +12,7 @@ automatically. Here's the list of available rules: - [AllPublicDeclarationsHaveDocumentation](#AllPublicDeclarationsHaveDocumentation) +- [AlwaysUseLiteralForEmptyCollectionInit](#AlwaysUseLiteralForEmptyCollectionInit) - [AlwaysUseLowerCamelCase](#AlwaysUseLowerCamelCase) - [AmbiguousTrailingClosureOverload](#AmbiguousTrailingClosureOverload) - [BeginDocumentationCommentWithOneLineSummary](#BeginDocumentationCommentWithOneLineSummary) @@ -43,6 +44,7 @@ Here's the list of available rules: - [ReturnVoidInsteadOfEmptyTuple](#ReturnVoidInsteadOfEmptyTuple) - [TypeNamesShouldBeCapitalized](#TypeNamesShouldBeCapitalized) - [UseEarlyExits](#UseEarlyExits) +- [UseExplicitNilCheckInConditions](#UseExplicitNilCheckInConditions) - [UseLetInEveryBoundCaseVariable](#UseLetInEveryBoundCaseVariable) - [UseShorthandTypeNames](#UseShorthandTypeNames) - [UseSingleLinePropertyGetter](#UseSingleLinePropertyGetter) @@ -59,6 +61,17 @@ Lint: If a public declaration is missing a documentation comment, a lint error i `AllPublicDeclarationsHaveDocumentation` is a linter-only rule. +### AlwaysUseLiteralForEmptyCollectionInit + +Never use `[]()` syntax. In call sites that should be replaced with `[]`, +for initializations use explicit type combined with empty array literal `let _: [] = []` +Static properties of a type that return that type should not include a reference to their type. + +Lint: Non-literal empty array initialization will yield a lint error. +Format: All invalid use sites would be related with empty literal (with or without explicit type annotation). + +`AlwaysUseLiteralForEmptyCollectionInit` rule can format your code automatically. + ### AlwaysUseLowerCamelCase All values should be written in lower camel-case (`lowerCamelCase`). @@ -446,6 +459,20 @@ Format: `if ... else { return/throw/break/continue }` constructs will be replace `UseEarlyExits` rule can format your code automatically. +### UseExplicitNilCheckInConditions + +When checking an optional value for `nil`-ness, prefer writing an explicit `nil` check rather +than binding and immediately discarding the value. + +For example, `if let _ = someValue { ... }` is forbidden. Use `if someValue != nil { ... }` +instead. + +Lint: `let _ = expr` inside a condition list will yield a lint error. + +Format: `let _ = expr` inside a condition list will be replaced by `expr != nil`. + +`UseExplicitNilCheckInConditions` rule can format your code automatically. + ### UseLetInEveryBoundCaseVariable Every variable bound in a `case` pattern must have its own `let/var`. diff --git a/Package.swift b/Package.swift index 124672571..9fc4bb7f6 100644 --- a/Package.swift +++ b/Package.swift @@ -52,6 +52,7 @@ let package = Package( dependencies: [ .product(name: "Markdown", package: "swift-markdown"), .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "SwiftOperators", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 01d2b1aaa..a7432e538 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -91,6 +91,7 @@ class LintPipeline: SyntaxVisitor { override func visit(_ node: ConditionElementSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) + visitIfEnabled(UseExplicitNilCheckInConditions.visit, for: node) return .visitChildren } @@ -376,6 +377,7 @@ extension FormatPipeline { node = OrderedImports(context: context).rewrite(node) node = ReturnVoidInsteadOfEmptyTuple(context: context).rewrite(node) node = UseEarlyExits(context: context).rewrite(node) + node = UseExplicitNilCheckInConditions(context: context).rewrite(node) node = UseShorthandTypeNames(context: context).rewrite(node) node = UseSingleLinePropertyGetter(context: context).rewrite(node) node = UseTripleSlashForDocumentationComments(context: context).rewrite(node) diff --git a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift index b4eb96438..f487d349c 100644 --- a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift @@ -48,6 +48,7 @@ public let ruleNameCache: [ObjectIdentifier: String] = [ ObjectIdentifier(ReturnVoidInsteadOfEmptyTuple.self): "ReturnVoidInsteadOfEmptyTuple", ObjectIdentifier(TypeNamesShouldBeCapitalized.self): "TypeNamesShouldBeCapitalized", ObjectIdentifier(UseEarlyExits.self): "UseEarlyExits", + ObjectIdentifier(UseExplicitNilCheckInConditions.self): "UseExplicitNilCheckInConditions", ObjectIdentifier(UseLetInEveryBoundCaseVariable.self): "UseLetInEveryBoundCaseVariable", ObjectIdentifier(UseShorthandTypeNames.self): "UseShorthandTypeNames", ObjectIdentifier(UseSingleLinePropertyGetter.self): "UseSingleLinePropertyGetter", diff --git a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift index e3ad66c12..cf91f9d04 100644 --- a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift @@ -47,6 +47,7 @@ "ReturnVoidInsteadOfEmptyTuple": true, "TypeNamesShouldBeCapitalized": true, "UseEarlyExits": false, + "UseExplicitNilCheckInConditions": true, "UseLetInEveryBoundCaseVariable": true, "UseShorthandTypeNames": true, "UseSingleLinePropertyGetter": true, diff --git a/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift new file mode 100644 index 000000000..74612b1b2 --- /dev/null +++ b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax +import SwiftSyntaxBuilder + +/// When checking an optional value for `nil`-ness, prefer writing an explicit `nil` check rather +/// than binding and immediately discarding the value. +/// +/// For example, `if let _ = someValue { ... }` is forbidden. Use `if someValue != nil { ... }` +/// instead. +/// +/// Lint: `let _ = expr` inside a condition list will yield a lint error. +/// +/// Format: `let _ = expr` inside a condition list will be replaced by `expr != nil`. +@_spi(Rules) +public final class UseExplicitNilCheckInConditions: SyntaxFormatRule { + public override func visit(_ node: ConditionElementSyntax) -> ConditionElementSyntax { + switch node.condition { + case .optionalBinding(let optionalBindingCondition): + guard + let initializerClause = optionalBindingCondition.initializer, + isDiscardedAssignmentPattern(optionalBindingCondition.pattern) + else { + return node + } + + diagnose(.useExplicitNilComparison, on: optionalBindingCondition) + + // Since we're moving the initializer value from the RHS to the LHS of an expression/pattern, + // preserve the relative position of the trailing trivia. Similarly, preserve the leading + // trivia of the original node, since that token is being removed entirely. + var value = initializerClause.value + let trailingTrivia = value.trailingTrivia + value.trailingTrivia = [] + + return ConditionElementSyntax( + condition: .expression("\(node.leadingTrivia)\(value) != nil\(trailingTrivia)")) + default: + return node + } + } + + /// Returns true if the given pattern is a discarding assignment expression (for example, the `_` + /// in `let _ = x`). + private func isDiscardedAssignmentPattern(_ pattern: PatternSyntax) -> Bool { + guard let exprPattern = pattern.as(ExpressionPatternSyntax.self) else { + return false + } + return exprPattern.expression.is(DiscardAssignmentExprSyntax.self) + } +} + +extension Finding.Message { + @_spi(Rules) + public static let useExplicitNilComparison: Finding.Message = + "compare this value using `!= nil` instead of binding and discarding it" +} diff --git a/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift b/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift new file mode 100644 index 000000000..4f6337e85 --- /dev/null +++ b/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift @@ -0,0 +1,82 @@ +import _SwiftFormatTestSupport + +@_spi(Rules) import SwiftFormat + +final class UseExplicitNilCheckInConditionsTests: LintOrFormatRuleTestCase { + func testIfExpressions() { + assertFormatting( + UseExplicitNilCheckInConditions.self, + input: """ + if 1️⃣let _ = x {} + if let x = y, 2️⃣let _ = x.m {} + if let x = y {} else if 3️⃣let _ = z {} + """, + expected: """ + if x != nil {} + if let x = y, x.m != nil {} + if let x = y {} else if z != nil {} + """, + findings: [ + FindingSpec("1️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + FindingSpec("2️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + FindingSpec("3️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + ] + ) + } + + func testGuardStatements() { + assertFormatting( + UseExplicitNilCheckInConditions.self, + input: """ + guard 1️⃣let _ = x else {} + guard let x = y, 2️⃣let _ = x.m else {} + """, + expected: """ + guard x != nil else {} + guard let x = y, x.m != nil else {} + """, + findings: [ + FindingSpec("1️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + FindingSpec("2️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + ] + ) + } + + func testWhileStatements() { + assertFormatting( + UseExplicitNilCheckInConditions.self, + input: """ + while 1️⃣let _ = x {} + while let x = y, 2️⃣let _ = x.m {} + """, + expected: """ + while x != nil {} + while let x = y, x.m != nil {} + """, + findings: [ + FindingSpec("1️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + FindingSpec("2️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + ] + ) + } + + func testTriviaPreservation() { + assertFormatting( + UseExplicitNilCheckInConditions.self, + input: """ + if /*comment*/ 1️⃣let _ = x /*comment*/ {} + if 2️⃣let _ = x // comment + {} + """, + expected: """ + if /*comment*/ x != nil /*comment*/ {} + if x != nil // comment + {} + """, + findings: [ + FindingSpec("1️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + FindingSpec("2️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + ] + ) + } +} From 867b650dc8dabfeb1025d936ff73d4ba1f526da8 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 15 Sep 2023 14:46:17 -0700 Subject: [PATCH 143/332] =?UTF-8?q?Don=E2=80=99t=20set=20`SWIFT=5FBUILD=5F?= =?UTF-8?q?SCRIPT=5FENVIRONMENT`=20when=20building=20swift-format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Setting `SWIFT_BUILD_SCRIPT_ENVIRONMENT` entails that we e.g. enable assertions in swift-syntax, even in release mode, which we don’t want since the copy of swift-format that we include in the toolchain is also built using this script and shouldn’t have these swift-syntax’s assertions enabled, to improve performance. --- build-script-helper.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/build-script-helper.py b/build-script-helper.py index 5b3f83516..482958ead 100755 --- a/build-script-helper.py +++ b/build-script-helper.py @@ -176,10 +176,7 @@ def get_swiftpm_options(package_path, build_path, multiroot_data_file, configura return args def get_swiftpm_environment_variables(no_local_deps): - # Tell SwiftSyntax that we are building in a build-script environment so that - # it does not need to be rebuilt if it has already been built before. env = dict(os.environ) - env['SWIFT_BUILD_SCRIPT_ENVIRONMENT'] = '1' if not no_local_deps: env['SWIFTCI_USE_LOCAL_DEPS'] = "1" return env From c7d86f1d9e5b833c517e0e68aa7cfa9bf53a428d Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Mon, 18 Sep 2023 21:00:39 +0200 Subject: [PATCH 144/332] Fix building error --- Sources/swift-format/Subcommands/Format.swift | 2 +- Sources/swift-format/Subcommands/Lint.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/swift-format/Subcommands/Format.swift b/Sources/swift-format/Subcommands/Format.swift index 7a0378612..548e30e18 100644 --- a/Sources/swift-format/Subcommands/Format.swift +++ b/Sources/swift-format/Subcommands/Format.swift @@ -40,7 +40,7 @@ extension SwiftFormatCommand { } func run() throws { - try performanceMeasurementOptions.countingInstructionsIfRequested { + try performanceMeasurementOptions.printingInstructionCountIfRequested() { let frontend = FormatFrontend(lintFormatOptions: formatOptions, inPlace: inPlace) frontend.run() if frontend.diagnosticsEngine.hasErrors { throw ExitCode.failure } diff --git a/Sources/swift-format/Subcommands/Lint.swift b/Sources/swift-format/Subcommands/Lint.swift index 985100647..43b3872a9 100644 --- a/Sources/swift-format/Subcommands/Lint.swift +++ b/Sources/swift-format/Subcommands/Lint.swift @@ -32,7 +32,7 @@ extension SwiftFormatCommand { var performanceMeasurementOptions: PerformanceMeasurementsOptions func run() throws { - try performanceMeasurementOptions.countingInstructionsIfRequested { + try performanceMeasurementOptions.printingInstructionCountIfRequested { let frontend = LintFrontend(lintFormatOptions: lintOptions) frontend.run() From 481e155da9bd429d8ec28f54278295c545da66c9 Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Mon, 18 Sep 2023 16:29:13 -0700 Subject: [PATCH 145/332] Rename `_InstructionCounter` In the unified build there ends up being two of these, one in swift-syntax and one in swift-format. Rename so that the target is unique. --- Package.swift | 4 ++-- .../include/InstructionsExecuted.h | 0 .../src/InstructionsExecuted.c | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename Sources/{_InstructionCounter => _SwiftFormatInstructionCounter}/include/InstructionsExecuted.h (100%) rename Sources/{_InstructionCounter => _SwiftFormatInstructionCounter}/src/InstructionsExecuted.c (100%) diff --git a/Package.swift b/Package.swift index c5a14a0ea..ae4d42be0 100644 --- a/Package.swift +++ b/Package.swift @@ -48,7 +48,7 @@ let package = Package( ], targets: [ .target( - name: "_InstructionCounter" + name: "_SwiftFormatInstructionCounter" ), .target( @@ -113,7 +113,7 @@ let package = Package( .executableTarget( name: "swift-format", dependencies: [ - "_InstructionCounter", + "_SwiftFormatInstructionCounter", "SwiftFormat", .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SwiftSyntax", package: "swift-syntax"), diff --git a/Sources/_InstructionCounter/include/InstructionsExecuted.h b/Sources/_SwiftFormatInstructionCounter/include/InstructionsExecuted.h similarity index 100% rename from Sources/_InstructionCounter/include/InstructionsExecuted.h rename to Sources/_SwiftFormatInstructionCounter/include/InstructionsExecuted.h diff --git a/Sources/_InstructionCounter/src/InstructionsExecuted.c b/Sources/_SwiftFormatInstructionCounter/src/InstructionsExecuted.c similarity index 100% rename from Sources/_InstructionCounter/src/InstructionsExecuted.c rename to Sources/_SwiftFormatInstructionCounter/src/InstructionsExecuted.c From 8edb6420318781226d9560b49bb03171dd1e72d0 Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Mon, 18 Sep 2023 17:09:45 -0700 Subject: [PATCH 146/332] Fix up import of renamed `_InstructionCount` --- Sources/swift-format/Subcommands/PerformanceMeasurement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/swift-format/Subcommands/PerformanceMeasurement.swift b/Sources/swift-format/Subcommands/PerformanceMeasurement.swift index 388009a93..a230aa052 100644 --- a/Sources/swift-format/Subcommands/PerformanceMeasurement.swift +++ b/Sources/swift-format/Subcommands/PerformanceMeasurement.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import ArgumentParser -import _InstructionCounter +import _SwiftFormatInstructionCounter struct PerformanceMeasurementsOptions: ParsableArguments { @Flag(help: "Measure number of instructions executed by swift-format") From 85b6ba62ef150caacf7bcef7579964e7b8cc2a7d Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 19 Sep 2023 20:00:56 -0400 Subject: [PATCH 147/332] Allow JSON configuration text to be passed directly on the command line. If the `--configuration` flag is provided and it's not a valid file path, then swift-format will now attempt to parse the value directly as the JSON configuration. For example, `--configuration '{"lineLength": 20}`. This is primarily useful for quick testing. --- Sources/SwiftFormat/API/Configuration.swift | 7 +++- Sources/swift-format/Frontend/Frontend.swift | 41 ++++++++++++------- .../Subcommands/LintFormatOptions.swift | 7 +++- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 6ef959695..17ef00eaa 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -186,9 +186,14 @@ public struct Configuration: Codable, Equatable { /// ``` public var multiElementCollectionTrailingCommas: Bool - /// Constructs a Configuration by loading it from a configuration file. + /// Creates a new `Configuration` by loading it from a configuration file. public init(contentsOf url: URL) throws { let data = try Data(contentsOf: url) + try self.init(data: data) + } + + /// Creates a new `Configuration` by decoding it from the UTF-8 representation in the given data. + public init(data: Data) throws { self = try JSONDecoder().decode(Configuration.self, from: data) } diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index 28391a192..86930ee0b 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -109,7 +109,7 @@ class Frontend { /// Processes source content from standard input. private func processStandardInput() { guard let configuration = configuration( - at: lintFormatOptions.configurationPath.map(URL.init(fileURLWithPath:)), + fromPathOrString: lintFormatOptions.configuration, orInferredFromSwiftFileAt: nil) else { // Already diagnosed in the called method. @@ -150,7 +150,7 @@ class Frontend { guard let configuration = configuration( - at: lintFormatOptions.configurationPath.map(URL.init(fileURLWithPath:)), + fromPathOrString: lintFormatOptions.configuration, orInferredFromSwiftFileAt: url) else { // Already diagnosed in the called method. @@ -161,31 +161,44 @@ class Frontend { } /// Returns the configuration that applies to the given `.swift` source file, when an explicit - /// configuration path is also perhaps provided. Checks for unrecognized rules within the configuration. + /// configuration path is also perhaps provided. + /// + /// This method also checks for unrecognized rules within the configuration. /// /// - Parameters: - /// - configurationFilePath: The path to a configuration file that will be loaded, or `nil` to - /// try to infer it from `swiftFilePath`. + /// - pathOrString: A string containing either the path to a configuration file that will be + /// loaded, JSON configuration data directly, or `nil` to try to infer it from + /// `swiftFilePath`. /// - swiftFilePath: The path to a `.swift` file, which will be used to infer the path to the /// configuration file if `configurationFilePath` is nil. /// - /// - Returns: If successful, the returned configuration is the one loaded from - /// `configurationFilePath` if it was provided, or by searching in paths inferred by - /// `swiftFilePath` if one exists, or the default configuration otherwise. If an error occurred - /// when reading the configuration, a diagnostic is emitted and `nil` is returned. - /// if neither `configurationFilePath` nor `swiftFilePath` were provided, a default `Configuration()` will be returned. + /// - Returns: If successful, the returned configuration is the one loaded from `pathOrString` if + /// it was provided, or by searching in paths inferred by `swiftFilePath` if one exists, or the + /// default configuration otherwise. If an error occurred when reading the configuration, a + /// diagnostic is emitted and `nil` is returned. If neither `pathOrString` nor `swiftFilePath` + /// were provided, a default `Configuration()` will be returned. private func configuration( - at configurationFileURL: URL?, + fromPathOrString pathOrString: String?, orInferredFromSwiftFileAt swiftFileURL: URL? ) -> Configuration? { - // If an explicit configuration file path was given, try to load it and fail if it cannot be - // loaded. (Do not try to fall back to a path inferred from the source file path.) - if let configurationFileURL = configurationFileURL { + if let pathOrString = pathOrString { + // If an explicit configuration file path was given, try to load it and fail if it cannot be + // loaded. (Do not try to fall back to a path inferred from the source file path.) + let configurationFileURL = URL(fileURLWithPath: pathOrString) do { let configuration = try configurationLoader.configuration(at: configurationFileURL) self.checkForUnrecognizedRules(in: configuration) return configuration } catch { + // If we failed to load this from the path, try interpreting the string as configuration + // data itself because the user might have written something like `--configuration '{...}'`, + let data = pathOrString.data(using: .utf8)! + if let configuration = try? Configuration(data: data) { + return configuration + } + + // Fail if the configuration flag was neither a valid file path nor valid configuration + // data. diagnosticsEngine.emitError("Unable to read configuration: \(error.localizedDescription)") return nil } diff --git a/Sources/swift-format/Subcommands/LintFormatOptions.swift b/Sources/swift-format/Subcommands/LintFormatOptions.swift index c0569b405..170ebaad2 100644 --- a/Sources/swift-format/Subcommands/LintFormatOptions.swift +++ b/Sources/swift-format/Subcommands/LintFormatOptions.swift @@ -20,8 +20,11 @@ struct LintFormatOptions: ParsableArguments { /// If not specified, the default configuration will be used. @Option( name: .customLong("configuration"), - help: "The path to a JSON file containing the configuration of the linter/formatter.") - var configurationPath: String? + help: """ + The path to a JSON file containing the configuration of the linter/formatter or a JSON \ + string containing the configuration directly. + """) + var configuration: String? /// The filename for the source code when reading from standard input, to include in diagnostic /// messages. From 0d0cf84b40c908daaddcc02f87b3f37d8117c813 Mon Sep 17 00:00:00 2001 From: K Date: Wed, 20 Sep 2023 12:15:54 +0200 Subject: [PATCH 148/332] Fix RuleDocumentation link in Configuration.md --- Documentation/Configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 5e30ffcff..1035286ce 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -110,7 +110,7 @@ An example `.swift-format` configuration file is shown below. In the `rules` block of `.swift-format`, you can specify which rules to apply when linting and formatting your project. Read the -[rules documentation](Documentation/RuleDocumentation.md) to see the list of all +[rules documentation](RuleDocumentation.md) to see the list of all supported linter and formatter rules, and their overview. You can also run this command to see the list of rules in the default From f9538d41225bc4e5b3ee1d8cbb67582f6c6b22a9 Mon Sep 17 00:00:00 2001 From: K Date: Wed, 20 Sep 2023 12:34:23 +0200 Subject: [PATCH 149/332] Update RuleDocumentation.md --- Documentation/RuleDocumentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index adedccd21..eb761845b 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -5,7 +5,7 @@ Use the rules below in the `rules` block of your `.swift-format` configuration file, as described in -[Configuration](Documentation/Configuration.md). All of these rules can be +[Configuration](Configuration.md). All of these rules can be applied in the linter, but only some of them can format your source code automatically. From ac99928bc76944881ec4c03f075d7c559b203539 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 21 Sep 2023 18:04:36 -0400 Subject: [PATCH 150/332] Make all `Finding.Message` extensions file-private. Now that our tests verify the actual text of the diagnostics, we don't need these helpers to be visible outside their file. This gets rid of some noise when you're writing `diagnose` calls in rules because you'll only see the ones that are relevant. --- .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 6 +++--- .../PrettyPrint/WhitespaceLinter.swift | 14 ++++++------- ...lPublicDeclarationsHaveDocumentation.swift | 3 +-- ...waysUseLiteralForEmptyCollectionInit.swift | 4 +++- .../Rules/AlwaysUseLowerCamelCase.swift | 3 +-- .../AmbiguousTrailingClosureOverload.swift | 6 ++---- ...cumentationCommentWithOneLineSummary.swift | 6 ++---- .../Rules/DoNotUseSemicolons.swift | 6 ++---- .../DontRepeatTypeInStaticProperties.swift | 3 +-- .../Rules/FileScopedDeclarationPrivacy.swift | 6 ++---- .../SwiftFormat/Rules/FullyIndirectEnum.swift | 5 ++--- .../Rules/GroupNumericLiterals.swift | 3 +-- .../Rules/IdentifiersMustBeASCII.swift | 3 +-- .../SwiftFormat/Rules/NeverForceUnwrap.swift | 6 ++---- .../SwiftFormat/Rules/NeverUseForceTry.swift | 3 +-- ...NeverUseImplicitlyUnwrappedOptionals.swift | 3 +-- .../NoAccessLevelOnExtensionDeclaration.swift | 12 ++++------- .../Rules/NoAssignmentInExpressions.swift | 3 +-- .../SwiftFormat/Rules/NoBlockComments.swift | 3 +-- .../Rules/NoCasesWithOnlyFallthrough.swift | 3 +-- .../NoEmptyTrailingClosureParentheses.swift | 3 +-- .../Rules/NoLabelsInCasePatterns.swift | 3 +-- .../Rules/NoLeadingUnderscores.swift | 3 +-- .../Rules/NoParensAroundConditions.swift | 3 +-- .../NoVoidReturnOnFunctionSignature.swift | 3 +-- .../Rules/OmitExplicitReturns.swift | 2 +- .../SwiftFormat/Rules/OneCasePerLine.swift | 3 +-- .../Rules/OneVariableDeclarationPerLine.swift | 3 +-- .../OnlyOneTrailingClosureArgument.swift | 3 +-- .../SwiftFormat/Rules/OrderedImports.swift | 12 ++++------- .../Rules/ReplaceForEachWithForLoop.swift | 2 +- .../Rules/ReturnVoidInsteadOfEmptyTuple.swift | 3 +-- .../Rules/TypeNamesShouldBeCapitalized.swift | 3 +-- Sources/SwiftFormat/Rules/UseEarlyExits.swift | 3 +-- .../UseExplicitNilCheckInConditions.swift | 3 +-- .../UseLetInEveryBoundCaseVariable.swift | 3 +-- .../Rules/UseShorthandTypeNames.swift | 3 +-- .../Rules/UseSingleLinePropertyGetter.swift | 3 +-- .../Rules/UseSynthesizedInitializer.swift | 3 +-- ...eTripleSlashForDocumentationComments.swift | 3 +-- .../Rules/UseWhereClausesInForLoops.swift | 6 ++---- .../Rules/ValidateDocumentationComments.swift | 21 +++++++------------ 42 files changed, 71 insertions(+), 124 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 8447ff8d2..5f4ff24c8 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -790,12 +790,12 @@ public class PrettyPrinter { } extension Finding.Message { - public static let moveEndOfLineComment: Finding.Message = + fileprivate static let moveEndOfLineComment: Finding.Message = "move end-of-line comment that exceeds the line length" - public static let addTrailingComma: Finding.Message = + fileprivate static let addTrailingComma: Finding.Message = "add trailing comma to the last element in multiline collection literal" - public static let removeTrailingComma: Finding.Message = + fileprivate static let removeTrailingComma: Finding.Message = "remove trailing comma from the last element in single line collection literal" } diff --git a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift index 088e7ac38..c88c88c5b 100644 --- a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift +++ b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift @@ -432,9 +432,9 @@ extension WhitespaceIndentation { } extension Finding.Message { - public static let trailingWhitespaceError: Finding.Message = "remove trailing whitespace" + fileprivate static let trailingWhitespaceError: Finding.Message = "remove trailing whitespace" - public static func indentationError( + fileprivate static func indentationError( expected expectedIndentation: WhitespaceIndentation, actual actualIndentation: WhitespaceIndentation ) -> Finding.Message { @@ -470,20 +470,20 @@ extension Finding.Message { } } - public static func spacingError(_ spaces: Int) -> Finding.Message { + fileprivate static func spacingError(_ spaces: Int) -> Finding.Message { let verb = spaces > 0 ? "add" : "remove" let noun = abs(spaces) == 1 ? "space" : "spaces" return "\(verb) \(abs(spaces)) \(noun)" } - public static let spacingCharError: Finding.Message = "use spaces for spacing" + fileprivate static let spacingCharError: Finding.Message = "use spaces for spacing" - public static let removeLineError: Finding.Message = "remove line break" + fileprivate static let removeLineError: Finding.Message = "remove line break" - public static func addLinesError(_ lines: Int) -> Finding.Message { + fileprivate static func addLinesError(_ lines: Int) -> Finding.Message { let noun = lines == 1 ? "break" : "breaks" return "add \(lines) line \(noun)" } - public static let lineLengthError: Finding.Message = "line is too long" + fileprivate static let lineLengthError: Finding.Message = "line is too long" } diff --git a/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift index d3989ae59..2ca2235b5 100644 --- a/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift +++ b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift @@ -87,8 +87,7 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static func declRequiresComment(_ name: String) -> Finding.Message { + fileprivate static func declRequiresComment(_ name: String) -> Finding.Message { "add a documentation comment for '\(name)'" } } diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift index 28c52d64a..b73e0ba49 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift @@ -201,7 +201,9 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { } extension Finding.Message { - public static func refactorIntoEmptyLiteral(replace: String, with: String) -> Finding.Message { + fileprivate static func refactorIntoEmptyLiteral(replace: String, with: String) + -> Finding.Message + { "replace '\(replace)' with '\(with)'" } } diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift index 918c54f75..aad4fdb44 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift @@ -214,8 +214,7 @@ extension ReturnClauseSyntax { } extension Finding.Message { - @_spi(Rules) - public static func nameMustBeLowerCamelCase( + fileprivate static func nameMustBeLowerCamelCase( _ name: String, description: String ) -> Finding.Message { "rename the \(description) '\(name)' using lowerCamelCase" diff --git a/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift b/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift index 6ab780d26..ecd2ff257 100644 --- a/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift +++ b/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift @@ -73,13 +73,11 @@ public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static func ambiguousTrailingClosureOverload(_ decl: String) -> Finding.Message { + fileprivate static func ambiguousTrailingClosureOverload(_ decl: String) -> Finding.Message { "rename '\(decl)' so it is no longer ambiguous when called with a trailing closure" } - @_spi(Rules) - public static func otherAmbiguousOverloadHere(_ decl: String) -> Finding.Message { + fileprivate static func otherAmbiguousOverloadHere(_ decl: String) -> Finding.Message { "ambiguous overload '\(decl)' is here" } } diff --git a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift index 4a7e3acf5..7ab8096ac 100644 --- a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift +++ b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift @@ -195,15 +195,13 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static func terminateSentenceWithPeriod(_ text: Sentence) + fileprivate static func terminateSentenceWithPeriod(_ text: Sentence) -> Finding.Message { "terminate this sentence with a period: \"\(text)\"" } - @_spi(Rules) - public static func addBlankLineAfterFirstSentence(_ text: Sentence) + fileprivate static func addBlankLineAfterFirstSentence(_ text: Sentence) -> Finding.Message { "add a blank comment line after this sentence: \"\(text)\"" diff --git a/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift index 85d7cb4fb..2948b9e36 100644 --- a/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift @@ -111,10 +111,8 @@ public final class DoNotUseSemicolons: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static let removeSemicolon: Finding.Message = "remove ';'" + fileprivate static let removeSemicolon: Finding.Message = "remove ';'" - @_spi(Rules) - public static let removeSemicolonAndMove: Finding.Message = + fileprivate static let removeSemicolonAndMove: Finding.Message = "remove ';' and move the next statement to a new line" } diff --git a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift index c66d1786a..0529b35a2 100644 --- a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift @@ -106,8 +106,7 @@ public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static func removeTypeFromName(name: String, type: Substring) -> Finding.Message { + fileprivate static func removeTypeFromName(name: String, type: Substring) -> Finding.Message { "remove the suffix '\(type)' from the name of the variable '\(name)'" } } diff --git a/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift b/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift index 30d43f788..bc1a8f2f3 100644 --- a/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift +++ b/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift @@ -160,11 +160,9 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static let replacePrivateWithFileprivate: Finding.Message = + fileprivate static let replacePrivateWithFileprivate: Finding.Message = "replace 'private' with 'fileprivate' on file-scoped declarations" - @_spi(Rules) - public static let replaceFileprivateWithPrivate: Finding.Message = + fileprivate static let replaceFileprivateWithPrivate: Finding.Message = "replace 'fileprivate' with 'private' on file-scoped declarations" } diff --git a/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift index 5c5af4173..98466fe2f 100644 --- a/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift @@ -123,10 +123,9 @@ public final class FullyIndirectEnum: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static func moveIndirectKeywordToEnumDecl(name: String) -> Finding.Message { + fileprivate static func moveIndirectKeywordToEnumDecl(name: String) -> Finding.Message { "declare enum '\(name)' itself as indirect when all cases are indirect" } - public static let removeIndirect: Finding.Message = "remove 'indirect' here" + fileprivate static let removeIndirect: Finding.Message = "remove 'indirect' here" } diff --git a/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift b/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift index af5428cf2..b29089efb 100644 --- a/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift +++ b/Sources/SwiftFormat/Rules/GroupNumericLiterals.swift @@ -80,8 +80,7 @@ public final class GroupNumericLiterals: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static func groupNumericLiteral(every stride: Int, base: String) -> Finding.Message { + fileprivate static func groupNumericLiteral(every stride: Int, base: String) -> Finding.Message { return "group every \(stride) digits in this \(base) literal using a '_' separator" } } diff --git a/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift b/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift index fb2222617..2da9d1290 100644 --- a/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift +++ b/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift @@ -31,8 +31,7 @@ public final class IdentifiersMustBeASCII: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static func nonASCIICharsNotAllowed( + fileprivate static func nonASCIICharsNotAllowed( _ invalidCharacters: [String], _ identifierName: String ) -> Finding.Message { """ diff --git a/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift index 5c7c40912..29937b987 100644 --- a/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift +++ b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift @@ -50,13 +50,11 @@ public final class NeverForceUnwrap: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static func doNotForceUnwrap(name: String) -> Finding.Message { + fileprivate static func doNotForceUnwrap(name: String) -> Finding.Message { "do not force unwrap '\(name)'" } - @_spi(Rules) - public static func doNotForceCast(name: String) -> Finding.Message { + fileprivate static func doNotForceCast(name: String) -> Finding.Message { "do not force cast to '\(name)'" } } diff --git a/Sources/SwiftFormat/Rules/NeverUseForceTry.swift b/Sources/SwiftFormat/Rules/NeverUseForceTry.swift index db15979c6..68d81f652 100644 --- a/Sources/SwiftFormat/Rules/NeverUseForceTry.swift +++ b/Sources/SwiftFormat/Rules/NeverUseForceTry.swift @@ -44,6 +44,5 @@ public final class NeverUseForceTry: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static let doNotForceTry: Finding.Message = "do not use force try" + fileprivate static let doNotForceTry: Finding.Message = "do not use force try" } diff --git a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift index fe9ad048d..65635cc74 100644 --- a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -61,8 +61,7 @@ public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static func doNotUseImplicitUnwrapping(identifier: String) -> Finding.Message { + fileprivate static func doNotUseImplicitUnwrapping(identifier: String) -> Finding.Message { "use '\(identifier)' or '\(identifier)?' instead of '\(identifier)!'" } } diff --git a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift index e16e82cf6..aa3447c41 100644 --- a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift @@ -105,22 +105,18 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static let removeRedundantAccessKeyword: Finding.Message = + fileprivate static let removeRedundantAccessKeyword: Finding.Message = "remove this redundant 'internal' access modifier from this extension" - @_spi(Rules) - public static func moveAccessKeyword(keyword: String) -> Finding.Message { + fileprivate static func moveAccessKeyword(keyword: String) -> Finding.Message { "move this '\(keyword)' access modifier to precede each member inside this extension" } - @_spi(Rules) - public static func moveAccessKeywordAndMakeFileprivate(keyword: String) -> Finding.Message { + fileprivate static func moveAccessKeywordAndMakeFileprivate(keyword: String) -> Finding.Message { "remove this '\(keyword)' access modifier and declare each member inside this extension as 'fileprivate'" } - @_spi(Rules) - public static func addModifierToExtensionMember(keyword: String) -> Finding.Message { + fileprivate static func addModifierToExtensionMember(keyword: String) -> Finding.Message { "add '\(keyword)' access modifier to this declaration" } } diff --git a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift index b5996f831..0112a3db8 100644 --- a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift @@ -157,7 +157,6 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static let moveAssignmentToOwnStatement: Finding.Message = + fileprivate static let moveAssignmentToOwnStatement: Finding.Message = "move this assignment expression into its own statement" } diff --git a/Sources/SwiftFormat/Rules/NoBlockComments.swift b/Sources/SwiftFormat/Rules/NoBlockComments.swift index f4f00a4ce..0df718966 100644 --- a/Sources/SwiftFormat/Rules/NoBlockComments.swift +++ b/Sources/SwiftFormat/Rules/NoBlockComments.swift @@ -29,7 +29,6 @@ public final class NoBlockComments: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static let avoidBlockComment: Finding.Message = + fileprivate static let avoidBlockComment: Finding.Message = "replace this block comment with line comments" } diff --git a/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift index a7a48d21f..2fd41572a 100644 --- a/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift +++ b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift @@ -224,8 +224,7 @@ extension TriviaPiece { } extension Finding.Message { - @_spi(Rules) - public static var collapseCase: Finding.Message { + fileprivate static var collapseCase: Finding.Message { "combine this fallthrough-only 'case' and the following 'case' into a single 'case'" } } diff --git a/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift b/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift index e79c38701..0601ae98f 100644 --- a/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift +++ b/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift @@ -54,8 +54,7 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static func removeEmptyTrailingParentheses(name: String) -> Finding.Message { + fileprivate static func removeEmptyTrailingParentheses(name: String) -> Finding.Message { "remove the empty parentheses following '\(name)'" } } diff --git a/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift b/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift index d0cd85d21..aba7cdf1f 100644 --- a/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift +++ b/Sources/SwiftFormat/Rules/NoLabelsInCasePatterns.swift @@ -75,8 +75,7 @@ public final class NoLabelsInCasePatterns: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static func removeRedundantLabel(name: String) -> Finding.Message { + fileprivate static func removeRedundantLabel(name: String) -> Finding.Message { "remove the label '\(name)' from this 'case' pattern" } } diff --git a/Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift b/Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift index e194d3faa..bc3a7fff5 100644 --- a/Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift +++ b/Sources/SwiftFormat/Rules/NoLeadingUnderscores.swift @@ -125,8 +125,7 @@ public final class NoLeadingUnderscores: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static func doNotStartWithUnderscore(identifier: String) -> Finding.Message { + fileprivate static func doNotStartWithUnderscore(identifier: String) -> Finding.Message { "remove the leading '_' from the name '\(identifier)'" } } diff --git a/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift index 08b1d4b4f..51f8377cd 100644 --- a/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift @@ -121,7 +121,6 @@ public final class NoParensAroundConditions: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static let removeParensAroundExpression: Finding.Message = + fileprivate static let removeParensAroundExpression: Finding.Message = "remove the parentheses around this expression" } diff --git a/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift b/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift index 103dfaab6..47b948e26 100644 --- a/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift +++ b/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift @@ -52,8 +52,7 @@ public final class NoVoidReturnOnFunctionSignature: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static func removeRedundantReturn(_ type: String) -> Finding.Message { + fileprivate static func removeRedundantReturn(_ type: String) -> Finding.Message { "remove the explicit return type '\(type)' from this function" } } diff --git a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift index 21f9833f6..1456a58c4 100644 --- a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift +++ b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift @@ -144,6 +144,6 @@ public final class OmitExplicitReturns: SyntaxFormatRule { } extension Finding.Message { - public static let omitReturnStatement: Finding.Message = + fileprivate static let omitReturnStatement: Finding.Message = "'return' can be omitted because body consists of a single expression" } diff --git a/Sources/SwiftFormat/Rules/OneCasePerLine.swift b/Sources/SwiftFormat/Rules/OneCasePerLine.swift index 4b4778c34..08f50e703 100644 --- a/Sources/SwiftFormat/Rules/OneCasePerLine.swift +++ b/Sources/SwiftFormat/Rules/OneCasePerLine.swift @@ -137,8 +137,7 @@ public final class OneCasePerLine: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static func moveAssociatedOrRawValueCase(name: String) -> Finding.Message { + fileprivate static func moveAssociatedOrRawValueCase(name: String) -> Finding.Message { "move '\(name)' to its own 'case' declaration" } } diff --git a/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift index 825ff232a..da9a2493d 100644 --- a/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift +++ b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift @@ -74,8 +74,7 @@ public final class OneVariableDeclarationPerLine: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static func onlyOneVariableDeclaration(specifier: String) -> Finding.Message { + fileprivate static func onlyOneVariableDeclaration(specifier: String) -> Finding.Message { "split this variable declaration to introduce only one variable per '\(specifier)'" } } diff --git a/Sources/SwiftFormat/Rules/OnlyOneTrailingClosureArgument.swift b/Sources/SwiftFormat/Rules/OnlyOneTrailingClosureArgument.swift index 551ea81d6..ea62a06ff 100644 --- a/Sources/SwiftFormat/Rules/OnlyOneTrailingClosureArgument.swift +++ b/Sources/SwiftFormat/Rules/OnlyOneTrailingClosureArgument.swift @@ -30,7 +30,6 @@ public final class OnlyOneTrailingClosureArgument: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static let removeTrailingClosure: Finding.Message = + fileprivate static let removeTrailingClosure: Finding.Message = "revise this function call to avoid using both closure arguments and a trailing closure" } diff --git a/Sources/SwiftFormat/Rules/OrderedImports.swift b/Sources/SwiftFormat/Rules/OrderedImports.swift index 969c18236..67831ecec 100644 --- a/Sources/SwiftFormat/Rules/OrderedImports.swift +++ b/Sources/SwiftFormat/Rules/OrderedImports.swift @@ -569,17 +569,13 @@ extension Line: CustomDebugStringConvertible { } extension Finding.Message { - @_spi(Rules) - public static let placeAtTopOfFile: Finding.Message = "place imports at the top of the file" + fileprivate static let placeAtTopOfFile: Finding.Message = "place imports at the top of the file" - @_spi(Rules) - public static func groupImports(before: LineType, after: LineType) -> Finding.Message { + fileprivate static func groupImports(before: LineType, after: LineType) -> Finding.Message { "place \(before) imports before \(after) imports" } - @_spi(Rules) - public static let removeDuplicateImport: Finding.Message = "remove this duplicate import" + fileprivate static let removeDuplicateImport: Finding.Message = "remove this duplicate import" - @_spi(Rules) - public static let sortImports: Finding.Message = "sort import statements lexicographically" + fileprivate static let sortImports: Finding.Message = "sort import statements lexicographically" } diff --git a/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift b/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift index 01de282c0..120b69967 100644 --- a/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift +++ b/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift @@ -47,7 +47,7 @@ public final class ReplaceForEachWithForLoop : SyntaxLintRule { } extension Finding.Message { - public static func replaceForEachWithLoop() -> Finding.Message { + fileprivate static func replaceForEachWithLoop() -> Finding.Message { "replace use of '.forEach { ... }' with for-in loop" } } diff --git a/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift index 10b7ef319..e98d67cdd 100644 --- a/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift @@ -119,6 +119,5 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static let returnVoid: Finding.Message = "replace '()' with 'Void'" + fileprivate static let returnVoid: Finding.Message = "replace '()' with 'Void'" } diff --git a/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift b/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift index bc5a6ba86..3fdb53828 100644 --- a/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift +++ b/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift @@ -66,8 +66,7 @@ public final class TypeNamesShouldBeCapitalized : SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static func capitalizeTypeName(name: String, kind: String) -> Finding.Message { + fileprivate static func capitalizeTypeName(name: String, kind: String) -> Finding.Message { var capitalized = name let leadingUnderscores = capitalized.prefix { $0 == "_" } let charAt = leadingUnderscores.endIndex diff --git a/Sources/SwiftFormat/Rules/UseEarlyExits.swift b/Sources/SwiftFormat/Rules/UseEarlyExits.swift index da2b3b61c..15f935b35 100644 --- a/Sources/SwiftFormat/Rules/UseEarlyExits.swift +++ b/Sources/SwiftFormat/Rules/UseEarlyExits.swift @@ -107,7 +107,6 @@ public final class UseEarlyExits: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static let useGuardStatement: Finding.Message = + fileprivate static let useGuardStatement: Finding.Message = "replace this 'if/else' block with a 'guard' statement containing the early exit" } diff --git a/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift index 74612b1b2..030c41e74 100644 --- a/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift +++ b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift @@ -61,7 +61,6 @@ public final class UseExplicitNilCheckInConditions: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static let useExplicitNilComparison: Finding.Message = + fileprivate static let useExplicitNilComparison: Finding.Message = "compare this value using `!= nil` instead of binding and discarding it" } diff --git a/Sources/SwiftFormat/Rules/UseLetInEveryBoundCaseVariable.swift b/Sources/SwiftFormat/Rules/UseLetInEveryBoundCaseVariable.swift index 442768cd0..840a8496f 100644 --- a/Sources/SwiftFormat/Rules/UseLetInEveryBoundCaseVariable.swift +++ b/Sources/SwiftFormat/Rules/UseLetInEveryBoundCaseVariable.swift @@ -66,7 +66,6 @@ public final class UseLetInEveryBoundCaseVariable: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static let useLetInBoundCaseVariables: Finding.Message = + fileprivate static let useLetInBoundCaseVariables: Finding.Message = "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" } diff --git a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift index a66aafb17..32df4662d 100644 --- a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift @@ -580,8 +580,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static func useTypeShorthand(type: String) -> Finding.Message { + fileprivate static func useTypeShorthand(type: String) -> Finding.Message { "use shorthand syntax for this '\(type)' type" } } diff --git a/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift index bdae9ffa6..abc722570 100644 --- a/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift @@ -41,7 +41,6 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static let removeExtraneousGetBlock: Finding.Message = + fileprivate static let removeExtraneousGetBlock: Finding.Message = "remove 'get {...}' around the accessor and move its body directly into the computed property" } diff --git a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift index 52a2577ec..e3d4e2444 100644 --- a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift @@ -185,8 +185,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { } extension Finding.Message { - @_spi(Rules) - public static let removeRedundantInitializer: Finding.Message = + fileprivate static let removeRedundantInitializer: Finding.Message = "remove this explicit initializer, which is identical to the compiler-synthesized initializer" } diff --git a/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift b/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift index e1f2e3eeb..5d64b10ed 100644 --- a/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift +++ b/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift @@ -109,7 +109,6 @@ public final class UseTripleSlashForDocumentationComments: SyntaxFormatRule { } extension Finding.Message { - @_spi(Rules) - public static let avoidDocBlockComment: Finding.Message = + fileprivate static let avoidDocBlockComment: Finding.Message = "replace documentation block comments with documentation line comments" } diff --git a/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift index 080aa1e2f..b8224b51c 100644 --- a/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift @@ -122,11 +122,9 @@ fileprivate func updateWithWhereCondition( } extension Finding.Message { - @_spi(Rules) - public static let useWhereInsteadOfIf: Finding.Message = + fileprivate static let useWhereInsteadOfIf: Finding.Message = "replace this 'if' statement with a 'where' clause" - @_spi(Rules) - public static let useWhereInsteadOfGuard: Finding.Message = + fileprivate static let useWhereInsteadOfGuard: Finding.Message = "replace this 'guard' statement with a 'where' clause" } diff --git a/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift index 9f8883762..95c6c1238 100644 --- a/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift @@ -167,39 +167,32 @@ fileprivate func parametersAreEqual( } extension Finding.Message { - @_spi(Rules) - public static func documentReturnValue(funcName: String) -> Finding.Message { + fileprivate static func documentReturnValue(funcName: String) -> Finding.Message { "add a 'Returns:' section to document the return value of '\(funcName)'" } - @_spi(Rules) - public static func removeReturnComment(funcName: String) -> Finding.Message { + fileprivate static func removeReturnComment(funcName: String) -> Finding.Message { "remove the 'Returns:' section of '\(funcName)'; it does not return a value" } - @_spi(Rules) - public static func parametersDontMatch(funcName: String) -> Finding.Message { + fileprivate static func parametersDontMatch(funcName: String) -> Finding.Message { "change the parameters of the documentation of '\(funcName)' to match its parameters" } - @_spi(Rules) - public static let useSingularParameter: Finding.Message = + fileprivate static let useSingularParameter: Finding.Message = "replace the plural 'Parameters:' section with a singular inline 'Parameter' section" - @_spi(Rules) - public static let usePluralParameters: Finding.Message = + fileprivate static let usePluralParameters: Finding.Message = """ replace the singular inline 'Parameter' section with a plural 'Parameters:' section \ that has the parameters nested inside it """ - @_spi(Rules) - public static func removeThrowsComment(funcName: String) -> Finding.Message { + fileprivate static func removeThrowsComment(funcName: String) -> Finding.Message { "remove the 'Throws:' sections of '\(funcName)'; it does not throw any errors" } - @_spi(Rules) - public static func documentErrorsThrown(funcName: String) -> Finding.Message { + fileprivate static func documentErrorsThrown(funcName: String) -> Finding.Message { "add a 'Throws:' section to document the errors thrown by '\(funcName)'" } } From 41c657a92e9906cb67a244fa6465dbdba81afef5 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 21 Sep 2023 18:21:34 -0400 Subject: [PATCH 151/332] Remove the `SwiftFormatConfiguration` module. Now that 509.0.0 has been cut, we can remove this compatibility module. Users should just import `SwiftFormat` for everything now. --- Package.swift | 14 +------------- .../Compatibility.swift | 19 ------------------- 2 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 Sources/SwiftFormatConfiguration/Compatibility.swift diff --git a/Package.swift b/Package.swift index ae4d42be0..3169369d2 100644 --- a/Package.swift +++ b/Package.swift @@ -27,12 +27,7 @@ let package = Package( ), .library( name: "SwiftFormat", - targets: ["SwiftFormat", "SwiftFormatConfiguration"] - ), - // TODO: Remove this product after the 509 release. - .library( - name: "SwiftFormatConfiguration", - targets: ["SwiftFormatConfiguration"] + targets: ["SwiftFormat"] ), .plugin( name: "FormatPlugin", @@ -62,13 +57,6 @@ let package = Package( .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), ] ), - // TODO: Remove this target after the 509 release. - .target( - name: "SwiftFormatConfiguration", - dependencies: [ - "SwiftFormat" - ] - ), .target( name: "_SwiftFormatTestSupport", dependencies: [ diff --git a/Sources/SwiftFormatConfiguration/Compatibility.swift b/Sources/SwiftFormatConfiguration/Compatibility.swift deleted file mode 100644 index 3fc586541..000000000 --- a/Sources/SwiftFormatConfiguration/Compatibility.swift +++ /dev/null @@ -1,19 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -// Make these symbols that used to live in `SwiftFormatConfiguration` available when that module is -// imported. -// TODO: Remove this after the 509 release. -@_exported import struct SwiftFormat.Configuration -@_exported import struct SwiftFormat.FileScopedDeclarationPrivacyConfiguration -@_exported import struct SwiftFormat.NoAssignmentInExpressionsConfiguration -@_exported import enum SwiftFormat.Indent From 7e4c06adbe2d86e3c1fa933d3188d40a701e4398 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 21 Sep 2023 18:29:43 -0400 Subject: [PATCH 152/332] Rename `generate-pipeline` to `generate-swift-format`. The name referring to just the pipeline is somewhat outdated now; it generates a lot of other stuff now too. So give it a more appropriate name indicating that it generates all the swift-format things. --- Documentation/Development.md | 4 ++-- Documentation/RuleDocumentation.md | 5 ++--- Package.swift | 2 +- Sources/SwiftFormat/API/Configuration.swift | 2 +- Sources/SwiftFormat/Core/Context.swift | 2 +- Sources/SwiftFormat/Core/Pipelines+Generated.swift | 2 +- Sources/SwiftFormat/Core/RuleNameCache+Generated.swift | 2 +- Sources/SwiftFormat/Core/RuleRegistry+Generated.swift | 2 +- .../FileGenerator.swift | 0 .../PipelineGenerator.swift | 2 +- .../RuleCollector.swift | 0 .../RuleDocumentationGenerator.swift | 3 +-- .../RuleNameCacheGenerator.swift | 2 +- .../RuleRegistryGenerator.swift | 2 +- .../Syntax+Convenience.swift | 0 .../{generate-pipeline => generate-swift-format}/main.swift | 0 16 files changed, 14 insertions(+), 16 deletions(-) rename Sources/{generate-pipeline => generate-swift-format}/FileGenerator.swift (100%) rename Sources/{generate-pipeline => generate-swift-format}/PipelineGenerator.swift (97%) rename Sources/{generate-pipeline => generate-swift-format}/RuleCollector.swift (100%) rename Sources/{generate-pipeline => generate-swift-format}/RuleDocumentationGenerator.swift (94%) rename Sources/{generate-pipeline => generate-swift-format}/RuleNameCacheGenerator.swift (95%) rename Sources/{generate-pipeline => generate-swift-format}/RuleRegistryGenerator.swift (95%) rename Sources/{generate-pipeline => generate-swift-format}/Syntax+Convenience.swift (100%) rename Sources/{generate-pipeline => generate-swift-format}/main.swift (100%) diff --git a/Documentation/Development.md b/Documentation/Development.md index 5445d2ed4..f9c67fce8 100644 --- a/Documentation/Development.md +++ b/Documentation/Development.md @@ -6,12 +6,12 @@ Since Swift does not yet have a runtime reflection system, we use code generation to keep the linting/formatting pipeline up-to-date. If you add or remove any rules from the `SwiftFormat` module, or if you add or remove any `visit` methods from an existing rule in that module, you must run the -`generate-pipeline` tool update the pipeline and configuration sources. +`generate-swift-format` tool update the pipeline and configuration sources. The easiest way to do this is to run the following command in your terminal: ```shell -swift run generate-pipeline +swift run generate-swift-format ``` If successful, this tool will update the files `Pipelines+Generated.swift`, diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index eb761845b..cc3ae92de 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -1,11 +1,10 @@ - + # `swift-format` Lint and Format Rules Use the rules below in the `rules` block of your `.swift-format` configuration file, as described in -[Configuration](Configuration.md). All of these rules can be +[Configuration](Documentation/Configuration.md). All of these rules can be applied in the linter, but only some of them can format your source code automatically. diff --git a/Package.swift b/Package.swift index ae4d42be0..7705de0f2 100644 --- a/Package.swift +++ b/Package.swift @@ -103,7 +103,7 @@ let package = Package( path: "Plugins/LintPlugin" ), .executableTarget( - name: "generate-pipeline", + name: "generate-swift-format", dependencies: [ "SwiftFormat", .product(name: "SwiftSyntax", package: "swift-syntax"), diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 17ef00eaa..ec7d97679 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -48,7 +48,7 @@ public struct Configuration: Codable, Equatable { /// A dictionary containing the default enabled/disabled states of rules, keyed by the rules' /// names. /// - /// This value is generated by `generate-pipeline` based on the `isOptIn` value of each rule. + /// This value is generated by `generate-swift-format` based on the `isOptIn` value of each rule. public static let defaultRuleEnablements: [String: Bool] = RuleRegistry.rules /// The version of this configuration. diff --git a/Sources/SwiftFormat/Core/Context.swift b/Sources/SwiftFormat/Core/Context.swift index 8851a685c..29e69b0dc 100644 --- a/Sources/SwiftFormat/Core/Context.swift +++ b/Sources/SwiftFormat/Core/Context.swift @@ -94,7 +94,7 @@ public final class Context { ruleNameCache[ObjectIdentifier(rule)] != nil, """ Missing cached rule name for '\(rule)'! \ - Ensure `generate-pipelines` has been run and `ruleNameCache` was injected. + Ensure `generate-swift-format` has been run and `ruleNameCache` was injected. """) let ruleName = ruleNameCache[ObjectIdentifier(rule)] ?? R.ruleName diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index a7432e538..6e9615823 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -// This file is automatically generated with generate-pipeline. Do Not Edit! +// This file is automatically generated with generate-swift-format. Do not edit! import SwiftSyntax diff --git a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift index f487d349c..b19b88d6b 100644 --- a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -// This file is automatically generated with generate-pipeline. Do Not Edit! +// This file is automatically generated with generate-swift-format. Do not edit! /// By default, the `Rule.ruleName` should be the name of the implementing rule type. @_spi(Testing) diff --git a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift index cf91f9d04..9fb96fcf0 100644 --- a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -// This file is automatically generated with generate-pipeline. Do Not Edit! +// This file is automatically generated with generate-swift-format. Do not edit! @_spi(Internal) public enum RuleRegistry { public static let rules: [String: Bool] = [ diff --git a/Sources/generate-pipeline/FileGenerator.swift b/Sources/generate-swift-format/FileGenerator.swift similarity index 100% rename from Sources/generate-pipeline/FileGenerator.swift rename to Sources/generate-swift-format/FileGenerator.swift diff --git a/Sources/generate-pipeline/PipelineGenerator.swift b/Sources/generate-swift-format/PipelineGenerator.swift similarity index 97% rename from Sources/generate-pipeline/PipelineGenerator.swift rename to Sources/generate-swift-format/PipelineGenerator.swift index 4b0054ccb..abf8b2c0e 100644 --- a/Sources/generate-pipeline/PipelineGenerator.swift +++ b/Sources/generate-swift-format/PipelineGenerator.swift @@ -38,7 +38,7 @@ final class PipelineGenerator: FileGenerator { // //===----------------------------------------------------------------------===// - // This file is automatically generated with generate-pipeline. Do Not Edit! + // This file is automatically generated with generate-swift-format. Do not edit! import SwiftSyntax diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-swift-format/RuleCollector.swift similarity index 100% rename from Sources/generate-pipeline/RuleCollector.swift rename to Sources/generate-swift-format/RuleCollector.swift diff --git a/Sources/generate-pipeline/RuleDocumentationGenerator.swift b/Sources/generate-swift-format/RuleDocumentationGenerator.swift similarity index 94% rename from Sources/generate-pipeline/RuleDocumentationGenerator.swift rename to Sources/generate-swift-format/RuleDocumentationGenerator.swift index fccddee4f..c90cc62e4 100644 --- a/Sources/generate-pipeline/RuleDocumentationGenerator.swift +++ b/Sources/generate-swift-format/RuleDocumentationGenerator.swift @@ -27,8 +27,7 @@ final class RuleDocumentationGenerator: FileGenerator { func write(into handle: FileHandle) throws { handle.write( """ - + # `swift-format` Lint and Format Rules diff --git a/Sources/generate-pipeline/RuleNameCacheGenerator.swift b/Sources/generate-swift-format/RuleNameCacheGenerator.swift similarity index 95% rename from Sources/generate-pipeline/RuleNameCacheGenerator.swift rename to Sources/generate-swift-format/RuleNameCacheGenerator.swift index 06c2978e6..55db7063b 100644 --- a/Sources/generate-pipeline/RuleNameCacheGenerator.swift +++ b/Sources/generate-swift-format/RuleNameCacheGenerator.swift @@ -38,7 +38,7 @@ final class RuleNameCacheGenerator: FileGenerator { // //===----------------------------------------------------------------------===// - // This file is automatically generated with generate-pipeline. Do Not Edit! + // This file is automatically generated with generate-swift-format. Do not edit! /// By default, the `Rule.ruleName` should be the name of the implementing rule type. @_spi(Testing) diff --git a/Sources/generate-pipeline/RuleRegistryGenerator.swift b/Sources/generate-swift-format/RuleRegistryGenerator.swift similarity index 95% rename from Sources/generate-pipeline/RuleRegistryGenerator.swift rename to Sources/generate-swift-format/RuleRegistryGenerator.swift index 8e4c66ff5..3994f5b3f 100644 --- a/Sources/generate-pipeline/RuleRegistryGenerator.swift +++ b/Sources/generate-swift-format/RuleRegistryGenerator.swift @@ -38,7 +38,7 @@ final class RuleRegistryGenerator: FileGenerator { // //===----------------------------------------------------------------------===// - // This file is automatically generated with generate-pipeline. Do Not Edit! + // This file is automatically generated with generate-swift-format. Do not edit! @_spi(Internal) public enum RuleRegistry { public static let rules: [String: Bool] = [ diff --git a/Sources/generate-pipeline/Syntax+Convenience.swift b/Sources/generate-swift-format/Syntax+Convenience.swift similarity index 100% rename from Sources/generate-pipeline/Syntax+Convenience.swift rename to Sources/generate-swift-format/Syntax+Convenience.swift diff --git a/Sources/generate-pipeline/main.swift b/Sources/generate-swift-format/main.swift similarity index 100% rename from Sources/generate-pipeline/main.swift rename to Sources/generate-swift-format/main.swift From 4e1129510987daeb46b62a2822be285554806eb6 Mon Sep 17 00:00:00 2001 From: Beat Rupp Date: Sat, 23 Sep 2023 11:38:27 +0200 Subject: [PATCH 153/332] Add installation instructions for Homebrew --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1a0a73ded..05d5d9b83 100644 --- a/README.md +++ b/README.md @@ -61,11 +61,19 @@ For example, if you are using Xcode 13.3 (Swift 5.6), you will need ## Getting swift-format If you are mainly interested in using swift-format (rather than developing it), -then once you have identified the version you need, you can check out the -source and build it using the following commands: +then you can get swift-format either via [Homebrew](https://brew.sh/) or by checking out the +source and building it. + +### Installing via Homebrew + +Run `brew install swift-format` to install the latest version. + +### Building from source + +Install swift-fromat using the following commands: ```sh -VERSION=508.0.0 # replace this with the version you need +VERSION=509.0.0 # replace this with the version you need git clone https://github.com/apple/swift-format.git cd swift-format git checkout "tags/$VERSION" From a059ab2c37a6ab8a3aa7178f7f8e76205e1e7afc Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 28 Sep 2023 14:34:16 -0400 Subject: [PATCH 154/332] Fix a bug where an unfolded `SequenceExpr` would make it to the pretty-printer. Since the `UseExplicitNilCheckInConditions` rule was using string interpolation based parsing, it returned a `SequenceExpr` instead of an `InfixOperatorExpr`. This unfolded expression then made it to the pretty printer, which it didn't expect (because the input is folded before being processed by the rules, but not after that). In this case, creating the new expression directly as nodes is barely more work than using the string-based parsing and then folding it, so I just did that. Tests didn't catch this because the sequence expr is valid Swift code when stringified (for comparison in unit tests). To address this, we now do a pretty-printer pass on all the format rule outputs. We don't assert based on the pretty-printed output (that's not really a "unit" test anymore), but it at least causes a test crash if the output wasn't suitable for pretty printing. Doing so caught a similar latent bug in `UseShorthandTypeNames`. --- .../PrettyPrint/TokenStreamCreator.swift | 6 +++++- .../Rules/UseExplicitNilCheckInConditions.swift | 14 +++++++++++--- .../SwiftFormat/Rules/UseShorthandTypeNames.swift | 11 +++++------ .../Rules/LintOrFormatRuleTestCase.swift | 11 +++++++++++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 048f0c866..fc2b82824 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -2079,7 +2079,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: SequenceExprSyntax) -> SyntaxVisitorContinueKind { - preconditionFailure("SequenceExpr should have already been folded.") + preconditionFailure( + """ + SequenceExpr should have already been folded; found at byte offsets \ + \(node.position.utf8Offset)..<\(node.endPosition.utf8Offset) + """) } override func visit(_ node: AssignmentExprSyntax) -> SyntaxVisitorContinueKind { diff --git a/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift index 030c41e74..537884759 100644 --- a/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift +++ b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift @@ -41,10 +41,18 @@ public final class UseExplicitNilCheckInConditions: SyntaxFormatRule { // trivia of the original node, since that token is being removed entirely. var value = initializerClause.value let trailingTrivia = value.trailingTrivia - value.trailingTrivia = [] + value.trailingTrivia = [.spaces(1)] - return ConditionElementSyntax( - condition: .expression("\(node.leadingTrivia)\(value) != nil\(trailingTrivia)")) + var operatorExpr = BinaryOperatorExprSyntax(text: "!=") + operatorExpr.trailingTrivia = [.spaces(1)] + + var inequalExpr = InfixOperatorExprSyntax( + leftOperand: value, + operator: operatorExpr, + rightOperand: NilLiteralExprSyntax()) + inequalExpr.leadingTrivia = node.leadingTrivia + inequalExpr.trailingTrivia = trailingTrivia + return ConditionElementSyntax(condition: .expression(ExprSyntax(inequalExpr))) default: return node } diff --git a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift index 32df4662d..1c3db0cc5 100644 --- a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift @@ -478,7 +478,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { effectSpecifiers: TypeEffectSpecifiersSyntax?, arrow: TokenSyntax, returnType: TypeSyntax - ) -> SequenceExprSyntax? { + ) -> InfixOperatorExprSyntax? { guard let parameterExprs = expressionRepresentation(of: parameters), let returnTypeExpr = expressionRepresentation(of: returnType) @@ -493,11 +493,10 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let arrowExpr = ArrowExprSyntax( effectSpecifiers: effectSpecifiers, arrow: arrow) - - return SequenceExprSyntax( - elements: ExprListSyntax([ - ExprSyntax(tupleExpr), ExprSyntax(arrowExpr), returnTypeExpr, - ])) + return InfixOperatorExprSyntax( + leftOperand: tupleExpr, + operator: arrowExpr, + rightOperand: returnTypeExpr) } /// Returns the leading and trailing trivia from the front and end of the entire given node. diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index d249ae216..cb83fa621 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -100,5 +100,16 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { context: context, file: file, line: line) + + // Verify that the pretty printer can consume the transformed tree (e.g., it does not contain + // any unfolded `SequenceExpr`s). We don't need to check the actual output here (we don't want + // the rule tests to be pretty-printer dependent), but this will catch invariants that aren't + // satisfied. + _ = PrettyPrinter( + context: context, + node: Syntax(actual), + printTokenStream: false, + whitespaceOnly: false + ).prettyPrint() } } From f01e0445181268a17a4e86612d2adef438a29116 Mon Sep 17 00:00:00 2001 From: Dylan Sturgeon Date: Thu, 28 Sep 2023 13:29:57 -0700 Subject: [PATCH 155/332] Ignore too long end of line comments when they're wrapped in `printControl` tokens that disable breaking. The rationale here is that if the comment exists in a section of code where breaking is purposefully disabled, then there's probably a reason that the comment cannot be moved to another line. The `printControl` tokens that disable breaking are the signal to the pretty print algorithm for which sections are those special "no breaks allowed here", and it can just respect those when diagnosing long lines. --- .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 2 +- .../PrettyPrint/TokenStreamCreator.swift | 9 +-- .../PrettyPrint/CommentTests.swift | 72 +++++++++++++------ 3 files changed, 57 insertions(+), 26 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 5f4ff24c8..1201f84c2 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -515,7 +515,7 @@ public class PrettyPrinter { write(comment.print(indent: currentIndentation)) if wasEndOfLine { - if comment.length > spaceRemaining { + if comment.length > spaceRemaining && !isBreakingSuppressed { diagnose(.moveEndOfLineComment, category: .endOfLineComment) } } else { diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 048f0c866..fc10ebbf0 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -2742,9 +2742,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// will stay inside the group. /// /// * If the trailing comment is a line comment, we first append any enqueued after-tokens - /// that are *not* breaks or newlines, then we append the comment, and then the remaining - /// after-tokens. Due to visitation ordering, this ensures that a trailing line comment is - /// not incorrectly inserted into the token stream *after* a break or newline. + /// that are *not* related to breaks or newlines (e.g. includes print control tokens), then + /// we append the comment, and then the remaining after-tokens. Due to visitation ordering, + /// this ensures that a trailing line comment is not incorrectly inserted into the token stream + /// *after* a break or newline. private func appendAfterTokensAndTrailingComments(_ token: TokenSyntax) { let (wasLineComment, trailingCommentTokens) = afterTokensForTrailingComment(token) let afterGroups = afterMap.removeValue(forKey: token) ?? [] @@ -2759,7 +2760,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { var shouldExtractTrailingComment = false if wasLineComment && !hasAppendedTrailingComment { switch afterToken { - case .break: shouldExtractTrailingComment = true + case .break, .printerControl: shouldExtractTrailingComment = true default: break } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift index c197c5d69..96aeb9908 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift @@ -1,3 +1,5 @@ +import _SwiftFormatTestSupport + final class CommentTests: PrettyPrintTestCase { func testDocumentationComments() { let input = @@ -408,7 +410,7 @@ final class CommentTests: PrettyPrintTestCase { case quux } """ - + let expected = """ struct Foo { @@ -424,7 +426,7 @@ final class CommentTests: PrettyPrintTestCase { } """ - + assertPrettyPrintEqual(input: input, expected: expected, linelength: 100) } @@ -594,25 +596,25 @@ final class CommentTests: PrettyPrintTestCase { func testCommentsInIfStatements() { let input = - """ - if foo.bar && false && // comment about foo.bar - baz && // comment about baz - // comment about next - next - && // other is important - // second line about other - other && - // comment about final on a new line - final - { - } - if foo.bar && foo.baz - && // comment about the next line - // another comment line - next.line - { - } - """ + """ + if foo.bar && false && // comment about foo.bar + baz && // comment about baz + // comment about next + next + && // other is important + // second line about other + other && + // comment about final on a new line + final + { + } + if foo.bar && foo.baz + && // comment about the next line + // another comment line + next.line + { + } + """ let expected = """ @@ -682,4 +684,32 @@ final class CommentTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 60) } + + func testDiagnoseMoveEndOfLineComment() { + assertPrettyPrintEqual( + input: """ + import veryveryverylongmodulenameherebecauseitistypical // special sentinel comment + + func fooBarBazRunningOutOfIdeas() { 1️⃣// comment that needs to move + if foo { // comment is fine + } + } + + """, + expected: """ + import veryveryverylongmodulenameherebecauseitistypical // special sentinel comment + + func fooBarBazRunningOutOfIdeas() { // comment that needs to move + if foo { // comment is fine + } + } + + """, + linelength: 45, + whitespaceOnly: true, + findings: [ + FindingSpec("1️⃣", message: "move end-of-line comment that exceeds the line length"), + ] + ) + } } From b8d99db747d5dba39042964d94ce781249ed7949 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Fri, 29 Sep 2023 12:46:37 -0400 Subject: [PATCH 156/332] Don't lose a trailing comma in `UseExplicitNilCheckInConditions`. This was happening because we were creating a new `ConditionListElement` and not considering the `trailingComma` property. It's safer to just mutate the original node, preserving anything that was previously there. --- .../Rules/UseExplicitNilCheckInConditions.swift | 5 ++++- .../UseExplicitNilCheckInConditionsTests.swift | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift index 537884759..a0b036fd3 100644 --- a/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift +++ b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift @@ -52,7 +52,10 @@ public final class UseExplicitNilCheckInConditions: SyntaxFormatRule { rightOperand: NilLiteralExprSyntax()) inequalExpr.leadingTrivia = node.leadingTrivia inequalExpr.trailingTrivia = trailingTrivia - return ConditionElementSyntax(condition: .expression(ExprSyntax(inequalExpr))) + + var result = node + result.condition = .expression(ExprSyntax(inequalExpr)) + return result default: return node } diff --git a/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift b/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift index 4f6337e85..f8f63c3a5 100644 --- a/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift @@ -79,4 +79,20 @@ final class UseExplicitNilCheckInConditionsTests: LintOrFormatRuleTestCase { ] ) } + + func testDoNotDropTrailingCommaInConditionList() { + assertFormatting( + UseExplicitNilCheckInConditions.self, + input: """ + if 1️⃣let _ = x, 2️⃣let _ = y {} + """, + expected: """ + if x != nil, y != nil {} + """, + findings: [ + FindingSpec("1️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + FindingSpec("2️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + ] + ) + } } From c9a84d621360f14480cd869c4dd9af392a81040e Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 29 Aug 2023 12:21:19 -0400 Subject: [PATCH 157/332] Ignore symlinks and hidden (dot) files during `--recursive`. Note that hidden files will *always* be honored if they are specified explicitly among the paths on the command line. Symlinks encountered during recursive traversal *or* passed on the command line will only be followed if the flag `--follow-symlinks` is passed. This is meant to avoid symlinks being hidden by shell expansions; for example, running `swift-format *` where the current directory contains a symlink and following that symlink might be surprising to users. This PR also adds a new test target for code in the `swift-format` binary in order to test `FileIterator` without factoring the binary's code out to a separate library target. We should use this in the future to expand coverage of other functionality like the frontends and commands. --- Package.swift | 4 + Sources/swift-format/Frontend/Frontend.swift | 9 +- .../Subcommands/LintFormatOptions.swift | 7 ++ .../swift-format/Utilities/FileIterator.swift | 115 +++++++++++++----- .../Utilities/FileIteratorTests.swift | 91 ++++++++++++++ 5 files changed, 193 insertions(+), 33 deletions(-) create mode 100644 Tests/swift-formatTests/Utilities/FileIteratorTests.swift diff --git a/Package.swift b/Package.swift index b7ec1973e..9b74aa635 100644 --- a/Package.swift +++ b/Package.swift @@ -130,6 +130,10 @@ let package = Package( .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ] ), + .testTarget( + name: "swift-formatTests", + dependencies: ["swift-format"] + ), ] ) diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index 86930ee0b..2d5274160 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -130,12 +130,17 @@ class Frontend { "processURLs(_:) should only be called when 'urls' is non-empty.") if parallel { - let filesToProcess = FileIterator(urls: urls).compactMap(openAndPrepareFile) + let filesToProcess = + FileIterator(urls: urls, followSymlinks: lintFormatOptions.followSymlinks) + .compactMap(openAndPrepareFile) DispatchQueue.concurrentPerform(iterations: filesToProcess.count) { index in processFile(filesToProcess[index]) } } else { - FileIterator(urls: urls).lazy.compactMap(openAndPrepareFile).forEach(processFile) + FileIterator(urls: urls, followSymlinks: lintFormatOptions.followSymlinks) + .lazy + .compactMap(openAndPrepareFile) + .forEach(processFile) } } diff --git a/Sources/swift-format/Subcommands/LintFormatOptions.swift b/Sources/swift-format/Subcommands/LintFormatOptions.swift index 170ebaad2..4e98d1e14 100644 --- a/Sources/swift-format/Subcommands/LintFormatOptions.swift +++ b/Sources/swift-format/Subcommands/LintFormatOptions.swift @@ -71,6 +71,13 @@ struct LintFormatOptions: ParsableArguments { """) var colorDiagnostics: Bool? + /// Whether symlinks should be followed. + @Flag(help: """ + Follow symbolic links passed on the command line, or found during directory traversal when \ + using `-r/--recursive`. + """) + var followSymlinks: Bool = false + /// The list of paths to Swift source files that should be formatted or linted. @Argument(help: "Zero or more input filenames.") var paths: [String] = [] diff --git a/Sources/swift-format/Utilities/FileIterator.swift b/Sources/swift-format/Utilities/FileIterator.swift index 20b513a01..6f88cc909 100644 --- a/Sources/swift-format/Utilities/FileIterator.swift +++ b/Sources/swift-format/Utilities/FileIterator.swift @@ -14,54 +14,82 @@ import Foundation /// Iterator for looping over lists of files and directories. Directories are automatically /// traversed recursively, and we check for files with a ".swift" extension. -struct FileIterator: Sequence, IteratorProtocol { +@_spi(Testing) +public struct FileIterator: Sequence, IteratorProtocol { /// List of file and directory URLs to iterate over. - let urls: [URL] + private let urls: [URL] + + /// If true, symlinks will be followed when iterating over directories and files. If not, they + /// will be ignored. + private let followSymlinks: Bool /// Iterator for the list of URLs. - var urlIterator: Array.Iterator + private var urlIterator: Array.Iterator /// Iterator for recursing through directories. - var dirIterator: FileManager.DirectoryEnumerator? = nil + private var dirIterator: FileManager.DirectoryEnumerator? = nil /// The current working directory of the process, which is used to relativize URLs of files found /// during iteration. - let workingDirectory = URL(fileURLWithPath: ".") + private let workingDirectory = URL(fileURLWithPath: ".") /// Keep track of the current directory we're recursing through. - var currentDirectory = URL(fileURLWithPath: "") + private var currentDirectory = URL(fileURLWithPath: "") /// Keep track of files we have visited to prevent duplicates. - var visited: Set = [] + private var visited: Set = [] /// The file extension to check for when recursing through directories. - let fileSuffix = ".swift" + private let fileSuffix = ".swift" /// Create a new file iterator over the given list of file URLs. /// /// The given URLs may be files or directories. If they are directories, the iterator will recurse /// into them. - init(urls: [URL]) { + public init(urls: [URL], followSymlinks: Bool) { self.urls = urls self.urlIterator = self.urls.makeIterator() + self.followSymlinks = followSymlinks } /// Iterate through the "paths" list, and emit the file paths in it. If we encounter a directory, /// recurse through it and emit .swift file paths. - mutating func next() -> URL? { + public mutating func next() -> URL? { var output: URL? = nil while output == nil { // Check if we're recursing through a directory. if dirIterator != nil { output = nextInDirectory() } else { - guard let next = urlIterator.next() else { return nil } - var isDir: ObjCBool = false - if FileManager.default.fileExists(atPath: next.path, isDirectory: &isDir), isDir.boolValue { - dirIterator = FileManager.default.enumerator(at: next, includingPropertiesForKeys: nil) + guard var next = urlIterator.next() else { + // If we've reached the end of all the URLs we wanted to iterate over, exit now. + return nil + } + + guard let fileType = fileType(at: next) else { + continue + } + + switch fileType { + case .typeSymbolicLink: + guard + followSymlinks, + let destination = try? FileManager.default.destinationOfSymbolicLink(atPath: next.path) + else { + break + } + next = URL(fileURLWithPath: destination) + fallthrough + + case .typeDirectory: + dirIterator = FileManager.default.enumerator( + at: next, + includingPropertiesForKeys: nil, + options: [.skipsHiddenFiles]) currentDirectory = next - } else { + + default: // We'll get here if the path is a file, or if it doesn't exist. In the latter case, // return the path anyway; we'll turn the error we get when we try to open the file into // an appropriate diagnostic instead of trying to handle it here. @@ -82,23 +110,41 @@ struct FileIterator: Sequence, IteratorProtocol { private mutating func nextInDirectory() -> URL? { var output: URL? = nil while output == nil { - if let item = dirIterator?.nextObject() as? URL { - if item.lastPathComponent.hasSuffix(fileSuffix) { - var isDir: ObjCBool = false - if FileManager.default.fileExists(atPath: item.path, isDirectory: &isDir) - && !isDir.boolValue - { - // We can't use the `.producesRelativePathURLs` enumeration option because it isn't - // supported yet on Linux, so we need to relativize the URL ourselves. - let relativePath = - item.path.hasPrefix(workingDirectory.path) - ? String(item.path.dropFirst(workingDirectory.path.count + 1)) - : item.path - output = - URL(fileURLWithPath: relativePath, isDirectory: false, relativeTo: workingDirectory) - } + guard let item = dirIterator?.nextObject() as? URL else { + break + } + + guard item.lastPathComponent.hasSuffix(fileSuffix), let fileType = fileType(at: item) else { + continue + } + + var path = item.path + switch fileType { + case .typeSymbolicLink where followSymlinks: + guard + let destination = try? FileManager.default.destinationOfSymbolicLink(atPath: path) + else { + break } - } else { break } + path = destination + fallthrough + + case .typeRegular: + // We attempt to relativize the URLs based on the current working directory, not the + // directory being iterated over, so that they can be displayed better in diagnostics. Thus, + // if the user passes paths that are relative to the current working diectory, they will + // be displayed as relative paths. Otherwise, they will still be displayed as absolute + // paths. + let relativePath = + path.hasPrefix(workingDirectory.path) + ? String(path.dropFirst(workingDirectory.path.count + 1)) + : path + output = + URL(fileURLWithPath: relativePath, isDirectory: false, relativeTo: workingDirectory) + + default: + break + } } // If we've exhausted the files in the directory recursion, unset the directory iterator. if output == nil { @@ -107,3 +153,10 @@ struct FileIterator: Sequence, IteratorProtocol { return output } } + +/// Returns the type of the file at the given URL. +private func fileType(at url: URL) -> FileAttributeType? { + // We cannot use `URL.resourceValues(forKeys:)` here because it appears to behave incorrectly on + // Linux. + return try? FileManager.default.attributesOfItem(atPath: url.path)[.type] as? FileAttributeType +} diff --git a/Tests/swift-formatTests/Utilities/FileIteratorTests.swift b/Tests/swift-formatTests/Utilities/FileIteratorTests.swift new file mode 100644 index 000000000..5bfe5bb18 --- /dev/null +++ b/Tests/swift-formatTests/Utilities/FileIteratorTests.swift @@ -0,0 +1,91 @@ +import XCTest + +@_spi(Testing) import swift_format + +final class FileIteratorTests: XCTestCase { + private var tmpdir: URL! + + override func setUpWithError() throws { + tmpdir = try FileManager.default.url( + for: .itemReplacementDirectory, + in: .userDomainMask, + appropriateFor: FileManager.default.temporaryDirectory, + create: true) + + // Create a simple file tree used by the tests below. + try touch("project/real1.swift") + try touch("project/real2.swift") + try touch("project/.hidden.swift") + try touch("project/.build/generated.swift") + try symlink("project/link.swift", to: "project/.hidden.swift") + } + + override func tearDownWithError() throws { + try FileManager.default.removeItem(at: tmpdir) + } + + func testNoFollowSymlinks() { + let seen = allFilesSeen(iteratingOver: [tmpdir], followSymlinks: false) + XCTAssertEqual(seen.count, 2) + XCTAssertTrue(seen.contains { $0.hasSuffix("project/real1.swift") }) + XCTAssertTrue(seen.contains { $0.hasSuffix("project/real2.swift") }) + } + + func testFollowSymlinks() { + let seen = allFilesSeen(iteratingOver: [tmpdir], followSymlinks: true) + XCTAssertEqual(seen.count, 3) + XCTAssertTrue(seen.contains { $0.hasSuffix("project/real1.swift") }) + XCTAssertTrue(seen.contains { $0.hasSuffix("project/real2.swift") }) + // Hidden but found through the visible symlink project/link.swift + XCTAssertTrue(seen.contains { $0.hasSuffix("project/.hidden.swift") }) + } + + func testTraversesHiddenFilesIfExplicitlySpecified() { + let seen = allFilesSeen( + iteratingOver: [tmpURL("project/.build"), tmpURL("project/.hidden.swift")], + followSymlinks: false) + XCTAssertEqual(seen.count, 2) + XCTAssertTrue(seen.contains { $0.hasSuffix("project/.build/generated.swift") }) + XCTAssertTrue(seen.contains { $0.hasSuffix("project/.hidden.swift") }) + } + + func testDoesNotFollowSymlinksIfFollowSymlinksIsFalseEvenIfExplicitlySpecified() { + // Symlinks are not traversed even if `followSymlinks` is false even if they are explicitly + // passed to the iterator. This is meant to avoid situations where a symlink could be hidden by + // shell expansion; for example, if the user writes `swift-format --no-follow-symlinks *`, if + // the current directory contains a symlink, they would probably *not* expect it to be followed. + let seen = allFilesSeen(iteratingOver: [tmpURL("project/link.swift")], followSymlinks: false) + XCTAssertTrue(seen.isEmpty) + } +} + +extension FileIteratorTests { + /// Returns a URL to a file or directory in the test's temporary space. + private func tmpURL(_ path: String) -> URL { + return tmpdir.appendingPathComponent(path, isDirectory: false) + } + + /// Create an empty file at the given path in the test's temporary space. + private func touch(_ path: String) throws { + let fileURL = tmpURL(path) + try FileManager.default.createDirectory( + at: fileURL.deletingLastPathComponent(), withIntermediateDirectories: true) + FileManager.default.createFile(atPath: fileURL.path, contents: Data()) + } + + /// Create a symlink between files or directories in the test's temporary space. + private func symlink(_ source: String, to target: String) throws { + try FileManager.default.createSymbolicLink( + at: tmpURL(source), withDestinationURL: tmpURL(target)) + } + + /// Computes the list of all files seen by using `FileIterator` to iterate over the given URLs. + private func allFilesSeen(iteratingOver urls: [URL], followSymlinks: Bool) -> [String] { + let iterator = FileIterator(urls: urls, followSymlinks: followSymlinks) + var seen: [String] = [] + for next in iterator { + seen.append(next.path) + } + return seen + } +} From 2556e08cb42492f80b48f0dfc603a96b2d3203b6 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 27 Sep 2023 19:53:35 -0400 Subject: [PATCH 158/332] Remove the legacy trivia workaround. This was added when SwiftSyntax changed its behavior regarding trailing trivia -- previously it was only whitespace but now it includes all trivia up to the next newline, including comments (block comments and end-of-line comments). This workaround rewrote trivia to correspond to the old layout before processing the syntax tree further so that we didn't have to overhaul all the rules at the time. However, it isn't good for long-term maintenance for swift-format to be written with assumptions about trivia that aren't consistent with how syntax trees Straight Outta The Parser are formed. --- .../Core/LegacyTriviaBehavior.swift | 44 ------- Sources/SwiftFormat/Core/Parsing.swift | 3 +- Sources/SwiftFormat/Core/Rule.swift | 47 +++++-- .../Core/SyntaxProtocol+Convenience.swift | 93 +++++++++++++ .../SwiftFormat/Core/Trivia+Convenience.swift | 14 +- .../PrettyPrint/TokenStreamCreator.swift | 124 +++++++++++------- .../Rules/DoNotUseSemicolons.swift | 105 ++++++++------- .../Rules/NoAssignmentInExpressions.swift | 8 +- .../SwiftFormat/Rules/NoBlockComments.swift | 8 +- .../Rules/NoCasesWithOnlyFallthrough.swift | 13 +- .../Rules/OneVariableDeclarationPerLine.swift | 22 +++- .../SwiftFormat/Rules/OrderedImports.swift | 27 ++-- ...eTripleSlashForDocumentationComments.swift | 2 +- .../PrettyPrint/CommentTests.swift | 48 +++++++ .../PrettyPrint/PrettyPrintTestCase.swift | 5 +- .../Rules/DoNotUseSemicolonsTests.swift | 71 +++++++++- .../Rules/LintOrFormatRuleTestCase.swift | 8 +- 17 files changed, 439 insertions(+), 203 deletions(-) delete mode 100644 Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift diff --git a/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift b/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift deleted file mode 100644 index bb4143fc3..000000000 --- a/Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift +++ /dev/null @@ -1,44 +0,0 @@ -import SwiftSyntax - -/// Rewrites the trivia on tokens in the given source file to restore the legacy trivia behavior -/// before https://github.com/apple/swift-syntax/pull/985 was merged. -/// -/// Eventually we should get rid of this and update the core formatting code to adjust to the new -/// behavior, but this workaround lets us keep the current implementation without larger changes. -@_spi(Testing) -public func restoringLegacyTriviaBehavior(_ sourceFile: SourceFileSyntax) -> SourceFileSyntax { - return LegacyTriviaBehaviorRewriter().visit(sourceFile) -} - -private final class LegacyTriviaBehaviorRewriter: SyntaxRewriter { - /// Trivia that was extracted from the trailing trivia of a token to be prepended to the leading - /// trivia of the next token. - private var pendingLeadingTrivia: Trivia? - - override func visit(_ token: TokenSyntax) -> TokenSyntax { - var token = token - if let pendingLeadingTrivia = pendingLeadingTrivia { - token.leadingTrivia = pendingLeadingTrivia + token.leadingTrivia - self.pendingLeadingTrivia = nil - } - if token.nextToken(viewMode: .sourceAccurate) != nil, - let firstIndexToMove = token.trailingTrivia.firstIndex(where: shouldTriviaPieceBeMoved) - { - pendingLeadingTrivia = Trivia(pieces: Array(token.trailingTrivia[firstIndexToMove...])) - token.trailingTrivia = Trivia(pieces: Array(token.trailingTrivia[.. Bool { - switch piece { - case .spaces, .tabs, .unexpectedText, .backslashes: - return false - default: - return true - } -} diff --git a/Sources/SwiftFormat/Core/Parsing.swift b/Sources/SwiftFormat/Core/Parsing.swift index f4087f124..da57a017f 100644 --- a/Sources/SwiftFormat/Core/Parsing.swift +++ b/Sources/SwiftFormat/Core/Parsing.swift @@ -63,8 +63,7 @@ func parseAndEmitDiagnostics( guard !hasErrors else { throw SwiftFormatError.fileContainsInvalidSyntax } - - return restoringLegacyTriviaBehavior(sourceFile) + return sourceFile } // Wraps a `DiagnosticMessage` but forces its severity to be that of a warning instead of an error. diff --git a/Sources/SwiftFormat/Core/Rule.swift b/Sources/SwiftFormat/Core/Rule.swift index 8e7d3e3b1..5994ea76e 100644 --- a/Sources/SwiftFormat/Core/Rule.swift +++ b/Sources/SwiftFormat/Core/Rule.swift @@ -29,6 +29,22 @@ public protocol Rule { init(context: Context) } +/// The part of a node where an emitted finding should be anchored. +@_spi(Rules) +public enum FindingAnchor { + /// The finding is anchored at the beginning of the node's actual content, skipping any leading + /// trivia. + case start + + /// The finding is anchored at the beginning of the trivia piece at the given index in the node's + /// leading trivia. + case leadingTrivia(Trivia.Index) + + /// The finding is anchored at the beginning of the trivia piece at the given index in the node's + /// trailing trivia. + case trailingTrivia(Trivia.Index) +} + extension Rule { /// By default, the `ruleName` is just the name of the implementing rule class. public static var ruleName: String { String("\(self)".split(separator: ".").last!) } @@ -40,30 +56,37 @@ extension Rule { /// - node: The syntax node to which the finding should be attached. The finding's location will /// be set to the start of the node (excluding leading trivia, unless `leadingTriviaIndex` is /// provided). - /// - leadingTriviaIndex: If non-nil, the index of a trivia piece in the node's leading trivia - /// that should be used to determine the location of the finding. Otherwise, the finding's - /// location will be the start of the node after any leading trivia. + /// - anchor: The part of the node where the finding should be anchored. Defaults to the start + /// of the node's content (after any leading trivia). /// - notes: An array of notes that provide additional detail about the finding. public func diagnose( _ message: Finding.Message, on node: SyntaxType?, severity: Finding.Severity? = nil, - leadingTriviaIndex: Trivia.Index? = nil, + anchor: FindingAnchor = .start, notes: [Finding.Note] = [] ) { let syntaxLocation: SourceLocation? - if let leadingTriviaIndex = leadingTriviaIndex { - syntaxLocation = node?.startLocation( - ofLeadingTriviaAt: leadingTriviaIndex, converter: context.sourceLocationConverter) + if let node = node { + switch anchor { + case .start: + syntaxLocation = node.startLocation(converter: context.sourceLocationConverter) + case .leadingTrivia(let index): + syntaxLocation = node.startLocation( + ofLeadingTriviaAt: index, converter: context.sourceLocationConverter) + case .trailingTrivia(let index): + syntaxLocation = node.startLocation( + ofTrailingTriviaAt: index, converter: context.sourceLocationConverter) + } } else { - syntaxLocation = node?.startLocation(converter: context.sourceLocationConverter) + syntaxLocation = nil } let category = RuleBasedFindingCategory(ruleType: type(of: self), severity: severity) context.findingEmitter.emit( - message, - category: category, - location: syntaxLocation.flatMap(Finding.Location.init), - notes: notes) + message, + category: category, + location: syntaxLocation.flatMap(Finding.Location.init), + notes: notes) } } diff --git a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift index 8d30d38d1..033410212 100644 --- a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift +++ b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift @@ -36,6 +36,29 @@ extension SyntaxProtocol { return self.position + offset } + /// Returns the absolute position of the trivia piece at the given index in the receiver's + /// trailing trivia collection. + /// + /// If the trivia piece spans multiple characters, the value returned is the position of the first + /// character. + /// + /// - Precondition: `index` is a valid index in the receiver's trailing trivia collection. + /// + /// - Parameter index: The index of the trivia piece in the trailing trivia whose position should + /// be returned. + /// - Returns: The absolute position of the trivia piece. + func position(ofTrailingTriviaAt index: Trivia.Index) -> AbsolutePosition { + guard trailingTrivia.indices.contains(index) else { + preconditionFailure("Index was out of bounds in the node's trailing trivia.") + } + + var offset = SourceLength.zero + for currentIndex in trailingTrivia.startIndex.. SourceLocation { return converter.location(for: position(ofLeadingTriviaAt: index)) } + + /// Returns the source location of the trivia piece at the given index in the receiver's trailing + /// trivia collection. + /// + /// If the trivia piece spans multiple characters, the value returned is the location of the first + /// character. + /// + /// - Precondition: `index` is a valid index in the receiver's trailing trivia collection. + /// + /// - Parameters: + /// - index: The index of the trivia piece in the trailing trivia whose location should be + /// returned. + /// - converter: The `SourceLocationConverter` that was previously initialized using the root + /// tree of this node. + /// - Returns: The source location of the trivia piece. + func startLocation( + ofTrailingTriviaAt index: Trivia.Index, + converter: SourceLocationConverter + ) -> SourceLocation { + return converter.location(for: position(ofTrailingTriviaAt: index)) + } + + /// The collection of all contiguous trivia preceding this node; that is, the trailing trivia of + /// the node before it and the leading trivia of the node itself. + var allPrecedingTrivia: Trivia { + var result: Trivia + if let previousTrailingTrivia = previousToken(viewMode: .sourceAccurate)?.trailingTrivia { + result = previousTrailingTrivia + } else { + result = Trivia() + } + result += leadingTrivia + return result + } + + /// The collection of all contiguous trivia following this node; that is, the trailing trivia of + /// the node and the leading trivia of the node after it. + var allFollowingTrivia: Trivia { + var result = trailingTrivia + if let nextLeadingTrivia = nextToken(viewMode: .sourceAccurate)?.leadingTrivia { + result += nextLeadingTrivia + } + return result + } + + /// Indicates whether the node has any preceding line comments. + /// + /// Due to the way trivia is parsed, a preceding comment might be in either the leading trivia of + /// the node or the trailing trivia of the previous token. + var hasPrecedingLineComment: Bool { + if let previousTrailingTrivia = previousToken(viewMode: .sourceAccurate)?.trailingTrivia, + previousTrailingTrivia.hasLineComment + { + return true + } + return leadingTrivia.hasLineComment + } + + /// Indicates whether the node has any preceding comments of any kind. + /// + /// Due to the way trivia is parsed, a preceding comment might be in either the leading trivia of + /// the node or the trailing trivia of the previous token. + var hasAnyPrecedingComment: Bool { + if let previousTrailingTrivia = previousToken(viewMode: .sourceAccurate)?.trailingTrivia, + previousTrailingTrivia.hasAnyComments + { + return true + } + return leadingTrivia.hasAnyComments + } } extension SyntaxCollection { diff --git a/Sources/SwiftFormat/Core/Trivia+Convenience.swift b/Sources/SwiftFormat/Core/Trivia+Convenience.swift index 569e490aa..612cd4236 100644 --- a/Sources/SwiftFormat/Core/Trivia+Convenience.swift +++ b/Sources/SwiftFormat/Core/Trivia+Convenience.swift @@ -34,12 +34,14 @@ extension Trivia { /// Returns this set of trivia, without any leading spaces. func withoutLeadingSpaces() -> Trivia { - return Trivia( - pieces: Array(drop { - if case .spaces = $0 { return false } - if case .tabs = $0 { return false } - return true - })) + return Trivia(pieces: self.pieces.drop(while: \.isSpaceOrTab)) + } + + func withoutTrailingSpaces() -> Trivia { + guard let lastNonSpaceIndex = self.pieces.lastIndex(where: \.isSpaceOrTab) else { + return self + } + return Trivia(pieces: self[.. 1 { @@ -913,7 +915,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: ArrayExprSyntax) -> SyntaxVisitorContinueKind { - if !node.elements.isEmpty || node.rightSquare.leadingTrivia.hasAnyComments { + if !node.elements.isEmpty || node.rightSquare.hasAnyPrecedingComment { after(node.leftSquare, tokens: .break(.open, size: 0), .open) before(node.rightSquare, tokens: .break(.close, size: 0), .close) } @@ -953,8 +955,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // The node's content is either a `DictionaryElementListSyntax` or a `TokenSyntax` for a colon // token (for an empty dictionary). if !(node.content.as(DictionaryElementListSyntax.self)?.isEmpty ?? true) - || node.content.leadingTrivia.hasAnyComments - || node.rightSquare.leadingTrivia.hasAnyComments + || node.content.hasAnyPrecedingComment + || node.rightSquare.hasAnyPrecedingComment { after(node.leftSquare, tokens: .break(.open, size: 0), .open) before(node.rightSquare, tokens: .break(.close, size: 0), .close) @@ -2704,8 +2706,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// some legitimate uses), this is a reasonable compromise to keep the garbage text roughly in the /// same place but still let surrounding formatting occur somewhat as expected. private func appendTrailingTrivia(_ token: TokenSyntax, forced: Bool = false) { - let trailingTrivia = Array(token.trailingTrivia) - let lastIndex: Array.Index + let trailingTrivia = Array(partitionTrailingTrivia(token.trailingTrivia).0) + let lastIndex: Array.Index if forced { lastIndex = trailingTrivia.index(before: trailingTrivia.endIndex) } else { @@ -2823,7 +2825,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) -> Bool where BodyContents.Element: SyntaxProtocol { // If the collection is empty, then any comments that might be present in the block must be // leading trivia of the right brace. - let commentPrecedesRightBrace = node.rightBrace.leadingTrivia.hasAnyComments + let commentPrecedesRightBrace = node.rightBrace.hasAnyPrecedingComment // We can't use `count` here because it also includes missing children. Instead, we get an // iterator and check if it returns `nil` immediately. var contentsIterator = node[keyPath: contentsKeyPath].makeIterator() @@ -2844,7 +2846,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) -> Bool where BodyContents.Element == Syntax { // If the collection is empty, then any comments that might be present in the block must be // leading trivia of the right brace. - let commentPrecedesRightBrace = node.rightBrace.leadingTrivia.hasAnyComments + let commentPrecedesRightBrace = node.rightBrace.hasAnyPrecedingComment // We can't use `count` here because it also includes missing children. Instead, we get an // iterator and check if it returns `nil` immediately. var contentsIterator = node[keyPath: contentsKeyPath].makeIterator() @@ -2865,7 +2867,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) -> Bool where BodyContents.Element == DeclSyntax { // If the collection is empty, then any comments that might be present in the block must be // leading trivia of the right brace. - let commentPrecedesRightBrace = node.rightBrace.leadingTrivia.hasAnyComments + let commentPrecedesRightBrace = node.rightBrace.hasAnyPrecedingComment // We can't use `count` here because it also includes missing children. Instead, we get an // iterator and check if it returns `nil` immediately. var contentsIterator = node[keyPath: contentsKeyPath].makeIterator() @@ -3028,7 +3030,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func arrangeBracesAndContents(leftBrace: TokenSyntax, accessors: AccessorDeclListSyntax, rightBrace: TokenSyntax) { // If the collection is empty, then any comments that might be present in the block must be // leading trivia of the right brace. - let commentPrecedesRightBrace = rightBrace.leadingTrivia.hasAnyComments + let commentPrecedesRightBrace = rightBrace.hasAnyPrecedingComment // We can't use `count` here because it also includes missing children. Instead, we get an // iterator and check if it returns `nil` immediately. var accessorsIterator = accessors.makeIterator() @@ -3061,10 +3063,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func afterTokensForTrailingComment(_ token: TokenSyntax) -> (isLineComment: Bool, tokens: [Token]) { - let nextToken = token.nextToken(viewMode: .sourceAccurate) - guard let trivia = nextToken?.leadingTrivia, - let firstPiece = trivia.first - else { + let (_, trailingComments) = partitionTrailingTrivia(token.trailingTrivia) + let trivia = + Trivia(pieces: trailingComments) + + (token.nextToken(viewMode: .sourceAccurate)?.leadingTrivia ?? []) + + guard let firstPiece = trivia.first else { return (false, []) } @@ -3127,9 +3131,30 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return (beforeTokens, []) } + /// Partitions the given trailing trivia into two contiguous slices: the first containing only + /// whitespace and unexpected text, and the second containing everything else from the first + /// non-whitespace/non-unexpected-text. + /// + /// It is possible that one or both of the slices will be empty. + private func partitionTrailingTrivia(_ trailingTrivia: Trivia) -> (Slice, Slice) { + let pivot = + trailingTrivia.firstIndex { !$0.isSpaceOrTab && !$0.isUnexpectedText } + ?? trailingTrivia.endIndex + return (trailingTrivia[.. SourceFileSyntax { if shouldFormatterIgnore(file: node) { return node @@ -3937,28 +3959,40 @@ class CommentMovingRewriter: SyntaxRewriter { return super.visit(node) } - override func visit(_ token: TokenSyntax) -> TokenSyntax { - guard let rewrittenTrivia = rewriteTokenTriviaMap[token] else { - return token + override func visit(_ node: InfixOperatorExprSyntax) -> ExprSyntax { + var node = super.visit(node).as(InfixOperatorExprSyntax.self)! + guard node.rightOperand.hasAnyPrecedingComment else { + return ExprSyntax(node) + } + + // Rearrange the comments around the operators to make it easier to break properly later. + // Since we break on the left of operators (except for assignment), line comments between an + // operator and the right-hand-side of an expression should be moved to the left of the + // operator. Block comments can remain where they're originally located since they don't force + // breaks. + let operatorLeading = node.operator.leadingTrivia + var operatorTrailing = node.operator.trailingTrivia + let rhsLeading = node.rightOperand.leadingTrivia + + let operatorTrailingLineComment: Trivia + if operatorTrailing.hasLineComment { + operatorTrailingLineComment = [operatorTrailing.pieces.last!] + operatorTrailing = Trivia(pieces: operatorTrailing.dropLast()) + } else { + operatorTrailingLineComment = [] } - var result = token - result.leadingTrivia = rewrittenTrivia - return result - } - - override func visit(_ node: InfixOperatorExprSyntax) -> ExprSyntax { - if let binaryOperatorExpr = node.operator.as(BinaryOperatorExprSyntax.self), - let followingToken = binaryOperatorExpr.operator.nextToken(viewMode: .all), - followingToken.leadingTrivia.hasLineComment - { - // Rewrite the trivia so that the comment is in the operator token's leading trivia. - let (remainingTrivia, extractedTrivia) = extractLineCommentTrivia(from: followingToken) - let combinedTrivia = binaryOperatorExpr.operator.leadingTrivia + extractedTrivia - rewriteTokenTriviaMap[binaryOperatorExpr.operator] = combinedTrivia - rewriteTokenTriviaMap[followingToken] = remainingTrivia + if operatorLeading.containsNewlines { + node.operator.leadingTrivia = operatorLeading + operatorTrailingLineComment + rhsLeading + node.operator.trailingTrivia = operatorTrailing + } else { + node.leftOperand.trailingTrivia += operatorTrailingLineComment + node.operator.leadingTrivia = rhsLeading + node.operator.trailingTrivia = operatorTrailing } - return super.visit(node) + node.rightOperand.leadingTrivia = [] + + return ExprSyntax(node) } /// Extracts trivia containing and related to line comments from `token`'s leading trivia. Returns @@ -4053,10 +4087,7 @@ fileprivate func isFormatterIgnorePresent(inTrivia trivia: Trivia, isWholeFile: fileprivate func shouldFormatterIgnore(node: Syntax) -> Bool { // Regardless of the level of nesting, if the ignore directive is present on the first token // contained within the node then the entire node is eligible for ignoring. - if let firstTrivia = node.firstToken(viewMode: .sourceAccurate)?.leadingTrivia { - return isFormatterIgnorePresent(inTrivia: firstTrivia, isWholeFile: false) - } - return false + return isFormatterIgnorePresent(inTrivia: node.allPrecedingTrivia, isWholeFile: false) } /// Returns whether the formatter should ignore the given file by printing it without changing the @@ -4065,10 +4096,7 @@ fileprivate func shouldFormatterIgnore(node: Syntax) -> Bool { /// /// - Parameter file: The root syntax node for a source file. fileprivate func shouldFormatterIgnore(file: SourceFileSyntax) -> Bool { - if let firstTrivia = file.firstToken(viewMode: .sourceAccurate)?.leadingTrivia { - return isFormatterIgnorePresent(inTrivia: firstTrivia, isWholeFile: true) - } - return false + return isFormatterIgnorePresent(inTrivia: file.allPrecedingTrivia, isWholeFile: true) } extension NewlineBehavior { diff --git a/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift index 2948b9e36..7559d72ed 100644 --- a/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift +++ b/Sources/SwiftFormat/Rules/DoNotUseSemicolons.swift @@ -19,94 +19,105 @@ import SwiftSyntax /// Format: All semicolons will be replaced with line breaks. @_spi(Rules) public final class DoNotUseSemicolons: SyntaxFormatRule { - /// Creates a new version of the given node which doesn't contain any semicolons. The node's /// items are recursively modified to remove semicolons, replacing with line breaks where needed. /// Items are checked recursively to support items that contain code blocks, which may have /// semicolons to be removed. + /// /// - Parameters: /// - node: A node that contains items which may have semicolons or nested code blocks. /// - nodeCreator: A closure that creates a new node given an array of items. private func nodeByRemovingSemicolons< - ItemType: SyntaxProtocol & WithSemicolonSyntax & Equatable, NodeType: SyntaxCollection - >(from node: NodeType, nodeCreator: ([ItemType]) -> NodeType) -> NodeType - where NodeType.Element == ItemType { + ItemType: SyntaxProtocol & WithSemicolonSyntax & Equatable, + NodeType: SyntaxCollection + >(from node: NodeType) -> NodeType where NodeType.Element == ItemType { var newItems = Array(node) - // Because newlines belong to the _first_ token on the new line, if we remove a semicolon, we - // need to keep track of the fact that the next statement needs a new line. - var previousHadSemicolon = false - for (idx, item) in node.enumerated() { - - // Store the previous statement's semicolon-ness. - defer { previousHadSemicolon = item.semicolon != nil } + // Keeps track of trailing trivia after a semicolon when it needs to be moved to precede the + // next statement. + var pendingTrivia = Trivia() + for (idx, item) in node.enumerated() { // Check for semicolons in statements inside of the item, because code blocks may be nested // inside of other code blocks. - guard let visitedItem = rewrite(Syntax(item)).as(ItemType.self) else { + guard var newItem = rewrite(Syntax(item)).as(ItemType.self) else { return node } - // Check if we need to make any modifications (removing semicolon/adding newlines) - guard visitedItem != item || item.semicolon != nil || previousHadSemicolon else { + // Check if we need to make any modifications (removing semicolon/adding newlines). + guard newItem != item || item.semicolon != nil || !pendingTrivia.isEmpty else { continue } - var newItem = visitedItem - defer { newItems[idx] = newItem } - // Check if the leading trivia for this statement needs a new line. - if previousHadSemicolon, let firstToken = newItem.firstToken(viewMode: .sourceAccurate), - !firstToken.leadingTrivia.containsNewlines - { - newItem.leadingTrivia = .newlines(1) + firstToken.leadingTrivia + if !pendingTrivia.isEmpty { + newItem.leadingTrivia = pendingTrivia + newItem.leadingTrivia } + pendingTrivia = [] // If there's a semicolon, diagnose and remove it. - if let semicolon = item.semicolon { - // Exception: do not remove the semicolon if it is separating a 'do' statement from a - // 'while' statement. - if Syntax(item).as(CodeBlockItemSyntax.self)? - .children(viewMode: .sourceAccurate).first?.is(DoStmtSyntax.self) == true, - idx < node.count - 1, - let childrenIdx = node.index(of: item) - { - let children = node.children(viewMode: .sourceAccurate) - let nextItem = children[children.index(after: childrenIdx)] - if Syntax(nextItem).as(CodeBlockItemSyntax.self)? - .children(viewMode: .sourceAccurate).first?.is(WhileStmtSyntax.self) == true - { - continue - } - } - - // This discards any trailing trivia from the semicolon. That trivia will only be horizontal - // whitespace, and the pretty printer adds any necessary spaces so it's safe to discard. - // TODO: When we stop using the legacy trivia transform, we need to fix this to preserve - // trailing comments. - newItem.semicolon = nil - + // Exception: Do not remove the semicolon if it is separating a `do` statement from a `while` + // statement. + if let semicolon = item.semicolon, + !(idx < node.count - 1 + && isCodeBlockItem(item, containingStmtType: DoStmtSyntax.self) + && isCodeBlockItem(newItems[idx + 1], containingStmtType: WhileStmtSyntax.self)) + { // When emitting the finding, tell the user to move the next statement down if there is // another statement following this one. Otherwise, just tell them to remove the semicolon. + var hasNextStatement: Bool if let nextToken = semicolon.nextToken(viewMode: .sourceAccurate), nextToken.tokenKind != .rightBrace && nextToken.tokenKind != .endOfFile && !nextToken.leadingTrivia.containsNewlines { + hasNextStatement = true + pendingTrivia = [.newlines(1)] diagnose(.removeSemicolonAndMove, on: semicolon) } else { + hasNextStatement = false diagnose(.removeSemicolon, on: semicolon) } + + // We treat block comments after the semicolon slightly differently from end-of-line + // comments. Assume that an end-of-line comment should stay on the same line when a + // semicolon is removed, but if we have something like `f(); /* blah */ g()`, assume that + // the comment is meant to be associated with `g()` (because it's not separated from that + // statement). + let trailingTrivia = newItem.trailingTrivia + newItem.semicolon = nil + if trailingTrivia.hasLineComment || !hasNextStatement { + newItem.trailingTrivia = trailingTrivia + } else { + pendingTrivia += trailingTrivia.withoutLeadingSpaces() + } } + newItems[idx] = newItem } - return nodeCreator(newItems) + + return NodeType(newItems) } public override func visit(_ node: CodeBlockItemListSyntax) -> CodeBlockItemListSyntax { - return nodeByRemovingSemicolons(from: node, nodeCreator: CodeBlockItemListSyntax.init) + return nodeByRemovingSemicolons(from: node) } public override func visit(_ node: MemberBlockItemListSyntax) -> MemberBlockItemListSyntax { - return nodeByRemovingSemicolons(from: node, nodeCreator: MemberBlockItemListSyntax.init) + return nodeByRemovingSemicolons(from: node) + } + + /// Returns true if the given syntax node is a `CodeBlockItem` containing a statement node of the + /// given type. + private func isCodeBlockItem( + _ node: some SyntaxProtocol, + containingStmtType stmtType: StmtSyntaxProtocol.Type + ) -> Bool { + if let codeBlockItem = node.as(CodeBlockItemSyntax.self), + case .stmt(let stmt) = codeBlockItem.item, + stmt.is(stmtType) + { + return true + } + return false } } diff --git a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift index 0112a3db8..b51a950dd 100644 --- a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift @@ -58,11 +58,15 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { // Move the leading trivia from the `return` statement to the new assignment statement, // since that's a more sensible place than between the two. var assignmentItem = CodeBlockItemSyntax(item: .expr(ExprSyntax(assignmentExpr))) - assignmentItem.leadingTrivia = returnStmt.leadingTrivia + assignmentExpr.leadingTrivia + assignmentItem.leadingTrivia = + returnStmt.leadingTrivia + + returnStmt.returnKeyword.trailingTrivia.withoutLeadingSpaces() + + assignmentExpr.leadingTrivia assignmentItem.trailingTrivia = [] - let trailingTrivia = returnStmt.trailingTrivia.withoutLeadingSpaces() + let trailingTrivia = returnStmt.trailingTrivia returnStmt.expression = nil + returnStmt.returnKeyword.trailingTrivia = [] var returnItem = CodeBlockItemSyntax(item: .stmt(StmtSyntax(returnStmt))) returnItem.leadingTrivia = [.newlines(1)] returnItem.trailingTrivia = trailingTrivia diff --git a/Sources/SwiftFormat/Rules/NoBlockComments.swift b/Sources/SwiftFormat/Rules/NoBlockComments.swift index 0df718966..1f5f0647c 100644 --- a/Sources/SwiftFormat/Rules/NoBlockComments.swift +++ b/Sources/SwiftFormat/Rules/NoBlockComments.swift @@ -21,7 +21,13 @@ public final class NoBlockComments: SyntaxLintRule { for triviaIndex in token.leadingTrivia.indices { let piece = token.leadingTrivia[triviaIndex] if case .blockComment = piece { - diagnose(.avoidBlockComment, on: token, leadingTriviaIndex: triviaIndex) + diagnose(.avoidBlockComment, on: token, anchor: .leadingTrivia(triviaIndex)) + } + } + for triviaIndex in token.trailingTrivia.indices { + let piece = token.trailingTrivia[triviaIndex] + if case .blockComment = piece { + diagnose(.avoidBlockComment, on: token, anchor: .trailingTrivia(triviaIndex)) } } return .skipChildren diff --git a/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift index 2fd41572a..79bebd135 100644 --- a/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift +++ b/Sources/SwiftFormat/Rules/NoCasesWithOnlyFallthrough.swift @@ -140,19 +140,20 @@ public final class NoCasesWithOnlyFallthrough: SyntaxFormatRule { } // Check for any comments that are adjacent to the case or fallthrough statement. - if switchCase.leadingTrivia.drop(while: { !$0.isNewline }).contains(where: { $0.isComment }) + if switchCase.allPrecedingTrivia + .drop(while: { !$0.isNewline }).contains(where: { $0.isComment }) { return false } - if onlyStatement.leadingTrivia.drop(while: { !$0.isNewline }).contains(where: { $0.isComment }) + if onlyStatement.allPrecedingTrivia + .drop(while: { !$0.isNewline }).contains(where: { $0.isComment }) { return false } - // Check for any comments that are inline on the fallthrough statement. Inline comments are - // always stored in the next token's leading trivia. - if let nextLeadingTrivia = onlyStatement.nextToken(viewMode: .sourceAccurate)?.leadingTrivia, - nextLeadingTrivia.prefix(while: { !$0.isNewline }).contains(where: { $0.isComment }) + // Check for any comments that are inline on the fallthrough statement. + if onlyStatement.allFollowingTrivia + .prefix(while: { !$0.isNewline }).contains(where: { $0.isComment }) { return false } diff --git a/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift index da9a2493d..321e5948e 100644 --- a/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift +++ b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift @@ -103,8 +103,8 @@ private struct VariableDeclSplitter { /// as a `CodeBlockItemSyntax`, that wraps it. private let generator: (VariableDeclSyntax) -> Node - /// Bindings that have been collected so far. - private var bindingQueue = [PatternBindingSyntax]() + /// Bindings that have been collected so far and the trivia that preceded them. + private var bindingQueue = [(PatternBindingSyntax, Trivia)]() /// The variable declaration being split. /// @@ -134,6 +134,12 @@ private struct VariableDeclSplitter { self.varDecl = varDecl self.nodes = [] + // We keep track of trivia that precedes each binding (which is reflected as trailing trivia + // on the previous token) so that we can reassociate it if we flush the bindings out as + // individual variable decls. This means that we can rewrite `let /*a*/ a, /*b*/ b: Int` as + // `let /*a*/ a: Int; let /*b*/ b: Int`, for example. + var precedingTrivia = varDecl.bindingSpecifier.trailingTrivia + for binding in varDecl.bindings { if binding.initializer != nil { // If this is the only initializer in the queue so far, that's ok. If @@ -142,14 +148,15 @@ private struct VariableDeclSplitter { // them as a single decl. var newBinding = binding newBinding.trailingComma = nil - bindingQueue.append(newBinding) + bindingQueue.append((newBinding, precedingTrivia)) flushRemaining() } else if let typeAnnotation = binding.typeAnnotation { - bindingQueue.append(binding) + bindingQueue.append((binding, precedingTrivia)) flushIndividually(typeAnnotation: typeAnnotation) } else { - bindingQueue.append(binding) + bindingQueue.append((binding, precedingTrivia)) } + precedingTrivia = binding.trailingComma?.trailingTrivia ?? [] } flushRemaining() @@ -174,7 +181,7 @@ private struct VariableDeclSplitter { guard !bindingQueue.isEmpty else { return } var newDecl = varDecl! - newDecl.bindings = PatternBindingListSyntax(bindingQueue) + newDecl.bindings = PatternBindingListSyntax(bindingQueue.map(\.0)) nodes.append(generator(newDecl)) fixOriginalVarDeclTrivia() @@ -189,7 +196,7 @@ private struct VariableDeclSplitter { ) { assert(!bindingQueue.isEmpty) - for binding in bindingQueue { + for (binding, trailingTrivia) in bindingQueue { assert(binding.initializer == nil) var newBinding = binding @@ -197,6 +204,7 @@ private struct VariableDeclSplitter { newBinding.trailingComma = nil var newDecl = varDecl! + newDecl.bindingSpecifier.trailingTrivia = trailingTrivia newDecl.bindings = PatternBindingListSyntax([newBinding]) nodes.append(generator(newDecl)) diff --git a/Sources/SwiftFormat/Rules/OrderedImports.swift b/Sources/SwiftFormat/Rules/OrderedImports.swift index 67831ecec..901542d20 100644 --- a/Sources/SwiftFormat/Rules/OrderedImports.swift +++ b/Sources/SwiftFormat/Rules/OrderedImports.swift @@ -286,18 +286,13 @@ fileprivate func generateLines(codeBlockItemList: CodeBlockItemListSyntax, conte { var lines: [Line] = [] var currentLine = Line() - var afterNewline = false - var isFirstBlock = true func appendNewLine() { lines.append(currentLine) currentLine = Line() - afterNewline = true // Note: trailing line comments always come before any newlines. } for block in codeBlockItemList { - afterNewline = false - for piece in block.leadingTrivia { switch piece { // Create new Line objects when we encounter newlines. @@ -306,22 +301,19 @@ fileprivate func generateLines(codeBlockItemList: CodeBlockItemListSyntax, conte appendNewLine() } default: - if afterNewline || isFirstBlock { - currentLine.leadingTrivia.append(piece) // This will be a standalone comment. - } else { - currentLine.trailingTrivia.append(piece) // This will be a trailing line comment. - } + currentLine.leadingTrivia.append(piece) // This will be a standalone comment. } } if block.item.is(ImportDeclSyntax.self) { // Always create a new `Line` for each import statement, so they can be reordered. if currentLine.syntaxNode != nil { - lines.append(currentLine) - currentLine = Line() + appendNewLine() } let sortable = context.isRuleEnabled(OrderedImports.self, node: Syntax(block)) - currentLine.syntaxNode = .importCodeBlock(block, sortable: sortable) + var blockWithoutTrailingTrivia = block + blockWithoutTrailingTrivia.trailingTrivia = [] + currentLine.syntaxNode = .importCodeBlock(blockWithoutTrailingTrivia, sortable: sortable) } else { guard let syntaxNode = currentLine.syntaxNode else { currentLine.syntaxNode = .nonImportCodeBlocks([block]) @@ -330,18 +322,19 @@ fileprivate func generateLines(codeBlockItemList: CodeBlockItemListSyntax, conte // Multiple code blocks can be merged, as long as there isn't an import statement. switch syntaxNode { case .importCodeBlock: - lines.append(currentLine) - currentLine = Line() + appendNewLine() currentLine.syntaxNode = .nonImportCodeBlocks([block]) case .nonImportCodeBlocks(let existingCodeBlocks): currentLine.syntaxNode = .nonImportCodeBlocks(existingCodeBlocks + [block]) } } - isFirstBlock = false + for piece in block.trailingTrivia { + currentLine.trailingTrivia.append(piece) // This will be a trailing line comment. + } } - lines.append(currentLine) + lines.append(currentLine) return lines } diff --git a/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift b/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift index 5d64b10ed..319c478eb 100644 --- a/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift +++ b/Sources/SwiftFormat/Rules/UseTripleSlashForDocumentationComments.swift @@ -80,7 +80,7 @@ public final class UseTripleSlashForDocumentationComments: SyntaxFormatRule { return decl } - diagnose(.avoidDocBlockComment, on: decl, leadingTriviaIndex: commentInfo.startIndex) + diagnose(.avoidDocBlockComment, on: decl, anchor: .leadingTrivia(commentInfo.startIndex)) // Keep any trivia leading up to the doc comment. var pieces = Array(decl.leadingTrivia[.. (String, Context) { // Ignore folding errors for unrecognized operators so that we fallback to a reasonable default. let sourceFileSyntax = - restoringLegacyTriviaBehavior( - OperatorTable.standardOperators.foldAll(Parser.parse(source: source)) { _ in } - .as(SourceFileSyntax.self)!) + OperatorTable.standardOperators.foldAll(Parser.parse(source: source)) { _ in } + .as(SourceFileSyntax.self)! let context = makeContext( sourceFileSyntax: sourceFileSyntax, configuration: configuration, diff --git a/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift b/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift index a7b08fe19..55a174d3d 100644 --- a/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift +++ b/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift @@ -34,8 +34,8 @@ final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { // printer and isn't a concern for the format rule. expected: """ guard let someVar = Optional(items.filter ({ a in foo(a) - return true})) else { - items.forEach { a in foo(a)} + return true })) else { + items.forEach { a in foo(a) } return } """, @@ -106,7 +106,7 @@ final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { print("4") /** Inline comment. */ print("5") - print("6")// This is an important statement. + print("6") // This is an important statement. print("7") """, findings: [ @@ -121,6 +121,71 @@ final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { ) } + func testBlockCommentAtEndOfBlock() { + assertFormatting( + DoNotUseSemicolons.self, + input: """ + print("hello")1️⃣; /* block comment */ + """, + expected: """ + print("hello") /* block comment */ + """, + findings: [ + FindingSpec("1️⃣", message: "remove ';'"), + ] + ) + + assertFormatting( + DoNotUseSemicolons.self, + input: """ + if x { + print("hello")1️⃣; /* block comment */ + } + """, + expected: """ + if x { + print("hello") /* block comment */ + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove ';'"), + ] + ) + } + + func testBlockCommentAfterSemicolonPrecedingOtherStatement() { + assertFormatting( + DoNotUseSemicolons.self, + input: """ + print("hello")1️⃣; /* block comment */ print("world") + """, + expected: """ + print("hello") + /* block comment */ print("world") + """, + findings: [ + FindingSpec("1️⃣", message: "remove ';' and move the next statement to a new line"), + ] + ) + + assertFormatting( + DoNotUseSemicolons.self, + input: """ + if x { + print("hello")1️⃣; /* block comment */ + } + """, + expected: """ + if x { + print("hello") /* block comment */ + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove ';'"), + ] + ) + } + func testSemicolonsSeparatingDoWhile() { assertFormatting( DoNotUseSemicolons.self, diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index cb83fa621..e9c342f97 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -28,8 +28,8 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { ) { let markedText = MarkedText(textWithMarkers: markedSource) let tree = Parser.parse(source: markedText.textWithoutMarkers) - let sourceFileSyntax = try! restoringLegacyTriviaBehavior( - OperatorTable.standardOperators.foldAll(tree).as(SourceFileSyntax.self)!) + let sourceFileSyntax = + try! OperatorTable.standardOperators.foldAll(tree).as(SourceFileSyntax.self)! var emittedFindings = [Finding]() @@ -76,8 +76,8 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { ) { let markedInput = MarkedText(textWithMarkers: input) let tree = Parser.parse(source: markedInput.textWithoutMarkers) - let sourceFileSyntax = try! restoringLegacyTriviaBehavior( - OperatorTable.standardOperators.foldAll(tree).as(SourceFileSyntax.self)!) + let sourceFileSyntax = + try! OperatorTable.standardOperators.foldAll(tree).as(SourceFileSyntax.self)! var emittedFindings = [Finding]() From 7d75e3a16fd2159e7f7058f565336069ad60dc6f Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 3 Oct 2023 14:45:33 -0400 Subject: [PATCH 159/332] Add parentheses when needed to convert `let _ = expr` to `expr != nil`. Parens are required if `expr` is a `try` expression, a ternary expression, or an infix operator expression whose operator has lower precedence than `!=`. --- .../UseExplicitNilCheckInConditions.swift | 50 ++++++++++++++++- ...UseExplicitNilCheckInConditionsTests.swift | 54 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift index a0b036fd3..2ff1ceee8 100644 --- a/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift +++ b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift @@ -47,7 +47,7 @@ public final class UseExplicitNilCheckInConditions: SyntaxFormatRule { operatorExpr.trailingTrivia = [.spaces(1)] var inequalExpr = InfixOperatorExprSyntax( - leftOperand: value, + leftOperand: addingParenthesesIfNecessary(to: value), operator: operatorExpr, rightOperand: NilLiteralExprSyntax()) inequalExpr.leadingTrivia = node.leadingTrivia @@ -69,6 +69,54 @@ public final class UseExplicitNilCheckInConditions: SyntaxFormatRule { } return exprPattern.expression.is(DiscardAssignmentExprSyntax.self) } + + /// Adds parentheses around the given expression if necessary to ensure that it will be parsed + /// correctly when followed by `!= nil`. + /// + /// Specifically, if `expr` is a `try` expression, ternary expression, or an infix operator with + /// the same or lower precedence, we wrap it. + private func addingParenthesesIfNecessary(to expr: ExprSyntax) -> ExprSyntax { + func addingParentheses(to expr: ExprSyntax) -> ExprSyntax { + var expr = expr + let leadingTrivia = expr.leadingTrivia + let trailingTrivia = expr.trailingTrivia + expr.leadingTrivia = [] + expr.trailingTrivia = [] + + var tupleExpr = TupleExprSyntax(elements: [LabeledExprSyntax(expression: expr)]) + tupleExpr.leadingTrivia = leadingTrivia + tupleExpr.trailingTrivia = trailingTrivia + return ExprSyntax(tupleExpr) + } + + switch Syntax(expr).as(SyntaxEnum.self) { + case .tryExpr, .ternaryExpr: + return addingParentheses(to: expr) + + case .infixOperatorExpr: + // There's no public API in SwiftSyntax to get the relationship between two precedence groups. + // Until that exists, here's a workaround I'm only mildly ashamed of: we reparse + // "\(expr) != nil" and then fold it. If the top-level node is anything but an + // `InfixOperatorExpr` whose operator is `!=` and whose RHS is `nil`, then it parsed + // incorrectly and we need to add parentheses around `expr`. + // + // Note that we could also cover the `tryExpr` and `ternaryExpr` cases above with this, but + // this reparsing trick is going to be slower so we should avoid it whenever we can. + let reparsedExpr = "\(expr) != nil" as ExprSyntax + if + let infixExpr = reparsedExpr.as(InfixOperatorExprSyntax.self), + let binOp = infixExpr.operator.as(BinaryOperatorExprSyntax.self), + binOp.operator.text == "!=", + infixExpr.rightOperand.is(NilLiteralExprSyntax.self) + { + return expr + } + return addingParentheses(to: expr) + + default: + return expr + } + } } extension Finding.Message { diff --git a/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift b/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift index f8f63c3a5..d15c6acfa 100644 --- a/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift @@ -95,4 +95,58 @@ final class UseExplicitNilCheckInConditionsTests: LintOrFormatRuleTestCase { ] ) } + + func testAddNecessaryParenthesesAroundTryExpr() { + assertFormatting( + UseExplicitNilCheckInConditions.self, + input: """ + if 1️⃣let _ = try? x {} + if 2️⃣let _ = try x {} + """, + expected: """ + if (try? x) != nil {} + if (try x) != nil {} + """, + findings: [ + FindingSpec("1️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + FindingSpec("2️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + ] + ) + } + + func testAddNecessaryParenthesesAroundTernaryExpr() { + assertFormatting( + UseExplicitNilCheckInConditions.self, + input: """ + if 1️⃣let _ = x ? y : z {} + """, + expected: """ + if (x ? y : z) != nil {} + """, + findings: [ + FindingSpec("1️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + ] + ) + } + + func testAddNecessaryParenthesesAroundSameOrLowerPrecedenceOperator() { + // The use of `&&` and `==` are semantically meaningless here because they don't return + // optionals. We just need them to stand in for any potential custom operator with lower or same + // precedence, respectively. + assertFormatting( + UseExplicitNilCheckInConditions.self, + input: """ + if 1️⃣let _ = x && y {} + if 2️⃣let _ = x == y {} + """, + expected: """ + if (x && y) != nil {} + if (x == y) != nil {} + """, + findings: [ + FindingSpec("1️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + FindingSpec("2️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + ] + ) + } } From 05ee7e63e197986841a9df231f4798763b2d181e Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 11 Oct 2023 16:29:41 -0400 Subject: [PATCH 160/332] `// swift-format-ignore-file` shouldn't modify anything, even whitespace. --- .../PrettyPrint/TokenStreamCreator.swift | 2 +- .../PrettyPrint/IgnoreNodeTests.swift | 34 +++++-------------- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index ea1f955ec..0137e3a2e 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -1516,7 +1516,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { if shouldFormatterIgnore(file: node) { - appendFormatterIgnored(node: Syntax(node)) + appendToken(.verbatim(Verbatim(text: "\(node)", indentingBehavior: .none))) return .skipChildren } after(node.shebang, tokens: .break(.same, newlines: .soft)) diff --git a/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift index 2537623ba..4e51f4de5 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift @@ -318,36 +318,18 @@ final class IgnoreNodeTests: PrettyPrintTestCase { { var bazzle = 0 } """ + assertPrettyPrintEqual(input: input, expected: input, linelength: 50) + } - let expected = + func testIgnoreWholeFileDoesNotTouchWhitespace() { + let input = """ // swift-format-ignore-file - import Zoo - import Aoo - import foo - - struct Foo { - private var baz: Bool { - return foo + - bar + // poorly placed comment - false - } - - var a = true // line comment - // aligned line comment - var b = false // correct trailing comment - - var c = 0 + - 1 - + (2 + 3) - } - - class Bar - { - var bazzle = 0 } + /// foo bar + \u{0020} + // baz """ - - assertPrettyPrintEqual(input: input, expected: expected, linelength: 50) + assertPrettyPrintEqual(input: input, expected: input, linelength: 100) } func testIgnoreWholeFileInNestedNode() { From ec39c44634b1ec66a68fa115496a1d3e1a852161 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Wed, 11 Oct 2023 16:30:20 -0400 Subject: [PATCH 161/332] NoParensAroundConditions: Preserve trailing trivia on the statement keyword. After removing the legacy trivia workaround, we were dropping trailing trivia on a conditional statement keyword; for example, the comment would be lost here: ```swift guard // here's a comment let x = x else {} ``` --- .../Rules/NoParensAroundConditions.swift | 15 ++-- .../Rules/NoParensAroundConditionsTests.swift | 71 +++++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift index 51f8377cd..7c9f1dc9d 100644 --- a/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift +++ b/Sources/SwiftFormat/Rules/NoParensAroundConditions.swift @@ -28,7 +28,7 @@ import SwiftSyntax public final class NoParensAroundConditions: SyntaxFormatRule { public override func visit(_ node: IfExprSyntax) -> ExprSyntax { var result = node - result.ifKeyword.trailingTrivia = [.spaces(1)] + fixKeywordTrailingTrivia(&result.ifKeyword.trailingTrivia) result.conditions = visit(node.conditions) result.body = visit(node.body) if let elseBody = node.elseBody { @@ -52,7 +52,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { public override func visit(_ node: GuardStmtSyntax) -> StmtSyntax { var result = node - result.guardKeyword.trailingTrivia = [.spaces(1)] + fixKeywordTrailingTrivia(&result.guardKeyword.trailingTrivia) result.conditions = visit(node.conditions) result.body = visit(node.body) return StmtSyntax(result) @@ -64,7 +64,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { } var result = node - result.switchKeyword.trailingTrivia = [.spaces(1)] + fixKeywordTrailingTrivia(&result.switchKeyword.trailingTrivia) result.subject = newSubject result.cases = visit(node.cases) return ExprSyntax(result) @@ -76,7 +76,7 @@ public final class NoParensAroundConditions: SyntaxFormatRule { } var result = node - result.whileKeyword.trailingTrivia = [.spaces(1)] + fixKeywordTrailingTrivia(&result.whileKeyword.trailingTrivia) result.condition = newCondition result.body = visit(node.body) return StmtSyntax(result) @@ -84,12 +84,17 @@ public final class NoParensAroundConditions: SyntaxFormatRule { public override func visit(_ node: WhileStmtSyntax) -> StmtSyntax { var result = node - result.whileKeyword.trailingTrivia = [.spaces(1)] + fixKeywordTrailingTrivia(&result.whileKeyword.trailingTrivia) result.conditions = visit(node.conditions) result.body = visit(node.body) return StmtSyntax(result) } + private func fixKeywordTrailingTrivia(_ trivia: inout Trivia) { + guard trivia.isEmpty else { return } + trivia = [.spaces(1)] + } + private func minimalSingleExpression(_ original: ExprSyntax) -> ExprSyntax? { guard let tuple = original.as(TupleExprSyntax.self), diff --git a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift index 770f2906d..7f87043ed 100644 --- a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift @@ -263,6 +263,77 @@ final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { FindingSpec("7️⃣", message: "remove the parentheses around this expression"), ] ) + } + func testBlockCommentsBeforeConditionArePreserved() { + assertFormatting( + NoParensAroundConditions.self, + input: """ + if/*foo*/1️⃣(x) {} + while/*foo*/2️⃣(x) {} + guard/*foo*/3️⃣(x), /*foo*/4️⃣(y), /*foo*/5️⃣(x == 3) else {} + repeat {} while/*foo*/6️⃣(x) + switch/*foo*/7️⃣(4) { default: break } + """, + expected: """ + if/*foo*/x {} + while/*foo*/x {} + guard/*foo*/x, /*foo*/y, /*foo*/x == 3 else {} + repeat {} while/*foo*/x + switch/*foo*/4 { default: break } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the parentheses around this expression"), + FindingSpec("2️⃣", message: "remove the parentheses around this expression"), + FindingSpec("3️⃣", message: "remove the parentheses around this expression"), + FindingSpec("4️⃣", message: "remove the parentheses around this expression"), + FindingSpec("5️⃣", message: "remove the parentheses around this expression"), + FindingSpec("6️⃣", message: "remove the parentheses around this expression"), + FindingSpec("7️⃣", message: "remove the parentheses around this expression"), + ] + ) + } + + func testCommentsAfterKeywordArePreserved() { + assertFormatting( + NoParensAroundConditions.self, + input: """ + if /*foo*/ // bar + 1️⃣(x) {} + while /*foo*/ // bar + 2️⃣(x) {} + guard /*foo*/ // bar + 3️⃣(x), /*foo*/ // bar + 4️⃣(y), /*foo*/ // bar + 5️⃣(x == 3) else {} + repeat {} while /*foo*/ // bar + 6️⃣(x) + switch /*foo*/ // bar + 7️⃣(4) { default: break } + """, + expected: """ + if /*foo*/ // bar + x {} + while /*foo*/ // bar + x {} + guard /*foo*/ // bar + x, /*foo*/ // bar + y, /*foo*/ // bar + x == 3 else {} + repeat {} while /*foo*/ // bar + x + switch /*foo*/ // bar + 4 { default: break } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the parentheses around this expression"), + FindingSpec("2️⃣", message: "remove the parentheses around this expression"), + FindingSpec("3️⃣", message: "remove the parentheses around this expression"), + FindingSpec("4️⃣", message: "remove the parentheses around this expression"), + FindingSpec("5️⃣", message: "remove the parentheses around this expression"), + FindingSpec("6️⃣", message: "remove the parentheses around this expression"), + FindingSpec("7️⃣", message: "remove the parentheses around this expression"), + ] + ) } } From f9b2ab9f31143ceb53ca3c1a5cc286ba9018361a Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 12 Oct 2023 08:59:02 -0400 Subject: [PATCH 162/332] OrderedImports: Fix trivia placement for trailing comments. Trailing comments were being appended to the next token's leading trivia instead of staying as trailing trivia; I forgot to make this adjustment when removing the legacy trivia workaround. Interestingly, this didn't surface as a problem in either the `OrderedImports` rule's unit tests or the pretty printer unit tests. Only the interaction between the two would surface the error. --- .../SwiftFormat/Rules/OrderedImports.swift | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftFormat/Rules/OrderedImports.swift b/Sources/SwiftFormat/Rules/OrderedImports.swift index 901542d20..1a1463197 100644 --- a/Sources/SwiftFormat/Rules/OrderedImports.swift +++ b/Sources/SwiftFormat/Rules/OrderedImports.swift @@ -342,20 +342,18 @@ fileprivate func generateLines(codeBlockItemList: CodeBlockItemListSyntax, conte /// replacing the trivia appropriately to ensure comments appear in the right location. fileprivate func convertToCodeBlockItems(lines: [Line]) -> [CodeBlockItemSyntax] { var output: [CodeBlockItemSyntax] = [] - var triviaBuffer: [TriviaPiece] = [] + var pendingLeadingTrivia: [TriviaPiece] = [] for line in lines { - triviaBuffer += line.leadingTrivia + pendingLeadingTrivia += line.leadingTrivia func append(codeBlockItem: CodeBlockItemSyntax) { - // Comments and newlines are always located in the leading trivia of an AST node, so we need - // not deal with trailing trivia. var codeBlockItem = codeBlockItem - codeBlockItem.leadingTrivia = Trivia(pieces: triviaBuffer) + codeBlockItem.leadingTrivia = Trivia(pieces: pendingLeadingTrivia) + codeBlockItem.trailingTrivia = Trivia(pieces: line.trailingTrivia) output.append(codeBlockItem) - triviaBuffer = [] - triviaBuffer += line.trailingTrivia + pendingLeadingTrivia = [] } if let syntaxNode = line.syntaxNode { @@ -367,11 +365,11 @@ fileprivate func convertToCodeBlockItems(lines: [Line]) -> [CodeBlockItemSyntax] } } - // Merge multiple newlines together into a single trivia piece by updating it's N value. - if let lastPiece = triviaBuffer.last, case .newlines(let N) = lastPiece { - triviaBuffer[triviaBuffer.endIndex - 1] = TriviaPiece.newlines(N + 1) + // Merge multiple newlines together into a single trivia piece by updating its count. + if let lastPiece = pendingLeadingTrivia.last, case .newlines(let count) = lastPiece { + pendingLeadingTrivia[pendingLeadingTrivia.endIndex - 1] = .newlines(count + 1) } else { - triviaBuffer.append(TriviaPiece.newlines(1)) + pendingLeadingTrivia.append(.newlines(1)) } } From 84d63b7e5a12d03392daa36c258324a4c06dd547 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 12 Oct 2023 16:53:03 -0400 Subject: [PATCH 163/332] Some minor frontend tweaks. - Switch to `@main` on the top-level command. - Move the function that prints the version information into its own file, so it can be swapped out with something else for custom builds. (It would be nice to automate this similar to what swift-testing is doing with their GitPlugin, but I don't want to steal that code verbatim just yet.) - Make the version string on the main branch "main" instead of an older value that just happened to stick around. In the future, this should always be "main" and it can be updated on the actual release branches (or automated ideally). --- Sources/swift-format/PrintVersion.swift | 16 ++++++++++++++++ .../{main.swift => SwiftFormatCommand.swift} | 3 +-- Sources/swift-format/VersionOptions.swift | 3 +-- 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 Sources/swift-format/PrintVersion.swift rename Sources/swift-format/{main.swift => SwiftFormatCommand.swift} (97%) diff --git a/Sources/swift-format/PrintVersion.swift b/Sources/swift-format/PrintVersion.swift new file mode 100644 index 000000000..15b90e48a --- /dev/null +++ b/Sources/swift-format/PrintVersion.swift @@ -0,0 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +func printVersionInformation() { + // TODO: Automate updates to this somehow. + print("main") +} diff --git a/Sources/swift-format/main.swift b/Sources/swift-format/SwiftFormatCommand.swift similarity index 97% rename from Sources/swift-format/main.swift rename to Sources/swift-format/SwiftFormatCommand.swift index 4d0a2deaf..5b814a159 100644 --- a/Sources/swift-format/main.swift +++ b/Sources/swift-format/SwiftFormatCommand.swift @@ -14,6 +14,7 @@ import ArgumentParser /// Collects the command line options that were passed to `swift-format` and dispatches to the /// appropriate subcommand. +@main struct SwiftFormatCommand: ParsableCommand { static var configuration = CommandConfiguration( commandName: "swift-format", @@ -29,5 +30,3 @@ struct SwiftFormatCommand: ParsableCommand { @OptionGroup() var versionOptions: VersionOptions } - -SwiftFormatCommand.main() diff --git a/Sources/swift-format/VersionOptions.swift b/Sources/swift-format/VersionOptions.swift index 84a420181..21afa50b8 100644 --- a/Sources/swift-format/VersionOptions.swift +++ b/Sources/swift-format/VersionOptions.swift @@ -19,8 +19,7 @@ struct VersionOptions: ParsableArguments { func validate() throws { if version { - // TODO: Automate updates to this somehow. - print("508.0.0") + printVersionInformation() throw ExitCode.success } } From 1e19a50b7c4b06b1941c85b8821c399420a68e7e Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 19 Oct 2023 11:58:27 -0400 Subject: [PATCH 164/332] [AllPublicDeclarationsHaveDocumentation] Fix behavior for `override` methods. This was mistakenly broken during a refactor and we didn't have test coverage for it. We didn't have test coverage for it because the tests behave differently than the rule does when executed in the whole pipeline, so we need to fix that too. I'm going to do that in a separate PR, because it might have knock-on effects elsewhere. Fixes #651. --- .../Rules/AllPublicDeclarationsHaveDocumentation.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift index 2ca2235b5..75d619539 100644 --- a/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift +++ b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift @@ -77,7 +77,8 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { ) { guard DocumentationCommentText(extractedFrom: decl.leadingTrivia) == nil, - modifiers.contains(anyOf: [.public, .override]) + modifiers.contains(anyOf: [.public]), + !modifiers.contains(anyOf: [.override]) else { return } From 9626e004e7cdb1327118e75dcf31dadfbf885c72 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 19 Oct 2023 13:13:37 -0400 Subject: [PATCH 165/332] Fix multi-line string wrapping in `@available` attributes. --- .../PrettyPrint/TokenStreamCreator.swift | 29 +++++++++- .../PrettyPrint/AttributeTests.swift | 54 +++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 0137e3a2e..85ccb71d0 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -1801,8 +1801,23 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AvailabilityLabeledArgumentSyntax) -> SyntaxVisitorContinueKind { before(node.label, tokens: .open) - after(node.colon, tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) - after(node.value.lastToken(viewMode: .sourceAccurate), tokens: .close) + + let tokensAfterColon: [Token] + let endTokens: [Token] + + if case .string(let string) = node.value, + string.openingQuote.tokenKind == .multilineStringQuote + { + tokensAfterColon = + [.break(.open(kind: .block), newlines: .elective(ignoresDiscretionary: true))] + endTokens = [.break(.close(mustBreak: false), size: 0), .close] + } else { + tokensAfterColon = [.break(.continue, newlines: .elective(ignoresDiscretionary: true))] + endTokens = [.close] + } + + after(node.colon, tokens: tokensAfterColon) + after(node.value.lastToken(viewMode: .sourceAccurate), tokens: endTokens) return .visitChildren } @@ -2382,6 +2397,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: SimpleStringLiteralExprSyntax) -> SyntaxVisitorContinueKind { + if node.openingQuote.tokenKind == .multilineStringQuote { + after(node.openingQuote, tokens: .break(.same, size: 0, newlines: .hard(count: 1))) + if !node.segments.isEmpty { + before(node.closingQuote, tokens: .break(.same, newlines: .hard(count: 1))) + } + } + return .visitChildren + } + override func visit(_ node: StringSegmentSyntax) -> SyntaxVisitorContinueKind { // Looks up the correct break kind based on prior context. func breakKind() -> BreakKind { diff --git a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift index 57a12a3a7..06aa46c8f 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift @@ -356,4 +356,58 @@ final class AttributeTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 32) } + + func testMultilineStringLiteralInCustomAttribute() { + let input = + #""" + @CustomAttribute(message: """ + This is a + multiline + string + """) + public func f() {} + """# + + let expected = + #""" + @CustomAttribute( + message: """ + This is a + multiline + string + """) + public func f() {} + + """# + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 100) + } + + func testMultilineStringLiteralInAvailableAttribute() { + let input = + #""" + @available(*, deprecated, message: """ + This is a + multiline + string + """) + public func f() {} + """# + + let expected = + #""" + @available( + *, deprecated, + message: """ + This is a + multiline + string + """ + ) + public func f() {} + + """# + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 100) + } } From c6386ad3b7da118d5123cb4dcffb1c70dc2d1da7 Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Wed, 25 Oct 2023 11:30:37 -0700 Subject: [PATCH 166/332] Disable calling rules when they return `.skipChildren`. Add a map `shouldSkipChildren` that tracks which rules are currently skipping children. Install handlers in visitPost for each rule that determine when we should start visiting children again (when we leave the node we're skipping). --- Sources/SwiftFormat/Core/LintPipeline.swift | 24 +- .../Core/Pipelines+Generated.swift | 224 ++++++++++++++++++ .../PipelineGenerator.swift | 23 ++ 3 files changed, 270 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/Core/LintPipeline.swift b/Sources/SwiftFormat/Core/LintPipeline.swift index 94e02e6cf..3eb10072d 100644 --- a/Sources/SwiftFormat/Core/LintPipeline.swift +++ b/Sources/SwiftFormat/Core/LintPipeline.swift @@ -29,8 +29,13 @@ extension LintPipeline { _ visitor: (Rule) -> (Node) -> SyntaxVisitorContinueKind, for node: Node ) { guard context.isRuleEnabled(Rule.self, node: Syntax(node)) else { return } + let ruleId = ObjectIdentifier(Rule.self) + guard self.shouldSkipChildren[ruleId] == nil else { return } let rule = self.rule(Rule.self) - _ = visitor(rule)(node) + let continueKind = visitor(rule)(node) + if case .skipChildren = continueKind { + self.shouldSkipChildren[ruleId] = node + } } /// Calls the `visit` method of a rule for the given node if that rule is enabled for the node. @@ -50,10 +55,27 @@ extension LintPipeline { // cannot currently be expressed as constraints without duplicating this function for each of // them individually. guard context.isRuleEnabled(Rule.self, node: Syntax(node)) else { return } + guard self.shouldSkipChildren[ObjectIdentifier(Rule.self)] == nil else { return } let rule = self.rule(Rule.self) _ = visitor(rule)(node) } + /// Cleans up any state associated with `rule` when we leave syntax node `node` + /// + /// - Parameters: + /// - rule: The type of the syntax rule we're cleaning up. + /// - node: The syntax node htat our traversal has left. + func onVisitPost( + rule: R.Type, for node: Node + ) { + let rule = ObjectIdentifier(rule) + if case .some(let skipNode) = self.shouldSkipChildren[rule] { + if node.id == skipNode.id { + self.shouldSkipChildren.removeValue(forKey: rule) + } + } + } + /// Retrieves an instance of a lint or format rule based on its type. /// /// There is at most 1 instance of each rule allocated per `LintPipeline`. This method will diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 6e9615823..9707ee80f 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -26,6 +26,10 @@ class LintPipeline: SyntaxVisitor { /// class type. var ruleCache = [ObjectIdentifier: Rule]() + /// Rules present in this dictionary skip visiting children until they leave the + /// syntax node stored as their value + var shouldSkipChildren = [ObjectIdentifier: SyntaxProtocol]() + /// Creates a new lint pipeline. init(context: Context) { self.context = context @@ -36,11 +40,17 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) return .visitChildren } + override func visitPost(_ node: ActorDeclSyntax) { + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + } override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } + override func visitPost(_ node: AsExprSyntax) { + onVisitPost(rule: NeverForceUnwrap.self, for: node) + } override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) @@ -48,6 +58,11 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) return .visitChildren } + override func visitPost(_ node: AssociatedTypeDeclSyntax) { + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -59,22 +74,41 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: ClassDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(OmitExplicitReturns.visit, for: node) return .visitChildren } + override func visitPost(_ node: ClosureExprSyntax) { + onVisitPost(rule: OmitExplicitReturns.self, for: node) + } override func visit(_ node: ClosureParameterSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: ClosureParameterSyntax) { + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: ClosureSignatureSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) visitIfEnabled(ReturnVoidInsteadOfEmptyTuple.visit, for: node) return .visitChildren } + override func visitPost(_ node: ClosureSignatureSyntax) { + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: ReturnVoidInsteadOfEmptyTuple.self, for: node) + } override func visit(_ node: CodeBlockItemListSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(DoNotUseSemicolons.visit, for: node) @@ -83,17 +117,30 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseEarlyExits.visit, for: node) return .visitChildren } + override func visitPost(_ node: CodeBlockItemListSyntax) { + onVisitPost(rule: DoNotUseSemicolons.self, for: node) + onVisitPost(rule: NoAssignmentInExpressions.self, for: node) + onVisitPost(rule: OneVariableDeclarationPerLine.self, for: node) + onVisitPost(rule: UseEarlyExits.self, for: node) + } override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) return .visitChildren } + override func visitPost(_ node: CodeBlockSyntax) { + onVisitPost(rule: AmbiguousTrailingClosureOverload.self, for: node) + } override func visit(_ node: ConditionElementSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) visitIfEnabled(UseExplicitNilCheckInConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: ConditionElementSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + onVisitPost(rule: UseExplicitNilCheckInConditions.self, for: node) + } override func visit(_ node: DeinitializerDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -101,17 +148,29 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: DeinitializerDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: EnumCaseElementSyntax) { + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: EnumCaseParameterSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: EnumCaseParameterSyntax) { + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) @@ -123,6 +182,15 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: EnumDeclSyntax) { + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) + onVisitPost(rule: FullyIndirectEnum.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: OneCasePerLine.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) @@ -130,16 +198,27 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: ExtensionDeclSyntax) { + onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) + onVisitPost(rule: NoAccessLevelOnExtensionDeclaration.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: ForStmtSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(UseWhereClausesInForLoops.visit, for: node) return .visitChildren } + override func visitPost(_ node: ForStmtSyntax) { + onVisitPost(rule: UseWhereClausesInForLoops.self, for: node) + } override func visit(_ node: ForceUnwrapExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } + override func visitPost(_ node: ForceUnwrapExprSyntax) { + onVisitPost(rule: NeverForceUnwrap.self, for: node) + } override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoEmptyTrailingClosureParentheses.visit, for: node) @@ -147,6 +226,11 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(ReplaceForEachWithForLoop.visit, for: node) return .visitChildren } + override func visitPost(_ node: FunctionCallExprSyntax) { + onVisitPost(rule: NoEmptyTrailingClosureParentheses.self, for: node) + onVisitPost(rule: OnlyOneTrailingClosureArgument.self, for: node) + onVisitPost(rule: ReplaceForEachWithForLoop.self, for: node) + } override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -158,58 +242,99 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(ValidateDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: FunctionDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: OmitExplicitReturns.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + onVisitPost(rule: ValidateDocumentationComments.self, for: node) + } override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLiteralForEmptyCollectionInit.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: FunctionParameterSyntax) { + onVisitPost(rule: AlwaysUseLiteralForEmptyCollectionInit.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: FunctionSignatureSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoVoidReturnOnFunctionSignature.visit, for: node) return .visitChildren } + override func visitPost(_ node: FunctionSignatureSyntax) { + onVisitPost(rule: NoVoidReturnOnFunctionSignature.self, for: node) + } override func visit(_ node: FunctionTypeSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(ReturnVoidInsteadOfEmptyTuple.visit, for: node) return .visitChildren } + override func visitPost(_ node: FunctionTypeSyntax) { + onVisitPost(rule: ReturnVoidInsteadOfEmptyTuple.self, for: node) + } override func visit(_ node: GenericParameterSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: GenericParameterSyntax) { + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: GenericSpecializationExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(UseShorthandTypeNames.visit, for: node) return .visitChildren } + override func visitPost(_ node: GenericSpecializationExprSyntax) { + onVisitPost(rule: UseShorthandTypeNames.self, for: node) + } override func visit(_ node: GuardStmtSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: GuardStmtSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + } override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(IdentifiersMustBeASCII.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: IdentifierPatternSyntax) { + onVisitPost(rule: IdentifiersMustBeASCII.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: IdentifierTypeSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(UseShorthandTypeNames.visit, for: node) return .visitChildren } + override func visitPost(_ node: IdentifierTypeSyntax) { + onVisitPost(rule: UseShorthandTypeNames.self, for: node) + } override func visit(_ node: IfExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: IfExprSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + } override func visit(_ node: InfixOperatorExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoAssignmentInExpressions.visit, for: node) return .visitChildren } + override func visitPost(_ node: InfixOperatorExprSyntax) { + onVisitPost(rule: NoAssignmentInExpressions.self, for: node) + } override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -218,31 +343,52 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(ValidateDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: InitializerDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + onVisitPost(rule: ValidateDocumentationComments.self, for: node) + } override func visit(_ node: IntegerLiteralExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(GroupNumericLiterals.visit, for: node) return .visitChildren } + override func visitPost(_ node: IntegerLiteralExprSyntax) { + onVisitPost(rule: GroupNumericLiterals.self, for: node) + } override func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoPlaygroundLiterals.visit, for: node) return .visitChildren } + override func visitPost(_ node: MacroExpansionExprSyntax) { + onVisitPost(rule: NoPlaygroundLiterals.self, for: node) + } override func visit(_ node: MemberBlockItemListSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(DoNotUseSemicolons.visit, for: node) return .visitChildren } + override func visitPost(_ node: MemberBlockItemListSyntax) { + onVisitPost(rule: DoNotUseSemicolons.self, for: node) + } override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) return .visitChildren } + override func visitPost(_ node: MemberBlockSyntax) { + onVisitPost(rule: AmbiguousTrailingClosureOverload.self, for: node) + } override func visit(_ node: OptionalBindingConditionSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) return .visitChildren } + override func visitPost(_ node: OptionalBindingConditionSyntax) { + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + } override func visit(_ node: PatternBindingSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLiteralForEmptyCollectionInit.visit, for: node) @@ -250,11 +396,19 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseSingleLinePropertyGetter.visit, for: node) return .visitChildren } + override func visitPost(_ node: PatternBindingSyntax) { + onVisitPost(rule: AlwaysUseLiteralForEmptyCollectionInit.self, for: node) + onVisitPost(rule: OmitExplicitReturns.self, for: node) + onVisitPost(rule: UseSingleLinePropertyGetter.self, for: node) + } override func visit(_ node: PrecedenceGroupDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: PrecedenceGroupDeclSyntax) { + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -265,11 +419,22 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: ProtocolDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: RepeatStmtSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: RepeatStmtSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + } override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) @@ -281,6 +446,15 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(OrderedImports.visit, for: node) return .visitChildren } + override func visitPost(_ node: SourceFileSyntax) { + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: AmbiguousTrailingClosureOverload.self, for: node) + onVisitPost(rule: FileScopedDeclarationPrivacy.self, for: node) + onVisitPost(rule: NeverForceUnwrap.self, for: node) + onVisitPost(rule: NeverUseForceTry.self, for: node) + onVisitPost(rule: NeverUseImplicitlyUnwrappedOptionals.self, for: node) + onVisitPost(rule: OrderedImports.self, for: node) + } override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -292,6 +466,15 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: StructDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + onVisitPost(rule: UseSynthesizedInitializer.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -300,31 +483,52 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: SubscriptDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: OmitExplicitReturns.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: SwitchCaseLabelSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoLabelsInCasePatterns.visit, for: node) return .visitChildren } + override func visitPost(_ node: SwitchCaseLabelSyntax) { + onVisitPost(rule: NoLabelsInCasePatterns.self, for: node) + } override func visit(_ node: SwitchCaseListSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoCasesWithOnlyFallthrough.visit, for: node) return .visitChildren } + override func visitPost(_ node: SwitchCaseListSyntax) { + onVisitPost(rule: NoCasesWithOnlyFallthrough.self, for: node) + } override func visit(_ node: SwitchExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: SwitchExprSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + } override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoBlockComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: TokenSyntax) { + onVisitPost(rule: NoBlockComments.self, for: node) + } override func visit(_ node: TryExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverUseForceTry.visit, for: node) return .visitChildren } + override func visitPost(_ node: TryExprSyntax) { + onVisitPost(rule: NeverUseForceTry.self, for: node) + } override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -334,11 +538,21 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: TypeAliasDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: ValueBindingPatternSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(UseLetInEveryBoundCaseVariable.visit, for: node) return .visitChildren } + override func visitPost(_ node: ValueBindingPatternSyntax) { + onVisitPost(rule: UseLetInEveryBoundCaseVariable.self, for: node) + } override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -348,11 +562,21 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: VariableDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: NeverUseImplicitlyUnwrappedOptionals.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: WhileStmtSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: WhileStmtSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + } } extension FormatPipeline { diff --git a/Sources/generate-swift-format/PipelineGenerator.swift b/Sources/generate-swift-format/PipelineGenerator.swift index abf8b2c0e..e2c2c8d08 100644 --- a/Sources/generate-swift-format/PipelineGenerator.swift +++ b/Sources/generate-swift-format/PipelineGenerator.swift @@ -54,6 +54,10 @@ final class PipelineGenerator: FileGenerator { /// class type. var ruleCache = [ObjectIdentifier: Rule]() + /// Rules present in this dictionary skip visiting children until they leave the + /// syntax node stored as their value + var shouldSkipChildren = [ObjectIdentifier: SyntaxProtocol]() + /// Creates a new lint pipeline. init(context: Context) { self.context = context @@ -85,6 +89,25 @@ final class PipelineGenerator: FileGenerator { } """) + + handle.write( + """ + override func visitPost(_ node: \(nodeType)) { + """ + ) + for ruleName in lintRules.sorted() { + handle.write( + """ + onVisitPost(rule: \(ruleName).self, for: node) + + """) + } + handle.write( + """ + } + + """ + ) } handle.write( From d881cd13108fbaa12e7a44bf562bc7784efca3b7 Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Wed, 25 Oct 2023 11:54:20 -0700 Subject: [PATCH 167/332] Modify tests to check the rule and the pipline have the same findings. Previously we only tested the rule in isolation. This mean we weren't actually checking the pipeline respected the SyntaxVisitorContinueKind. Regardless of what the pipeline did, the tests would respect the continue kind because they run the rule itself. Now we run the rule in isolation and the pipeline and assert that they have the same findings. This ensures the rule behaves as expected (or atleast as tested) when run in the pipeline. --- .../Rules/LintOrFormatRuleTestCase.swift | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index e9c342f97..16ddda951 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -50,6 +50,27 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { context: context, file: file, line: line) + + var emittedPipelineFindings = [Finding]() + // Disable default rules, so only select rule runs in pipeline + configuration.rules = [type.ruleName: true] + let pipeline = SwiftLinter( + configuration: configuration, + findingConsumer: { emittedPipelineFindings.append($0) }) + pipeline.debugOptions.insert(.disablePrettyPrint) + try! pipeline.lint( + syntax: sourceFileSyntax, + operatorTable: OperatorTable.standardOperators, + assumingFileURL: URL(string: file.description)!) + + // Check that pipeline produces the same findings as the isolated linter rule + assertFindings( + expected: findings, + markerLocations: markedText.markers, + emittedFindings: emittedPipelineFindings, + context: context, + file: file, + line: line) } /// Asserts that the result of applying a formatter to the provided input code yields the output. @@ -111,5 +132,20 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { printTokenStream: false, whitespaceOnly: false ).prettyPrint() + + var emittedPipelineFindings = [Finding]() + // Disable default rules, so only select rule runs in pipeline + configuration.rules = [formatType.ruleName: true] + let pipeline = SwiftFormatter( + configuration: configuration, findingConsumer: { emittedPipelineFindings.append($0) }) + pipeline.debugOptions.insert(.disablePrettyPrint) + var pipelineActual = "" + try! pipeline.format( + syntax: sourceFileSyntax, operatorTable: OperatorTable.standardOperators, + assumingFileURL: nil, to: &pipelineActual) + assertStringsEqualWithDiff(pipelineActual, expected) + assertFindings( + expected: findings, markerLocations: markedInput.markers, + emittedFindings: emittedPipelineFindings, context: context, file: file, line: line) } } From 3f8f55ca5000095ee658ef431cae544678be8f4e Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Fri, 27 Oct 2023 10:55:13 -0700 Subject: [PATCH 168/332] Remove deprecated variants from parameterClause case match --- .../Rules/AlwaysUseLowerCamelCase.swift | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift index aad4fdb44..e24d23178 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift @@ -85,26 +85,6 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { secondName, allowUnderscores: false, description: identifierDescription(for: node)) } } - } else if let parameterClause = input.as(EnumCaseParameterClauseSyntax.self) { - for param in parameterClause.parameters { - if let firstName = param.firstName { - diagnoseLowerCamelCaseViolations( - firstName, allowUnderscores: false, description: identifierDescription(for: node)) - } - if let secondName = param.secondName { - diagnoseLowerCamelCaseViolations( - secondName, allowUnderscores: false, description: identifierDescription(for: node)) - } - } - } else if let parameterClause = input.as(FunctionParameterClauseSyntax.self) { - for param in parameterClause.parameters { - diagnoseLowerCamelCaseViolations( - param.firstName, allowUnderscores: false, description: identifierDescription(for: node)) - if let secondName = param.secondName { - diagnoseLowerCamelCaseViolations( - secondName, allowUnderscores: false, description: identifierDescription(for: node)) - } - } } } return .visitChildren From 994f34bbfd3954fe0f06e1cc81926ce25b8be835 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 20 Nov 2023 08:21:43 -0500 Subject: [PATCH 169/332] Fix formatting of keypath subscript components with labels. Also refine the treatment of the closing bracket so that it is only forced to go onto a new line if the subscript component is not the last component in the keypath. This aligns with how we handle closing brackets/parentheses in member access chains. Fixes #663. --- .../PrettyPrint/TokenStreamCreator.swift | 24 +++++++++++++++++-- .../PrettyPrint/KeyPathExprTests.swift | 12 ++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 85ccb71d0..070de11a5 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -1894,11 +1894,31 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: KeyPathSubscriptComponentSyntax) -> SyntaxVisitorContinueKind { - after(node.leftSquare, tokens: .break(.open, size: 0), .open) - before(node.rightSquare, tokens: .break(.close, size: 0), .close) + var breakBeforeRightParen = !isCompactSingleFunctionCallArgument(node.arguments) + if let component = node.parent?.as(KeyPathComponentSyntax.self) { + breakBeforeRightParen = !isLastKeyPathComponent(component) + } + + arrangeFunctionCallArgumentList( + node.arguments, + leftDelimiter: node.leftSquare, + rightDelimiter: node.rightSquare, + forcesBreakBeforeRightDelimiter: breakBeforeRightParen) return .visitChildren } + /// Returns a value indicating whether the given key path component was the last component in the + /// list containing it. + private func isLastKeyPathComponent(_ component: KeyPathComponentSyntax) -> Bool { + guard + let componentList = component.parent?.as(KeyPathComponentListSyntax.self), + let lastComponent = componentList.last + else { + return false + } + return component == lastComponent + } + override func visit(_ node: TernaryExprSyntax) -> SyntaxVisitorContinueKind { // The order of the .open/.close tokens here is intentional. They are normally paired with the // corresponding breaks, but in this case, we want to prioritize keeping the entire `? a : b` diff --git a/Tests/SwiftFormatTests/PrettyPrint/KeyPathExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/KeyPathExprTests.swift index e0ab55364..8573d4be2 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/KeyPathExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/KeyPathExprTests.swift @@ -131,7 +131,9 @@ final class KeyPathExprTests: PrettyPrintTestCase { #""" let x = \ReallyLongType.reallyLongProperty.anotherLongProperty let x = \.reeeeallyLongProperty.anotherLongProperty + let x = \.longProperty.a.b.c[really + long + expression] let x = \.longProperty.a.b.c[really + long + expression].anotherLongProperty + let x = \.longProperty.a.b.c[label:really + long + expression].anotherLongProperty """# let expected = @@ -146,6 +148,16 @@ final class KeyPathExprTests: PrettyPrintTestCase { let x = \.longProperty.a.b.c[ really + long + + expression] + let x = + \.longProperty.a.b.c[ + really + long + + expression + ].anotherLongProperty + let x = + \.longProperty.a.b.c[ + label: really + + long + expression ].anotherLongProperty From adf3bda722123f2de9a0bb1699a2fec316d5c096 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 20 Nov 2023 08:27:06 -0500 Subject: [PATCH 170/332] [NoEmptyTrailingClosureParentheses] Do not remove parens if they contain comments. Fixes #665. --- .../NoEmptyTrailingClosureParentheses.swift | 5 +++- ...EmptyTrailingClosureParenthesesTests.swift | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift b/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift index 0601ae98f..d3c17eedd 100644 --- a/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift +++ b/Sources/SwiftFormat/Rules/NoEmptyTrailingClosureParentheses.swift @@ -27,7 +27,10 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule { guard let trailingClosure = node.trailingClosure, let leftParen = node.leftParen, - node.arguments.isEmpty + let rightParen = node.rightParen, + node.arguments.isEmpty, + !leftParen.trailingTrivia.hasAnyComments, + !rightParen.leadingTrivia.hasAnyComments else { return super.visit(node) } diff --git a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift index bee6c215c..d5b6275a7 100644 --- a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift @@ -83,4 +83,29 @@ final class NoEmptyTrailingClosureParenthesesTests: LintOrFormatRuleTestCase { ] ) } + + func testDoNotRemoveParensContainingOnlyComments() { + assertFormatting( + NoEmptyTrailingClosureParentheses.self, + input: """ + greetEnthusiastically(/*oldArg: x*/) { "John" } + greetEnthusiastically( + /*oldArg: x*/ + ) { "John" } + greetEnthusiastically( + // oldArg: x + ) { "John" } + """, + expected: """ + greetEnthusiastically(/*oldArg: x*/) { "John" } + greetEnthusiastically( + /*oldArg: x*/ + ) { "John" } + greetEnthusiastically( + // oldArg: x + ) { "John" } + """, + findings: [] + ) + } } From b268ae8f7651e5edec419912c7554b8811c0214e Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Mon, 20 Nov 2023 08:53:46 -0500 Subject: [PATCH 171/332] [UseShorthandTypeNames] Fix shorthand for optional attributed types. Like with `some/any` type sugar, when we simplify an `Optional` whose type parameter is an attributed type, we need to wrap it in parentheses so that the `?` doesn't bind with the contained type. Fixes #657. --- .../Rules/UseShorthandTypeNames.swift | 21 +++++----- .../Rules/UseShorthandTypeNamesTests.swift | 40 +++++++++++++++++++ 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift index 1c3db0cc5..ea48c5899 100644 --- a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift @@ -238,11 +238,12 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { ) -> TypeSyntax { var wrappedType = wrappedType - // Function types and some-or-any types must be wrapped in parentheses before using shorthand - // optional syntax, otherwise the "?" will bind incorrectly (in the function case it binds to - // only the result, and in the some-or-any case it only binds to the child protocol). Attach the - // leading trivia to the left-paren that we're adding in these cases. + // Certain types must be wrapped in parentheses before using shorthand optional syntax to avoid + // the "?" from binding incorrectly when re-parsed. Attach the leading trivia to the left-paren + // that we're adding in these cases. switch Syntax(wrappedType).as(SyntaxEnum.self) { + case .attributedType(let attributedType): + wrappedType = parenthesizedType(attributedType, leadingTrivia: leadingTrivia) case .functionType(let functionType): wrappedType = parenthesizedType(functionType, leadingTrivia: leadingTrivia) case .someOrAnyType(let someOrAnyType): @@ -319,12 +320,11 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { ) -> OptionalChainingExprSyntax? { guard var wrappedTypeExpr = expressionRepresentation(of: wrappedType) else { return nil } - // Function types and some-or-any types must be wrapped in parentheses before using shorthand - // optional syntax, otherwise the "?" will bind incorrectly (in the function case it binds to - // only the result, and in the some-or-any case it only binds to the child protocol). Attach the - // leading trivia to the left-paren that we're adding in these cases. + // Certain types must be wrapped in parentheses before using shorthand optional syntax to avoid + // the "?" from binding incorrectly when re-parsed. Attach the leading trivia to the left-paren + // that we're adding in these cases. switch Syntax(wrappedType).as(SyntaxEnum.self) { - case .functionType, .someOrAnyType: + case .attributedType, .functionType, .someOrAnyType: wrappedTypeExpr = parenthesizedExpr(wrappedTypeExpr, leadingTrivia: leadingTrivia) default: // Otherwise, the argument type can safely become an optional by simply appending a "?". If @@ -448,6 +448,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { case .someOrAnyType(let someOrAnyType): return ExprSyntax(TypeExprSyntax(type: someOrAnyType)) + case .attributedType(let attributedType): + return ExprSyntax(TypeExprSyntax(type: attributedType)) + default: return nil } diff --git a/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift b/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift index a54b98a9e..ce1f3d797 100644 --- a/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift @@ -692,4 +692,44 @@ final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { ] ) } + + func testAttributedTypesInOptionalsAreParenthesized() { + // If we need to insert parentheses, verify that we do, but also verify that we don't insert + // them unnecessarily. + assertFormatting( + UseShorthandTypeNames.self, + input: """ + var x: 1️⃣Optional = S() + var y: 2️⃣Optional<@Sendable (Int) -> Void> = S() + var z = [3️⃣Optional]([S()]) + var a = [4️⃣Optional<@Sendable (Int) -> Void>]([S()]) + + var x: 5️⃣Optional<(consuming P)> = S() + var y: 6️⃣Optional<(@Sendable (Int) -> Void)> = S() + var z = [7️⃣Optional<(consuming P)>]([S()]) + var a = [8️⃣Optional<(@Sendable (Int) -> Void)>]([S()]) + """, + expected: """ + var x: (consuming P)? = S() + var y: (@Sendable (Int) -> Void)? = S() + var z = [(consuming P)?]([S()]) + var a = [(@Sendable (Int) -> Void)?]([S()]) + + var x: (consuming P)? = S() + var y: (@Sendable (Int) -> Void)? = S() + var z = [(consuming P)?]([S()]) + var a = [(@Sendable (Int) -> Void)?]([S()]) + """, + findings: [ + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("2️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("3️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("4️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("5️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("6️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("7️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("8️⃣", message: "use shorthand syntax for this 'Optional' type"), + ] + ) + } } From d0c581689fc62e8d23f962a25852473572f08b96 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 14 Dec 2023 14:49:31 -0500 Subject: [PATCH 172/332] [OrderedImports] Fix dropped trailing comments on top-level code items. Also add some validation logic in format rule tests. We already run the pretty printer afterwards to verify that there aren't any invariants broken that would cause an assertion failure, but we don't compare the actual pretty-printed text to the originally transformed tree (because we don't want the output of those tests to be sensitive to pretty-printer configuration). What we *can* do, which is still an improvement, is walk the token sequence and compare the tokens and trivia in a whitespace-insensitive manner. This makes sure that we don't move trivia around in a way that the format rule would accept but that the pretty-printer wouldn't know how to handle. --- .../SwiftFormat/Rules/OrderedImports.swift | 20 ++++---- .../Rules/LintOrFormatRuleTestCase.swift | 48 +++++++++++++++++-- .../Rules/OrderedImportsTests.swift | 34 +++++++++++++ 3 files changed, 87 insertions(+), 15 deletions(-) diff --git a/Sources/SwiftFormat/Rules/OrderedImports.swift b/Sources/SwiftFormat/Rules/OrderedImports.swift index 1a1463197..292bbd472 100644 --- a/Sources/SwiftFormat/Rules/OrderedImports.swift +++ b/Sources/SwiftFormat/Rules/OrderedImports.swift @@ -315,17 +315,17 @@ fileprivate func generateLines(codeBlockItemList: CodeBlockItemListSyntax, conte blockWithoutTrailingTrivia.trailingTrivia = [] currentLine.syntaxNode = .importCodeBlock(blockWithoutTrailingTrivia, sortable: sortable) } else { - guard let syntaxNode = currentLine.syntaxNode else { - currentLine.syntaxNode = .nonImportCodeBlocks([block]) - continue - } - // Multiple code blocks can be merged, as long as there isn't an import statement. - switch syntaxNode { - case .importCodeBlock: - appendNewLine() + if let syntaxNode = currentLine.syntaxNode { + // Multiple code blocks can be merged, as long as there isn't an import statement. + switch syntaxNode { + case .importCodeBlock: + appendNewLine() + currentLine.syntaxNode = .nonImportCodeBlocks([block]) + case .nonImportCodeBlocks(let existingCodeBlocks): + currentLine.syntaxNode = .nonImportCodeBlocks(existingCodeBlocks + [block]) + } + } else { currentLine.syntaxNode = .nonImportCodeBlocks([block]) - case .nonImportCodeBlocks(let existingCodeBlocks): - currentLine.syntaxNode = .nonImportCodeBlocks(existingCodeBlocks + [block]) } } diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index 16ddda951..e989bd804 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -112,7 +112,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { let formatter = formatType.init(context: context) let actual = formatter.visit(sourceFileSyntax) - assertStringsEqualWithDiff(actual.description, expected, file: file, line: line) + assertStringsEqualWithDiff("\(actual)", expected, file: file, line: line) assertFindings( expected: findings, @@ -123,15 +123,22 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { line: line) // Verify that the pretty printer can consume the transformed tree (e.g., it does not contain - // any unfolded `SequenceExpr`s). We don't need to check the actual output here (we don't want - // the rule tests to be pretty-printer dependent), but this will catch invariants that aren't - // satisfied. - _ = PrettyPrinter( + // any unfolded `SequenceExpr`s). Then do a whitespace-insensitive comparison of the two trees + // to verify that the format rule didn't transform the tree in such a way that it caused the + // pretty-printer to drop important information (the most likely case is a format rule + // misplacing trivia in a way that the pretty-printer isn't able to handle). + let prettyPrintedSource = PrettyPrinter( context: context, node: Syntax(actual), printTokenStream: false, whitespaceOnly: false ).prettyPrint() + let prettyPrintedTree = Parser.parse(source: prettyPrintedSource) + XCTAssertEqual( + whitespaceInsensitiveText(of: actual), + whitespaceInsensitiveText(of: prettyPrintedTree), + "After pretty-printing and removing fluid whitespace, the files did not match", + file: file, line: line) var emittedPipelineFindings = [Finding]() // Disable default rules, so only select rule runs in pipeline @@ -149,3 +156,34 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { emittedFindings: emittedPipelineFindings, context: context, file: file, line: line) } } + +/// Returns a string containing a whitespace-insensitive representation of the given source file. +private func whitespaceInsensitiveText(of file: SourceFileSyntax) -> String { + var result = "" + for token in file.tokens(viewMode: .sourceAccurate) { + appendNonspaceTrivia(token.leadingTrivia, to: &result) + result.append(token.text) + appendNonspaceTrivia(token.trailingTrivia, to: &result) + } + return result +} + +/// Appends any non-whitespace trivia pieces from the given trivia collection to the output string. +private func appendNonspaceTrivia(_ trivia: Trivia, to string: inout String) { + for piece in trivia { + switch piece { + case .carriageReturnLineFeeds, .carriageReturns, .formfeeds, .newlines, .spaces, .tabs: + break + case .lineComment(let comment), .docLineComment(let comment): + // A tree transforming rule might leave whitespace at the end of a line comment, which the + // pretty printer will remove, so we should ignore that. + if let lastNonWhitespaceIndex = comment.lastIndex(where: { !$0.isWhitespace }) { + string.append(contentsOf: comment[...lastNonWhitespaceIndex]) + } else { + string.append(comment) + } + default: + piece.write(to: &string) + } + } +} diff --git a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift index bae1b3e77..a61a07539 100644 --- a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift @@ -618,4 +618,38 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { ] ) } + + func testTrailingCommentsOnTopLevelCodeItems() { + assertFormatting( + OrderedImports.self, + input: """ + import Zebras + 1️⃣import Apples + #if canImport(Darwin) + import Darwin + #elseif canImport(Glibc) + import Glibc + #endif // canImport(Darwin) + + foo() // calls the foo + bar() // calls the bar + """, + expected: """ + import Apples + import Zebras + + #if canImport(Darwin) + import Darwin + #elseif canImport(Glibc) + import Glibc + #endif // canImport(Darwin) + + foo() // calls the foo + bar() // calls the bar + """, + findings: [ + FindingSpec("1️⃣", message: "sort import statements lexicographically"), + ] + ) + } } From cedbd82993f977f401f16526ff23cea713a9a6a2 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sat, 20 Jan 2024 19:37:49 -0800 Subject: [PATCH 173/332] Group functions in build-script-helper.py --- build-script-helper.py | 182 ++++++++++++++++++++++------------------- 1 file changed, 96 insertions(+), 86 deletions(-) diff --git a/build-script-helper.py b/build-script-helper.py index db0782b3d..38b7e300e 100755 --- a/build-script-helper.py +++ b/build-script-helper.py @@ -19,12 +19,92 @@ import os, platform import subprocess -def printerr(message): +# ----------------------------------------------------------------------------- +# General utilities + +def printerr(message: str): print(message, file=sys.stderr) -def main(argv_prefix = []): - args = parse_args(argv_prefix + sys.argv[1:]) - run(args) +def check_call(cmd, verbose, env=os.environ, **kwargs): + if verbose: + print(' '.join([escape_cmd_arg(arg) for arg in cmd])) + return subprocess.check_call(cmd, env=env, stderr=subprocess.STDOUT, **kwargs) + +def check_output(cmd, verbose, env=os.environ, capture_stderr=True, **kwargs): + if verbose: + print(' '.join([escape_cmd_arg(arg) for arg in cmd])) + if capture_stderr: + stderr = subprocess.STDOUT + else: + stderr = subprocess.DEVNULL + return subprocess.check_output(cmd, env=env, stderr=stderr, encoding='utf-8', **kwargs) + +def escape_cmd_arg(arg): + if '"' in arg or ' ' in arg: + return '"%s"' % arg.replace('"', '\\"') + else: + return arg + +# ----------------------------------------------------------------------------- +# SwiftPM wrappers + +def update_swiftpm_dependencies(package_path, swift_exec, build_path, env, verbose): + args = [swift_exec, 'package', '--package-path', package_path, '--scratch-path', build_path, 'update'] + check_call(args, env=env, verbose=verbose) + +def invoke_swift(package_path, swift_exec, action, products, build_path, multiroot_data_file, configuration, env, verbose): + # Until rdar://53881101 is implemented, we cannot request a build of multiple + # targets simultaneously. For now, just build one product after the other. + for product in products: + invoke_swift_single_product(package_path, swift_exec, action, product, build_path, multiroot_data_file, configuration, env, verbose) + +def get_swiftpm_options(package_path, build_path, multiroot_data_file, configuration, verbose): + args = [ + '--package-path', package_path, + '--configuration', configuration, + '--scratch-path', build_path + ] + if multiroot_data_file: + args += ['--multiroot-data-file', multiroot_data_file] + if verbose: + args += ['--verbose'] + if platform.system() == 'Darwin': + args += [ + '-Xlinker', '-rpath', '-Xlinker', '/usr/lib/swift', + '-Xlinker', '-rpath', '-Xlinker', '@executable_path/../lib/swift/macosx', + '-Xlinker', '-rpath', '-Xlinker', '@executable_path/../lib/swift-5.5/macosx', + ] + return args + +def get_swiftpm_environment_variables(no_local_deps): + env = dict(os.environ) + if not no_local_deps: + env['SWIFTCI_USE_LOCAL_DEPS'] = "1" + return env + + +def invoke_swift_single_product(package_path, swift_exec, action, product, build_path, multiroot_data_file, configuration, env, verbose): + args = [swift_exec, action] + args += get_swiftpm_options(package_path, build_path, multiroot_data_file, configuration, verbose) + if action == 'test': + args += [ + '--test-product', product, + '--disable-testable-imports' + ] + else: + args += ['--product', product] + + check_call(args, env=env, verbose=verbose) + +def generate_xcodeproj(package_path, swift_exec, env, verbose): + package_name = os.path.basename(package_path) + xcodeproj_path = os.path.join(package_path, '%s.xcodeproj' % package_name) + args = [swift_exec, 'package', '--package-path', package_path, 'generate-xcodeproj', '--output', xcodeproj_path] + check_call(args, env=env, verbose=verbose) + +# ----------------------------------------------------------------------------- +# Argument parsing + def parse_args(args): parser = argparse.ArgumentParser(prog='build-script-helper.py') @@ -54,6 +134,14 @@ def parse_args(args): return parsed +def should_run_action(action_name, selected_actions): + if action_name in selected_actions: + return True + elif "all" in selected_actions: + return True + else: + return False + def run(args): package_name = os.path.basename(args.package_path) @@ -139,87 +227,9 @@ def run(args): cmd = ['rsync', '-a', os.path.join(bin_path, 'swift-format'), os.path.join(prefix, 'bin')] check_call(cmd, verbose=args.verbose) -def should_run_action(action_name, selected_actions): - if action_name in selected_actions: - return True - elif "all" in selected_actions: - return True - else: - return False - -def update_swiftpm_dependencies(package_path, swift_exec, build_path, env, verbose): - args = [swift_exec, 'package', '--package-path', package_path, '--scratch-path', build_path, 'update'] - check_call(args, env=env, verbose=verbose) - -def invoke_swift(package_path, swift_exec, action, products, build_path, multiroot_data_file, configuration, env, verbose): - # Until rdar://53881101 is implemented, we cannot request a build of multiple - # targets simultaneously. For now, just build one product after the other. - for product in products: - invoke_swift_single_product(package_path, swift_exec, action, product, build_path, multiroot_data_file, configuration, env, verbose) - -def get_swiftpm_options(package_path, build_path, multiroot_data_file, configuration, verbose): - args = [ - '--package-path', package_path, - '--configuration', configuration, - '--scratch-path', build_path - ] - if multiroot_data_file: - args += ['--multiroot-data-file', multiroot_data_file] - if verbose: - args += ['--verbose'] - if platform.system() == 'Darwin': - args += [ - '-Xlinker', '-rpath', '-Xlinker', '/usr/lib/swift', - '-Xlinker', '-rpath', '-Xlinker', '@executable_path/../lib/swift/macosx', - '-Xlinker', '-rpath', '-Xlinker', '@executable_path/../lib/swift-5.5/macosx', - ] - return args - -def get_swiftpm_environment_variables(no_local_deps): - env = dict(os.environ) - if not no_local_deps: - env['SWIFTCI_USE_LOCAL_DEPS'] = "1" - return env - - -def invoke_swift_single_product(package_path, swift_exec, action, product, build_path, multiroot_data_file, configuration, env, verbose): - args = [swift_exec, action] - args += get_swiftpm_options(package_path, build_path, multiroot_data_file, configuration, verbose) - if action == 'test': - args += [ - '--test-product', product, - '--disable-testable-imports' - ] - else: - args += ['--product', product] - - check_call(args, env=env, verbose=verbose) - -def generate_xcodeproj(package_path, swift_exec, env, verbose): - package_name = os.path.basename(package_path) - xcodeproj_path = os.path.join(package_path, '%s.xcodeproj' % package_name) - args = [swift_exec, 'package', '--package-path', package_path, 'generate-xcodeproj', '--output', xcodeproj_path] - check_call(args, env=env, verbose=verbose) - -def check_call(cmd, verbose, env=os.environ, **kwargs): - if verbose: - print(' '.join([escape_cmd_arg(arg) for arg in cmd])) - return subprocess.check_call(cmd, env=env, stderr=subprocess.STDOUT, **kwargs) - -def check_output(cmd, verbose, env=os.environ, capture_stderr=True, **kwargs): - if verbose: - print(' '.join([escape_cmd_arg(arg) for arg in cmd])) - if capture_stderr: - stderr = subprocess.STDOUT - else: - stderr = subprocess.DEVNULL - return subprocess.check_output(cmd, env=env, stderr=stderr, encoding='utf-8', **kwargs) - -def escape_cmd_arg(arg): - if '"' in arg or ' ' in arg: - return '"%s"' % arg.replace('"', '\\"') - else: - return arg +def main(argv_prefix = []): + args = parse_args(argv_prefix + sys.argv[1:]) + run(args) if __name__ == '__main__': - main() + main() \ No newline at end of file From 9a6e304be1f2bf22865cccdafcad6a37537c03e2 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sat, 20 Jan 2024 19:38:40 -0800 Subject: [PATCH 174/332] Format build-script-helper.py with `black` --- build-script-helper.py | 473 ++++++++++++++++++++++++++--------------- 1 file changed, 296 insertions(+), 177 deletions(-) diff --git a/build-script-helper.py b/build-script-helper.py index 38b7e300e..b54845307 100755 --- a/build-script-helper.py +++ b/build-script-helper.py @@ -22,214 +22,333 @@ # ----------------------------------------------------------------------------- # General utilities + def printerr(message: str): - print(message, file=sys.stderr) + print(message, file=sys.stderr) + def check_call(cmd, verbose, env=os.environ, **kwargs): - if verbose: - print(' '.join([escape_cmd_arg(arg) for arg in cmd])) - return subprocess.check_call(cmd, env=env, stderr=subprocess.STDOUT, **kwargs) + if verbose: + print(" ".join([escape_cmd_arg(arg) for arg in cmd])) + return subprocess.check_call(cmd, env=env, stderr=subprocess.STDOUT, **kwargs) + def check_output(cmd, verbose, env=os.environ, capture_stderr=True, **kwargs): - if verbose: - print(' '.join([escape_cmd_arg(arg) for arg in cmd])) - if capture_stderr: - stderr = subprocess.STDOUT - else: - stderr = subprocess.DEVNULL - return subprocess.check_output(cmd, env=env, stderr=stderr, encoding='utf-8', **kwargs) + if verbose: + print(" ".join([escape_cmd_arg(arg) for arg in cmd])) + if capture_stderr: + stderr = subprocess.STDOUT + else: + stderr = subprocess.DEVNULL + return subprocess.check_output( + cmd, env=env, stderr=stderr, encoding="utf-8", **kwargs + ) + def escape_cmd_arg(arg): - if '"' in arg or ' ' in arg: - return '"%s"' % arg.replace('"', '\\"') - else: - return arg + if '"' in arg or " " in arg: + return '"%s"' % arg.replace('"', '\\"') + else: + return arg + # ----------------------------------------------------------------------------- # SwiftPM wrappers + def update_swiftpm_dependencies(package_path, swift_exec, build_path, env, verbose): - args = [swift_exec, 'package', '--package-path', package_path, '--scratch-path', build_path, 'update'] - check_call(args, env=env, verbose=verbose) - -def invoke_swift(package_path, swift_exec, action, products, build_path, multiroot_data_file, configuration, env, verbose): - # Until rdar://53881101 is implemented, we cannot request a build of multiple - # targets simultaneously. For now, just build one product after the other. - for product in products: - invoke_swift_single_product(package_path, swift_exec, action, product, build_path, multiroot_data_file, configuration, env, verbose) - -def get_swiftpm_options(package_path, build_path, multiroot_data_file, configuration, verbose): - args = [ - '--package-path', package_path, - '--configuration', configuration, - '--scratch-path', build_path - ] - if multiroot_data_file: - args += ['--multiroot-data-file', multiroot_data_file] - if verbose: - args += ['--verbose'] - if platform.system() == 'Darwin': - args += [ - '-Xlinker', '-rpath', '-Xlinker', '/usr/lib/swift', - '-Xlinker', '-rpath', '-Xlinker', '@executable_path/../lib/swift/macosx', - '-Xlinker', '-rpath', '-Xlinker', '@executable_path/../lib/swift-5.5/macosx', + args = [ + swift_exec, + "package", + "--package-path", + package_path, + "--scratch-path", + build_path, + "update", + ] + check_call(args, env=env, verbose=verbose) + + +def invoke_swift( + package_path, + swift_exec, + action, + products, + build_path, + multiroot_data_file, + configuration, + env, + verbose, +): + # Until rdar://53881101 is implemented, we cannot request a build of multiple + # targets simultaneously. For now, just build one product after the other. + for product in products: + invoke_swift_single_product( + package_path, + swift_exec, + action, + product, + build_path, + multiroot_data_file, + configuration, + env, + verbose, + ) + + +def get_swiftpm_options( + package_path, build_path, multiroot_data_file, configuration, verbose +): + args = [ + "--package-path", + package_path, + "--configuration", + configuration, + "--scratch-path", + build_path, ] - return args + if multiroot_data_file: + args += ["--multiroot-data-file", multiroot_data_file] + if verbose: + args += ["--verbose"] + if platform.system() == "Darwin": + args += [ + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../lib/swift/macosx", + "-Xlinker", + "-rpath", + "-Xlinker", + "@executable_path/../lib/swift-5.5/macosx", + ] + return args + def get_swiftpm_environment_variables(no_local_deps): - env = dict(os.environ) - if not no_local_deps: - env['SWIFTCI_USE_LOCAL_DEPS'] = "1" - return env - - -def invoke_swift_single_product(package_path, swift_exec, action, product, build_path, multiroot_data_file, configuration, env, verbose): - args = [swift_exec, action] - args += get_swiftpm_options(package_path, build_path, multiroot_data_file, configuration, verbose) - if action == 'test': - args += [ - '--test-product', product, - '--disable-testable-imports' - ] - else: - args += ['--product', product] + env = dict(os.environ) + if not no_local_deps: + env["SWIFTCI_USE_LOCAL_DEPS"] = "1" + return env + + +def invoke_swift_single_product( + package_path, + swift_exec, + action, + product, + build_path, + multiroot_data_file, + configuration, + env, + verbose, +): + args = [swift_exec, action] + args += get_swiftpm_options( + package_path, build_path, multiroot_data_file, configuration, verbose + ) + if action == "test": + args += ["--test-product", product, "--disable-testable-imports"] + else: + args += ["--product", product] + + check_call(args, env=env, verbose=verbose) - check_call(args, env=env, verbose=verbose) def generate_xcodeproj(package_path, swift_exec, env, verbose): - package_name = os.path.basename(package_path) - xcodeproj_path = os.path.join(package_path, '%s.xcodeproj' % package_name) - args = [swift_exec, 'package', '--package-path', package_path, 'generate-xcodeproj', '--output', xcodeproj_path] - check_call(args, env=env, verbose=verbose) + package_name = os.path.basename(package_path) + xcodeproj_path = os.path.join(package_path, "%s.xcodeproj" % package_name) + args = [ + swift_exec, + "package", + "--package-path", + package_path, + "generate-xcodeproj", + "--output", + xcodeproj_path, + ] + check_call(args, env=env, verbose=verbose) + # ----------------------------------------------------------------------------- # Argument parsing def parse_args(args): - parser = argparse.ArgumentParser(prog='build-script-helper.py') + parser = argparse.ArgumentParser(prog="build-script-helper.py") + + parser.add_argument("--package-path", default="") + parser.add_argument( + "-v", "--verbose", action="store_true", help="log executed commands" + ) + parser.add_argument( + "--prefix", + dest="install_prefixes", + nargs="*", + metavar="PATHS", + help="install path", + ) + parser.add_argument("--configuration", default="debug") + parser.add_argument("--build-path", default=None) + parser.add_argument( + "--multiroot-data-file", + help="Path to an Xcode workspace to create a unified build of SwiftSyntax with other projects.", + ) + parser.add_argument( + "--toolchain", + required=True, + help="the toolchain to use when building this package", + ) + parser.add_argument( + "--update", action="store_true", help="update all SwiftPM dependencies" + ) + parser.add_argument( + "--no-local-deps", + action="store_true", + help="use normal remote dependencies when building", + ) + parser.add_argument( + "build_actions", + help="Extra actions to perform. Can be any number of the following", + choices=["all", "build", "test", "generate-xcodeproj", "install"], + nargs="*", + default=["build"], + ) - parser.add_argument('--package-path', default='') - parser.add_argument('-v', '--verbose', action='store_true', help='log executed commands') - parser.add_argument('--prefix', dest='install_prefixes', nargs='*', metavar='PATHS', help='install path') - parser.add_argument('--configuration', default='debug') - parser.add_argument('--build-path', default=None) - parser.add_argument('--multiroot-data-file', help='Path to an Xcode workspace to create a unified build of SwiftSyntax with other projects.') - parser.add_argument('--toolchain', required=True, help='the toolchain to use when building this package') - parser.add_argument('--update', action='store_true', help='update all SwiftPM dependencies') - parser.add_argument('--no-local-deps', action='store_true', help='use normal remote dependencies when building') - parser.add_argument('build_actions', help="Extra actions to perform. Can be any number of the following", choices=['all', 'build', 'test', 'generate-xcodeproj', 'install'], nargs="*", default=['build']) + parsed = parser.parse_args(args) - parsed = parser.parse_args(args) + parsed.swift_exec = os.path.join(parsed.toolchain, "bin", "swift") - parsed.swift_exec = os.path.join(parsed.toolchain, 'bin', 'swift') + # Convert package_path to absolute path, relative to root of repo. + repo_path = os.path.dirname(__file__) + parsed.package_path = os.path.realpath(os.path.join(repo_path, parsed.package_path)) - # Convert package_path to absolute path, relative to root of repo. - repo_path = os.path.dirname(__file__) - parsed.package_path = os.path.realpath( - os.path.join(repo_path, parsed.package_path)) + if not parsed.build_path: + parsed.build_path = os.path.join(parsed.package_path, ".build") - if not parsed.build_path: - parsed.build_path = os.path.join(parsed.package_path, '.build') + return parsed - return parsed def should_run_action(action_name, selected_actions): - if action_name in selected_actions: - return True - elif "all" in selected_actions: - return True - else: - return False + if action_name in selected_actions: + return True + elif "all" in selected_actions: + return True + else: + return False + def run(args): - package_name = os.path.basename(args.package_path) - - env = get_swiftpm_environment_variables(no_local_deps=args.no_local_deps) - # Use local dependencies (i.e. checked out next swift-format). - - if args.update: - print("** Updating dependencies of %s **" % package_name) - try: - update_swiftpm_dependencies(package_path=args.package_path, - swift_exec=args.swift_exec, - build_path=args.build_path, - env=env, - verbose=args.verbose) - except subprocess.CalledProcessError as e: - printerr('FAIL: Updating dependencies of %s failed' % package_name) - printerr('Executing: %s' % ' '.join(e.cmd)) - sys.exit(1) - - # The test action creates its own build. No need to build if we are just testing. - if should_run_action('build', args.build_actions) or should_run_action('install', args.build_actions): - print("** Building %s **" % package_name) - try: - invoke_swift(package_path=args.package_path, - swift_exec=args.swift_exec, - action='build', - products=['swift-format'], - build_path=args.build_path, - multiroot_data_file=args.multiroot_data_file, - configuration=args.configuration, - env=env, - verbose=args.verbose) - except subprocess.CalledProcessError as e: - printerr('FAIL: Building %s failed' % package_name) - printerr('Executing: %s' % ' '.join(e.cmd)) - sys.exit(1) - - output_dir = os.path.realpath(os.path.join(args.build_path, args.configuration)) - - if should_run_action("generate-xcodeproj", args.build_actions): - print("** Generating Xcode project for %s **" % package_name) - try: - generate_xcodeproj(args.package_path, - swift_exec=args.swift_exec, - env=env, - verbose=args.verbose) - except subprocess.CalledProcessError as e: - printerr('FAIL: Generating the Xcode project failed') - printerr('Executing: %s' % ' '.join(e.cmd)) - sys.exit(1) - - if should_run_action("test", args.build_actions): - print("** Testing %s **" % package_name) - try: - invoke_swift(package_path=args.package_path, - swift_exec=args.swift_exec, - action='test', - products=['%sPackageTests' % package_name], - build_path=args.build_path, - multiroot_data_file=args.multiroot_data_file, - configuration=args.configuration, - env=env, - verbose=args.verbose) - except subprocess.CalledProcessError as e: - printerr('FAIL: Testing %s failed' % package_name) - printerr('Executing: %s' % ' '.join(e.cmd)) - sys.exit(1) - - if should_run_action("install", args.build_actions): - print("** Installing %s **" % package_name) - - swiftpm_args = get_swiftpm_options( - package_path=args.package_path, - build_path=args.build_path, - multiroot_data_file=args.multiroot_data_file, - configuration=args.configuration, - verbose=args.verbose - ) - cmd = [args.swift_exec, 'build', '--show-bin-path'] + swiftpm_args - bin_path = check_output(cmd, env=env, capture_stderr=False, verbose=args.verbose).strip() - - for prefix in args.install_prefixes: - cmd = ['rsync', '-a', os.path.join(bin_path, 'swift-format'), os.path.join(prefix, 'bin')] - check_call(cmd, verbose=args.verbose) - -def main(argv_prefix = []): - args = parse_args(argv_prefix + sys.argv[1:]) - run(args) - -if __name__ == '__main__': - main() \ No newline at end of file + package_name = os.path.basename(args.package_path) + + env = get_swiftpm_environment_variables(no_local_deps=args.no_local_deps) + # Use local dependencies (i.e. checked out next swift-format). + + if args.update: + print("** Updating dependencies of %s **" % package_name) + try: + update_swiftpm_dependencies( + package_path=args.package_path, + swift_exec=args.swift_exec, + build_path=args.build_path, + env=env, + verbose=args.verbose, + ) + except subprocess.CalledProcessError as e: + printerr("FAIL: Updating dependencies of %s failed" % package_name) + printerr("Executing: %s" % " ".join(e.cmd)) + sys.exit(1) + + # The test action creates its own build. No need to build if we are just testing. + if should_run_action("build", args.build_actions) or should_run_action( + "install", args.build_actions + ): + print("** Building %s **" % package_name) + try: + invoke_swift( + package_path=args.package_path, + swift_exec=args.swift_exec, + action="build", + products=["swift-format"], + build_path=args.build_path, + multiroot_data_file=args.multiroot_data_file, + configuration=args.configuration, + env=env, + verbose=args.verbose, + ) + except subprocess.CalledProcessError as e: + printerr("FAIL: Building %s failed" % package_name) + printerr("Executing: %s" % " ".join(e.cmd)) + sys.exit(1) + + output_dir = os.path.realpath(os.path.join(args.build_path, args.configuration)) + + if should_run_action("generate-xcodeproj", args.build_actions): + print("** Generating Xcode project for %s **" % package_name) + try: + generate_xcodeproj( + args.package_path, + swift_exec=args.swift_exec, + env=env, + verbose=args.verbose, + ) + except subprocess.CalledProcessError as e: + printerr("FAIL: Generating the Xcode project failed") + printerr("Executing: %s" % " ".join(e.cmd)) + sys.exit(1) + + if should_run_action("test", args.build_actions): + print("** Testing %s **" % package_name) + try: + invoke_swift( + package_path=args.package_path, + swift_exec=args.swift_exec, + action="test", + products=["%sPackageTests" % package_name], + build_path=args.build_path, + multiroot_data_file=args.multiroot_data_file, + configuration=args.configuration, + env=env, + verbose=args.verbose, + ) + except subprocess.CalledProcessError as e: + printerr("FAIL: Testing %s failed" % package_name) + printerr("Executing: %s" % " ".join(e.cmd)) + sys.exit(1) + + if should_run_action("install", args.build_actions): + print("** Installing %s **" % package_name) + + swiftpm_args = get_swiftpm_options( + package_path=args.package_path, + build_path=args.build_path, + multiroot_data_file=args.multiroot_data_file, + configuration=args.configuration, + verbose=args.verbose, + ) + cmd = [args.swift_exec, "build", "--show-bin-path"] + swiftpm_args + bin_path = check_output( + cmd, env=env, capture_stderr=False, verbose=args.verbose + ).strip() + + for prefix in args.install_prefixes: + cmd = [ + "rsync", + "-a", + os.path.join(bin_path, "swift-format"), + os.path.join(prefix, "bin"), + ] + check_call(cmd, verbose=args.verbose) + + +def main(argv_prefix=[]): + args = parse_args(argv_prefix + sys.argv[1:]) + run(args) + + +if __name__ == "__main__": + main() From 4e397f4c9430a22558241a5a988388809bbddf35 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sat, 20 Jan 2024 20:16:31 -0800 Subject: [PATCH 175/332] Clean up build-script-helper.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert the file to more modern Python, remove actions that weren’t used anymore and make it more readable in general. --- build-script-helper.py | 361 ++++++++++++++++------------------------- 1 file changed, 143 insertions(+), 218 deletions(-) diff --git a/build-script-helper.py b/build-script-helper.py index b54845307..cfb70d11a 100755 --- a/build-script-helper.py +++ b/build-script-helper.py @@ -18,22 +18,33 @@ import sys import os, platform import subprocess +from pathlib import Path +from typing import List, Union, Optional # ----------------------------------------------------------------------------- # General utilities -def printerr(message: str): +def fatal_error(message: str) -> None: print(message, file=sys.stderr) + raise SystemExit(1) -def check_call(cmd, verbose, env=os.environ, **kwargs): +def printerr(message: str) -> None: + print(message, file=sys.stderr) + + +def check_call( + cmd: List[Union[str, Path]], verbose: bool, env=os.environ, **kwargs +) -> None: if verbose: print(" ".join([escape_cmd_arg(arg) for arg in cmd])) - return subprocess.check_call(cmd, env=env, stderr=subprocess.STDOUT, **kwargs) + subprocess.check_call(cmd, env=env, stderr=subprocess.STDOUT, **kwargs) -def check_output(cmd, verbose, env=os.environ, capture_stderr=True, **kwargs): +def check_output( + cmd: List[Union[str, Path]], verbose, env=os.environ, capture_stderr=True, **kwargs +) -> str: if verbose: print(" ".join([escape_cmd_arg(arg) for arg in cmd])) if capture_stderr: @@ -45,7 +56,8 @@ def check_output(cmd, verbose, env=os.environ, capture_stderr=True, **kwargs): ) -def escape_cmd_arg(arg): +def escape_cmd_arg(arg: Union[str, Path]) -> str: + arg = str(arg) if '"' in arg or " " in arg: return '"%s"' % arg.replace('"', '\\"') else: @@ -56,50 +68,14 @@ def escape_cmd_arg(arg): # SwiftPM wrappers -def update_swiftpm_dependencies(package_path, swift_exec, build_path, env, verbose): - args = [ - swift_exec, - "package", - "--package-path", - package_path, - "--scratch-path", - build_path, - "update", - ] - check_call(args, env=env, verbose=verbose) - - -def invoke_swift( - package_path, - swift_exec, - action, - products, - build_path, - multiroot_data_file, - configuration, - env, - verbose, -): - # Until rdar://53881101 is implemented, we cannot request a build of multiple - # targets simultaneously. For now, just build one product after the other. - for product in products: - invoke_swift_single_product( - package_path, - swift_exec, - action, - product, - build_path, - multiroot_data_file, - configuration, - env, - verbose, - ) - - def get_swiftpm_options( - package_path, build_path, multiroot_data_file, configuration, verbose -): - args = [ + package_path: Path, + build_path: Path, + multiroot_data_file: Optional[Path], + configuration: str, + verbose: bool, +) -> List[Union[str, Path]]: + args: List[Union[str, Path]] = [ "--package-path", package_path, "--configuration", @@ -112,15 +88,14 @@ def get_swiftpm_options( if verbose: args += ["--verbose"] if platform.system() == "Darwin": + args += ["-Xlinker", "-rpath", "-Xlinker", "/usr/lib/swift"] args += [ - "-Xlinker", - "-rpath", - "-Xlinker", - "/usr/lib/swift", "-Xlinker", "-rpath", "-Xlinker", "@executable_path/../lib/swift/macosx", + ] + args += [ "-Xlinker", "-rpath", "-Xlinker", @@ -129,24 +104,26 @@ def get_swiftpm_options( return args -def get_swiftpm_environment_variables(no_local_deps): +def get_swiftpm_environment_variables(): env = dict(os.environ) - if not no_local_deps: - env["SWIFTCI_USE_LOCAL_DEPS"] = "1" + env["SWIFTCI_USE_LOCAL_DEPS"] = "1" return env -def invoke_swift_single_product( - package_path, - swift_exec, - action, - product, - build_path, - multiroot_data_file, - configuration, +def invoke_swiftpm( + package_path: Path, + swift_exec: Path, + action: str, + product: str, + build_path: Path, + multiroot_data_file: Optional[Path], + configuration: str, env, - verbose, + verbose: bool, ): + """ + Build or test a single SwiftPM product. + """ args = [swift_exec, action] args += get_swiftpm_options( package_path, build_path, multiroot_data_file, configuration, verbose @@ -159,195 +136,143 @@ def invoke_swift_single_product( check_call(args, env=env, verbose=verbose) -def generate_xcodeproj(package_path, swift_exec, env, verbose): - package_name = os.path.basename(package_path) - xcodeproj_path = os.path.join(package_path, "%s.xcodeproj" % package_name) - args = [ - swift_exec, - "package", - "--package-path", - package_path, - "generate-xcodeproj", - "--output", - xcodeproj_path, - ] - check_call(args, env=env, verbose=verbose) +# ----------------------------------------------------------------------------- +# Actions + + +def build(args: argparse.Namespace) -> None: + print("** Building swift-format **") + env = get_swiftpm_environment_variables() + invoke_swiftpm( + package_path=args.package_path, + swift_exec=args.swift_exec, + action="build", + product="swift-format", + build_path=args.build_path, + multiroot_data_file=args.multiroot_data_file, + configuration=args.configuration, + env=env, + verbose=args.verbose, + ) + + +def test(args: argparse.Namespace) -> None: + print("** Testing swift-format **") + env = get_swiftpm_environment_variables() + invoke_swiftpm( + package_path=args.package_path, + swift_exec=args.swift_exec, + action="test", + product="swift-formatPackageTests", + build_path=args.build_path, + multiroot_data_file=args.multiroot_data_file, + configuration=args.configuration, + env=env, + verbose=args.verbose, + ) + + +def install(args: argparse.Namespace) -> None: + build(args) + + print("** Installing swift-format **") + + env = get_swiftpm_environment_variables() + swiftpm_args = get_swiftpm_options( + package_path=args.package_path, + build_path=args.build_path, + multiroot_data_file=args.multiroot_data_file, + configuration=args.configuration, + verbose=args.verbose, + ) + cmd = [args.swift_exec, "build", "--show-bin-path"] + swiftpm_args + bin_path = check_output( + cmd, env=env, capture_stderr=False, verbose=args.verbose + ).strip() + + for prefix in args.install_prefixes: + cmd = [ + "rsync", + "-a", + Path(bin_path) / "swift-format", + prefix / "bin", + ] + check_call(cmd, verbose=args.verbose) # ----------------------------------------------------------------------------- # Argument parsing -def parse_args(args): - parser = argparse.ArgumentParser(prog="build-script-helper.py") - +def add_common_args(parser: argparse.ArgumentParser) -> None: parser.add_argument("--package-path", default="") parser.add_argument( "-v", "--verbose", action="store_true", help="log executed commands" ) - parser.add_argument( - "--prefix", - dest="install_prefixes", - nargs="*", - metavar="PATHS", - help="install path", - ) parser.add_argument("--configuration", default="debug") - parser.add_argument("--build-path", default=None) + parser.add_argument("--build-path", type=Path, default=None) parser.add_argument( "--multiroot-data-file", + type=Path, help="Path to an Xcode workspace to create a unified build of SwiftSyntax with other projects.", ) parser.add_argument( "--toolchain", required=True, + type=Path, help="the toolchain to use when building this package", ) - parser.add_argument( - "--update", action="store_true", help="update all SwiftPM dependencies" - ) - parser.add_argument( - "--no-local-deps", - action="store_true", - help="use normal remote dependencies when building", + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(prog="build-script-helper.py") + subparsers = parser.add_subparsers( + title="subcommands", dest="action", required=True, metavar="action" ) - parser.add_argument( - "build_actions", - help="Extra actions to perform. Can be any number of the following", - choices=["all", "build", "test", "generate-xcodeproj", "install"], + + build_parser = subparsers.add_parser("build", help="build the package") + add_common_args(build_parser) + + test_parser = subparsers.add_parser("test", help="test the package") + add_common_args(test_parser) + + install_parser = subparsers.add_parser("install", help="install the package") + add_common_args(install_parser) + install_parser.add_argument( + "--prefix", + dest="install_prefixes", nargs="*", - default=["build"], + type=Path, + metavar="PATHS", + help="install path", ) - parsed = parser.parse_args(args) + parsed = parser.parse_args(sys.argv[1:]) - parsed.swift_exec = os.path.join(parsed.toolchain, "bin", "swift") + parsed.swift_exec = parsed.toolchain / "bin" / "swift" # Convert package_path to absolute path, relative to root of repo. - repo_path = os.path.dirname(__file__) - parsed.package_path = os.path.realpath(os.path.join(repo_path, parsed.package_path)) + repo_path = Path(__file__).parent + parsed.package_path = (repo_path / parsed.package_path).resolve() if not parsed.build_path: - parsed.build_path = os.path.join(parsed.package_path, ".build") + parsed.build_path = parsed.package_path / ".build" return parsed -def should_run_action(action_name, selected_actions): - if action_name in selected_actions: - return True - elif "all" in selected_actions: - return True - else: - return False - - -def run(args): - package_name = os.path.basename(args.package_path) - - env = get_swiftpm_environment_variables(no_local_deps=args.no_local_deps) - # Use local dependencies (i.e. checked out next swift-format). - - if args.update: - print("** Updating dependencies of %s **" % package_name) - try: - update_swiftpm_dependencies( - package_path=args.package_path, - swift_exec=args.swift_exec, - build_path=args.build_path, - env=env, - verbose=args.verbose, - ) - except subprocess.CalledProcessError as e: - printerr("FAIL: Updating dependencies of %s failed" % package_name) - printerr("Executing: %s" % " ".join(e.cmd)) - sys.exit(1) +def main(): + args = parse_args() # The test action creates its own build. No need to build if we are just testing. - if should_run_action("build", args.build_actions) or should_run_action( - "install", args.build_actions - ): - print("** Building %s **" % package_name) - try: - invoke_swift( - package_path=args.package_path, - swift_exec=args.swift_exec, - action="build", - products=["swift-format"], - build_path=args.build_path, - multiroot_data_file=args.multiroot_data_file, - configuration=args.configuration, - env=env, - verbose=args.verbose, - ) - except subprocess.CalledProcessError as e: - printerr("FAIL: Building %s failed" % package_name) - printerr("Executing: %s" % " ".join(e.cmd)) - sys.exit(1) - - output_dir = os.path.realpath(os.path.join(args.build_path, args.configuration)) - - if should_run_action("generate-xcodeproj", args.build_actions): - print("** Generating Xcode project for %s **" % package_name) - try: - generate_xcodeproj( - args.package_path, - swift_exec=args.swift_exec, - env=env, - verbose=args.verbose, - ) - except subprocess.CalledProcessError as e: - printerr("FAIL: Generating the Xcode project failed") - printerr("Executing: %s" % " ".join(e.cmd)) - sys.exit(1) - - if should_run_action("test", args.build_actions): - print("** Testing %s **" % package_name) - try: - invoke_swift( - package_path=args.package_path, - swift_exec=args.swift_exec, - action="test", - products=["%sPackageTests" % package_name], - build_path=args.build_path, - multiroot_data_file=args.multiroot_data_file, - configuration=args.configuration, - env=env, - verbose=args.verbose, - ) - except subprocess.CalledProcessError as e: - printerr("FAIL: Testing %s failed" % package_name) - printerr("Executing: %s" % " ".join(e.cmd)) - sys.exit(1) - - if should_run_action("install", args.build_actions): - print("** Installing %s **" % package_name) - - swiftpm_args = get_swiftpm_options( - package_path=args.package_path, - build_path=args.build_path, - multiroot_data_file=args.multiroot_data_file, - configuration=args.configuration, - verbose=args.verbose, - ) - cmd = [args.swift_exec, "build", "--show-bin-path"] + swiftpm_args - bin_path = check_output( - cmd, env=env, capture_stderr=False, verbose=args.verbose - ).strip() - - for prefix in args.install_prefixes: - cmd = [ - "rsync", - "-a", - os.path.join(bin_path, "swift-format"), - os.path.join(prefix, "bin"), - ] - check_call(cmd, verbose=args.verbose) - - -def main(argv_prefix=[]): - args = parse_args(argv_prefix + sys.argv[1:]) - run(args) + if args.action == "build": + build(args) + elif args.action == "test": + test(args) + elif args.action == "install": + install(args) + else: + fatal_error(f"unknown action '{args.action}'") if __name__ == "__main__": From 33158505f0ab7cde9244f407983774be136568b3 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sat, 20 Jan 2024 20:41:21 -0800 Subject: [PATCH 176/332] Allow cross-compiling swift-format This should build swift-format as a fat binary containing both an x86_64 and an arm64 slice in the open source toolchains. --- build-script-helper.py | 72 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/build-script-helper.py b/build-script-helper.py index cfb70d11a..9f06ee87c 100755 --- a/build-script-helper.py +++ b/build-script-helper.py @@ -20,6 +20,7 @@ import subprocess from pathlib import Path from typing import List, Union, Optional +import json # ----------------------------------------------------------------------------- # General utilities @@ -68,11 +69,29 @@ def escape_cmd_arg(arg: Union[str, Path]) -> str: # SwiftPM wrappers +def get_build_target(swift_exec: Path, cross_compile_config: Optional[Path]) -> str: + """Returns the target-triple of the current machine or for cross-compilation.""" + command = [swift_exec, "-print-target-info"] + if cross_compile_config: + cross_compile_json = json.load(open(cross_compile_config)) + command += ["-target", cross_compile_json["target"]] + target_info_json = subprocess.check_output( + command, stderr=subprocess.PIPE, universal_newlines=True + ).strip() + target_info = json.loads(target_info_json) + if "-apple-macosx" in target_info["target"]["unversionedTriple"]: + return target_info["target"]["unversionedTriple"] + return target_info["target"]["triple"] + + def get_swiftpm_options( + swift_exec: Path, package_path: Path, build_path: Path, multiroot_data_file: Optional[Path], configuration: str, + cross_compile_host: Optional[str], + cross_compile_config: Optional[Path], verbose: bool, ) -> List[Union[str, Path]]: args: List[Union[str, Path]] = [ @@ -87,8 +106,17 @@ def get_swiftpm_options( args += ["--multiroot-data-file", multiroot_data_file] if verbose: args += ["--verbose"] - if platform.system() == "Darwin": - args += ["-Xlinker", "-rpath", "-Xlinker", "/usr/lib/swift"] + build_target = get_build_target( + swift_exec, cross_compile_config=cross_compile_config + ) + build_os = build_target.split("-")[2] + if build_os.startswith("macosx"): + args += [ + "-Xlinker", + "-rpath", + "-Xlinker", + "/usr/lib/swift", + ] args += [ "-Xlinker", "-rpath", @@ -101,6 +129,21 @@ def get_swiftpm_options( "-Xlinker", "@executable_path/../lib/swift-5.5/macosx", ] + else: + # Library rpath for swift, dispatch, Foundation, etc. when installing + args += [ + "-Xlinker", + "-rpath", + "-Xlinker", + "$ORIGIN/../lib/swift/" + build_os, + ] + + if cross_compile_host: + if build_os.startswith("macosx") and cross_compile_host.startswith("macosx-"): + args += ["--arch", "x86_64", "--arch", "arm64"] + else: + fatal_error("cannot cross-compile for %s" % cross_compile_host) + return args @@ -118,6 +161,8 @@ def invoke_swiftpm( build_path: Path, multiroot_data_file: Optional[Path], configuration: str, + cross_compile_host: Optional[str], + cross_compile_config: Optional[Path], env, verbose: bool, ): @@ -126,7 +171,14 @@ def invoke_swiftpm( """ args = [swift_exec, action] args += get_swiftpm_options( - package_path, build_path, multiroot_data_file, configuration, verbose + swift_exec=swift_exec, + package_path=package_path, + build_path=build_path, + multiroot_data_file=multiroot_data_file, + configuration=configuration, + cross_compile_host=cross_compile_host, + cross_compile_config=cross_compile_config, + verbose=verbose, ) if action == "test": args += ["--test-product", product, "--disable-testable-imports"] @@ -151,6 +203,8 @@ def build(args: argparse.Namespace) -> None: build_path=args.build_path, multiroot_data_file=args.multiroot_data_file, configuration=args.configuration, + cross_compile_host=args.cross_compile_host, + cross_compile_config=args.cross_compile_config, env=env, verbose=args.verbose, ) @@ -167,6 +221,8 @@ def test(args: argparse.Namespace) -> None: build_path=args.build_path, multiroot_data_file=args.multiroot_data_file, configuration=args.configuration, + cross_compile_host=args.cross_compile_host, + cross_compile_config=args.cross_compile_config, env=env, verbose=args.verbose, ) @@ -179,10 +235,13 @@ def install(args: argparse.Namespace) -> None: env = get_swiftpm_environment_variables() swiftpm_args = get_swiftpm_options( + swift_exec=args.swift_exec, package_path=args.package_path, build_path=args.build_path, multiroot_data_file=args.multiroot_data_file, configuration=args.configuration, + cross_compile_host=args.cross_compile_host, + cross_compile_config=args.cross_compile_config, verbose=args.verbose, ) cmd = [args.swift_exec, "build", "--show-bin-path"] + swiftpm_args @@ -222,6 +281,13 @@ def add_common_args(parser: argparse.ArgumentParser) -> None: type=Path, help="the toolchain to use when building this package", ) + parser.add_argument( + "--cross-compile-host", help="cross-compile for another host instead" + ) + parser.add_argument( + "--cross-compile-config", + help="an SPM JSON destination file containing Swift cross-compilation flags", + ) def parse_args() -> argparse.Namespace: From 7b6dc0e45a921da8a4c55262838fa914c588367f Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 22 Jan 2024 20:09:32 -0800 Subject: [PATCH 177/332] =?UTF-8?q?Don=E2=80=99t=20include=20the=20toolcha?= =?UTF-8?q?in=20rpath=20when=20installing=20swift-format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When installing swift-format on Linux, we don’t want it to have an absolute rpath to where the stdlib was installed on the builder machine. --- Package.swift | 21 ++++++++++++++++++++- build-script-helper.py | 10 ++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index 9b74aa635..4e40fee7b 100644 --- a/Package.swift +++ b/Package.swift @@ -14,6 +14,24 @@ import Foundation import PackageDescription +// MARK: - Parse build arguments + +func hasEnvironmentVariable(_ name: String) -> Bool { + return ProcessInfo.processInfo.environment[name] != nil +} + +// When building the toolchain on the CI, don't add the CI's runpath for the +// final build before installing. +let installAction = hasEnvironmentVariable("SOURCEKIT_LSP_CI_INSTALL") + + +// MARK: - Compute custom build settings + +var swiftformatLinkSettings: [LinkerSetting] = [] +if installAction { + swiftformatLinkSettings += [.unsafeFlags(["-no-toolchain-stdlib-rpath"], .when(platforms: [.linux, .android]))] +} + let package = Package( name: "swift-format", platforms: [ @@ -106,7 +124,8 @@ let package = Package( .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), - ] + ], + linkerSettings: swiftformatLinkSettings ), .testTarget( diff --git a/build-script-helper.py b/build-script-helper.py index 9f06ee87c..09013f74d 100755 --- a/build-script-helper.py +++ b/build-script-helper.py @@ -147,9 +147,11 @@ def get_swiftpm_options( return args -def get_swiftpm_environment_variables(): +def get_swiftpm_environment_variables(action: str): env = dict(os.environ) env["SWIFTCI_USE_LOCAL_DEPS"] = "1" + if action == "install": + env["SOURCEKIT_LSP_CI_INSTALL"] = "1" return env @@ -194,7 +196,7 @@ def invoke_swiftpm( def build(args: argparse.Namespace) -> None: print("** Building swift-format **") - env = get_swiftpm_environment_variables() + env = get_swiftpm_environment_variables(args.action) invoke_swiftpm( package_path=args.package_path, swift_exec=args.swift_exec, @@ -212,7 +214,7 @@ def build(args: argparse.Namespace) -> None: def test(args: argparse.Namespace) -> None: print("** Testing swift-format **") - env = get_swiftpm_environment_variables() + env = get_swiftpm_environment_variables(args.action) invoke_swiftpm( package_path=args.package_path, swift_exec=args.swift_exec, @@ -233,7 +235,7 @@ def install(args: argparse.Namespace) -> None: print("** Installing swift-format **") - env = get_swiftpm_environment_variables() + env = get_swiftpm_environment_variables(args.action) swiftpm_args = get_swiftpm_options( swift_exec=args.swift_exec, package_path=args.package_path, From 130434b244b63ab7fffb07e5bc88f07820563f1a Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 24 Jan 2024 16:00:41 -0800 Subject: [PATCH 178/332] Order package manifest to start with the package declaration Instead of starting with environment variable parsing, start with the actual package manifest, which is most likely the most interesting to most users. --- Package.swift | 87 +++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/Package.swift b/Package.swift index 4e40fee7b..1b78b1fb8 100644 --- a/Package.swift +++ b/Package.swift @@ -14,24 +14,6 @@ import Foundation import PackageDescription -// MARK: - Parse build arguments - -func hasEnvironmentVariable(_ name: String) -> Bool { - return ProcessInfo.processInfo.environment[name] != nil -} - -// When building the toolchain on the CI, don't add the CI's runpath for the -// final build before installing. -let installAction = hasEnvironmentVariable("SOURCEKIT_LSP_CI_INSTALL") - - -// MARK: - Compute custom build settings - -var swiftformatLinkSettings: [LinkerSetting] = [] -if installAction { - swiftformatLinkSettings += [.unsafeFlags(["-no-toolchain-stdlib-rpath"], .when(platforms: [.linux, .android]))] -} - let package = Package( name: "swift-format", platforms: [ @@ -56,9 +38,7 @@ let package = Package( targets: ["Lint Source Code"] ), ], - dependencies: [ - // See the "Dependencies" section below. - ], + dependencies: dependencies, targets: [ .target( name: "_SwiftFormatInstructionCounter" @@ -156,28 +136,47 @@ let package = Package( ] ) -// MARK: Dependencies +// MARK: - Parse build arguments -if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { - // Building standalone. - package.dependencies += [ - .package( - url: "https://github.com/apple/swift-argument-parser.git", - from: "1.2.2" - ), - .package( - url: "https://github.com/apple/swift-markdown.git", - from: "0.2.0" - ), - .package( - url: "https://github.com/apple/swift-syntax.git", - branch: "main" - ), - ] -} else { - package.dependencies += [ - .package(path: "../swift-argument-parser"), - .package(path: "../swift-markdown"), - .package(path: "../swift-syntax"), - ] +func hasEnvironmentVariable(_ name: String) -> Bool { + return ProcessInfo.processInfo.environment[name] != nil +} + +// When building the toolchain on the CI, don't add the CI's runpath for the +// final build before installing. +var installAction: Bool { hasEnvironmentVariable("SOURCEKIT_LSP_CI_INSTALL") } + +/// Assume that all the package dependencies are checked out next to sourcekit-lsp and use that instead of fetching a +/// remote dependency. +var useLocalDependencies: Bool { hasEnvironmentVariable("SWIFTCI_USE_LOCAL_DEPS") } + +// MARK: - Dependencies + +var dependencies: [Package.Dependency] { + if useLocalDependencies { + return [ + .package(path: "../swift-argument-parser"), + .package(path: "../swift-markdown"), + .package(path: "../swift-syntax"), + ] + } else { + return [ + .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2"), + .package(url: "https://github.com/apple/swift-markdown.git", from: "0.2.0"), + .package(url: "https://github.com/apple/swift-syntax.git", branch: "main"), + ] + } } + + + +// MARK: - Compute custom build settings + +var swiftformatLinkSettings: [LinkerSetting] { + if installAction { + return [.unsafeFlags(["-no-toolchain-stdlib-rpath"], .when(platforms: [.linux, .android]))] + } else { + return [] + } +} + From 623cb175be14ef665d7decfb6390449710728bfe Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 24 Jan 2024 18:13:12 -0800 Subject: [PATCH 179/332] Clean up rpaths of installed swift-format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Don’t explicitly add rpaths to `/usr/lib/swift`, `@executable_path/../lib/swift/macosx` and `@executable_path/../lib/swift-5.5/macosx` on Darwin. I don’t know what they were needed for but they don’t seem to be necessary. Also standard Swift command line tools created from Xcode don’t contain these rpaths. - Increase the deployment target to macOS 12.0. This removes an rpath in the built binary that is an absolute path to the toolchain on the host, which was used to build swift-format. - Set `--disable-local-rpath` on Linux. Otherwise the swift-format executable has an `$ORIGIN` rpath on Linux, which we don’t want. - Rename `SOURCEKIT_LSP_CI_INSTALL` -> `SWIFTFORMAT_CI_INSTALL`. Just cleaning up after copy-pasting. rdar://121400644 --- Package.swift | 6 +++--- build-script-helper.py | 24 +++--------------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/Package.swift b/Package.swift index 1b78b1fb8..9a1995039 100644 --- a/Package.swift +++ b/Package.swift @@ -17,8 +17,8 @@ import PackageDescription let package = Package( name: "swift-format", platforms: [ - .iOS("13.0"), - .macOS("10.15") + .macOS("12.0"), + .iOS("13.0") ], products: [ .executable( @@ -144,7 +144,7 @@ func hasEnvironmentVariable(_ name: String) -> Bool { // When building the toolchain on the CI, don't add the CI's runpath for the // final build before installing. -var installAction: Bool { hasEnvironmentVariable("SOURCEKIT_LSP_CI_INSTALL") } +var installAction: Bool { hasEnvironmentVariable("SWIFTFORMAT_CI_INSTALL") } /// Assume that all the package dependencies are checked out next to sourcekit-lsp and use that instead of fetching a /// remote dependency. diff --git a/build-script-helper.py b/build-script-helper.py index 09013f74d..051b01bb8 100755 --- a/build-script-helper.py +++ b/build-script-helper.py @@ -110,26 +110,7 @@ def get_swiftpm_options( swift_exec, cross_compile_config=cross_compile_config ) build_os = build_target.split("-")[2] - if build_os.startswith("macosx"): - args += [ - "-Xlinker", - "-rpath", - "-Xlinker", - "/usr/lib/swift", - ] - args += [ - "-Xlinker", - "-rpath", - "-Xlinker", - "@executable_path/../lib/swift/macosx", - ] - args += [ - "-Xlinker", - "-rpath", - "-Xlinker", - "@executable_path/../lib/swift-5.5/macosx", - ] - else: + if not build_os.startswith("macosx"): # Library rpath for swift, dispatch, Foundation, etc. when installing args += [ "-Xlinker", @@ -137,6 +118,7 @@ def get_swiftpm_options( "-Xlinker", "$ORIGIN/../lib/swift/" + build_os, ] + args += ['--disable-local-rpath'] if cross_compile_host: if build_os.startswith("macosx") and cross_compile_host.startswith("macosx-"): @@ -151,7 +133,7 @@ def get_swiftpm_environment_variables(action: str): env = dict(os.environ) env["SWIFTCI_USE_LOCAL_DEPS"] = "1" if action == "install": - env["SOURCEKIT_LSP_CI_INSTALL"] = "1" + env["SWIFTFORMAT_CI_INSTALL"] = "1" return env From c70ff3f24e14e6e2e9e000b12b36970c6290cf12 Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Fri, 26 Jan 2024 20:34:49 -0800 Subject: [PATCH 180/332] [CI] Only use 'required' for Python >= 3.7 --- build-script-helper.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build-script-helper.py b/build-script-helper.py index 09013f74d..5a0372163 100755 --- a/build-script-helper.py +++ b/build-script-helper.py @@ -294,9 +294,10 @@ def add_common_args(parser: argparse.ArgumentParser) -> None: def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(prog="build-script-helper.py") - subparsers = parser.add_subparsers( - title="subcommands", dest="action", required=True, metavar="action" - ) + if sys.version_info >= (3, 7, 0): + subparsers = parser.add_subparsers(title="subcommands", dest="action", required=True, metavar="action") + else: + subparsers = parser.add_subparsers(title="subcommands", dest="action", metavar="action") build_parser = subparsers.add_parser("build", help="build the package") add_common_args(build_parser) From e312ede68bd8549381f9e2edfef7e6c176e9a50e Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Tue, 9 Jan 2024 08:34:08 -0800 Subject: [PATCH 181/332] build: introduce a CMake based build for swift-format This is in preparation to use SwiftFormat from SourceKit-LSP which is distributed as part of the toolchain. On Windows, we are now able to build swift-format against the shared Swift Syntax package, yielding an overall size reduction: SPM swift-format.exe: 75,683,840 b CMake swift-format.exe: 830,464 b SwiftFormat.dll: 7,818,240 b Net Savings: 67,035,136 b --- CMakeLists.txt | 92 +++++++++++++++ Sources/CMakeLists.txt | 12 ++ Sources/SwiftFormat/CMakeLists.txt | 105 +++++++++++++++++ .../CMakeLists.txt | 13 +++ .../include/module.modulemap | 3 + Sources/swift-format/CMakeLists.txt | 37 ++++++ cmake/modules/CMakeLists.txt | 20 ++++ cmake/modules/SwiftFormatConfig.cmake.in | 12 ++ cmake/modules/SwiftSupport.cmake | 109 ++++++++++++++++++ 9 files changed, 403 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 Sources/CMakeLists.txt create mode 100644 Sources/SwiftFormat/CMakeLists.txt create mode 100644 Sources/_SwiftFormatInstructionCounter/CMakeLists.txt create mode 100644 Sources/_SwiftFormatInstructionCounter/include/module.modulemap create mode 100644 Sources/swift-format/CMakeLists.txt create mode 100644 cmake/modules/CMakeLists.txt create mode 100644 cmake/modules/SwiftFormatConfig.cmake.in create mode 100644 cmake/modules/SwiftSupport.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..00a9f061b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,92 @@ +#[[ +This source file is part of the swift-format open source project + +Copyright (c) 2024 Apple Inc. and the swift-format project authors +Licensed under Apache License v2.0 with Runtime Library Exception + +See https://swift.org/LICENSE.txt for license information +#]] + +cmake_minimum_required(VERSION 3.19.0) + +if(POLICY CMP0077) + cmake_policy(SET CMP0077 NEW) +endif() +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() + +project(SwiftFormat + LANGUAGES C Swift) + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) +set(CMAKE_Swift_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY MultiThreadedDLL) + +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) + +include(FetchContent) +include(GNUInstallDirs) +include(SwiftSupport) + +find_package(Foundation CONFIG) + +set(_SF_VENDOR_DEPENDENCIES) + +set(BUILD_EXAMPLES NO) +set(BUILD_TESTING NO) + +find_package(ArgumentParser CONFIG) +if(NOT ArgumentParser_FOUND) + FetchContent_Declare(ArgumentParser + GIT_REPOSITORY https://github.com/apple/swift-argument-parser + GIT_TAG 1.2.3) + list(APPEND _SF_VENDOR_DEPENDENCIES ArgumentParser) +endif() + +find_package(cmark-gfm CONFIG) +if(NOT cmark-gfm_FOUND) + FetchContent_Declare(cmark-gfm + GIT_REPOSITORY https://github.com/apple/swift-cmark + GIT_TAG gfm) + list(APPEND _SF_VENDOR_DEPENDENCIES cmark-gfm) +endif() + +find_package(SwiftMarkdown CONFIG) +if(NOT SwiftMarkdown_FOUND) + # TODO(compnerd) we need a latest version for now as we need the CMake support + # which is untagged. + FetchContent_Declare(Markdown + GIT_REPOSITORY https://github.com/apple/swift-markdown + GIT_TAG main) + list(APPEND _SF_VENDOR_DEPENDENCIES Markdown) +endif() + +find_package(SwiftSyntax CONFIG) +if(NOT SwiftSyntax_FOUND) + FetchContent_Declare(Syntax + GIT_REPOSITORY https://github.com/apple/swift-syntax + GIT_TAG main) + list(APPEND _SF_VENDOR_DEPENDENCIES Syntax) +endif() + +if(_SF_VENDOR_DEPENDENCIES) + FetchContent_MakeAvailable(${_SF_VENDOR_DEPENDENCIES}) + + if(NOT TARGET SwiftMarkdown::Markdown) + add_library(SwiftMarkdown::Markdown ALIAS Markdown) + endif() + + if(NOT TARGET SwiftSyntax::SwiftSyntax) + add_library(SwiftSyntax::SwiftSyntax ALIAS SwiftSyntax) + add_library(SwiftSyntax::SwiftSyntaxBuilder ALIAS SwiftSyntaxBuilder) + add_library(SwiftSyntax::SwiftOperators ALIAS SwiftOperators) + add_library(SwiftSyntax::SwiftParser ALIAS SwiftParser) + add_library(SwiftSyntax::SwiftParserDiagnostics ALIAS SwiftParserDiagnostics) + endif() +endif() + +add_subdirectory(Sources) diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt new file mode 100644 index 000000000..32fdad63e --- /dev/null +++ b/Sources/CMakeLists.txt @@ -0,0 +1,12 @@ +#[[ +This source file is part of the swift-format open source project + +Copyright (c) 2024 Apple Inc. and the swift-format project authors +Licensed under Apache License v2.0 with Runtime Library Exception + +See https://swift.org/LICENSE.txt for license information +#]] + +add_subdirectory(SwiftFormat) +add_subdirectory(_SwiftFormatInstructionCounter) +add_subdirectory(swift-format) diff --git a/Sources/SwiftFormat/CMakeLists.txt b/Sources/SwiftFormat/CMakeLists.txt new file mode 100644 index 000000000..4ce86a341 --- /dev/null +++ b/Sources/SwiftFormat/CMakeLists.txt @@ -0,0 +1,105 @@ +#[[ +This source file is part of the swift-format open source project + +Copyright (c) 2024 Apple Inc. and the swift-format project authors +Licensed under Apache License v2.0 with Runtime Library Exception + +See https://swift.org/LICENSE.txt for license information +#]] + +add_library(SwiftFormat + API/Configuration+Default.swift + API/Configuration.swift + API/DebugOptions.swift + API/Finding.swift + API/FindingCategorizing.swift + API/Indent.swift + API/SwiftFormatError.swift + API/SwiftFormatter.swift + API/SwiftLinter.swift + Core/Context.swift + Core/DocumentationComment.swift + Core/DocumentationCommentText.swift + Core/Finding+Convenience.swift + Core/FindingEmitter.swift + Core/FormatPipeline.swift + Core/FunctionDeclSyntax+Convenience.swift + Core/ImportsXCTestVisitor.swift + Core/LazySplitSequence.swift + Core/LintPipeline.swift + Core/ModifierListSyntax+Convenience.swift + Core/Parsing.swift + Core/Pipelines+Generated.swift + Core/RememberingIterator.swift + Core/Rule.swift + Core/RuleBasedFindingCategory.swift + Core/RuleMask.swift + Core/RuleNameCache+Generated.swift + Core/RuleRegistry+Generated.swift + Core/RuleState.swift + Core/SyntaxFormatRule.swift + Core/SyntaxLintRule.swift + Core/SyntaxProtocol+Convenience.swift + Core/Trivia+Convenience.swift + Core/WithSemicolonSyntax.swift + PrettyPrint/Comment.swift + PrettyPrint/Indent+Length.swift + PrettyPrint/PrettyPrint.swift + PrettyPrint/PrettyPrintFindingCategory.swift + PrettyPrint/Token.swift + PrettyPrint/TokenStreamCreator.swift + PrettyPrint/Verbatim.swift + PrettyPrint/WhitespaceFindingCategory.swift + PrettyPrint/WhitespaceLinter.swift + Rules/AllPublicDeclarationsHaveDocumentation.swift + Rules/AlwaysUseLiteralForEmptyCollectionInit.swift + Rules/AlwaysUseLowerCamelCase.swift + Rules/AmbiguousTrailingClosureOverload.swift + Rules/BeginDocumentationCommentWithOneLineSummary.swift + Rules/DoNotUseSemicolons.swift + Rules/DontRepeatTypeInStaticProperties.swift + Rules/FileScopedDeclarationPrivacy.swift + Rules/FullyIndirectEnum.swift + Rules/GroupNumericLiterals.swift + Rules/IdentifiersMustBeASCII.swift + Rules/NeverForceUnwrap.swift + Rules/NeverUseForceTry.swift + Rules/NeverUseImplicitlyUnwrappedOptionals.swift + Rules/NoAccessLevelOnExtensionDeclaration.swift + Rules/NoAssignmentInExpressions.swift + Rules/NoBlockComments.swift + Rules/NoCasesWithOnlyFallthrough.swift + Rules/NoEmptyTrailingClosureParentheses.swift + Rules/NoLabelsInCasePatterns.swift + Rules/NoLeadingUnderscores.swift + Rules/NoParensAroundConditions.swift + Rules/NoPlaygroundLiterals.swift + Rules/NoVoidReturnOnFunctionSignature.swift + Rules/OmitExplicitReturns.swift + Rules/OneCasePerLine.swift + Rules/OneVariableDeclarationPerLine.swift + Rules/OnlyOneTrailingClosureArgument.swift + Rules/OrderedImports.swift + Rules/ReplaceForEachWithForLoop.swift + Rules/ReturnVoidInsteadOfEmptyTuple.swift + Rules/TypeNamesShouldBeCapitalized.swift + Rules/UseEarlyExits.swift + Rules/UseExplicitNilCheckInConditions.swift + Rules/UseLetInEveryBoundCaseVariable.swift + Rules/UseShorthandTypeNames.swift + Rules/UseSingleLinePropertyGetter.swift + Rules/UseSynthesizedInitializer.swift + Rules/UseTripleSlashForDocumentationComments.swift + Rules/UseWhereClausesInForLoops.swift + Rules/ValidateDocumentationComments.swift) +target_link_libraries(SwiftFormat PUBLIC + SwiftMarkdown::Markdown + SwiftSyntax::SwiftSyntax + SwiftSyntax::SwiftSyntaxBuilder + SwiftSyntax::SwiftOperators + SwiftSyntax::SwiftParser + SwiftSyntax::SwiftParserDiagnostics + libcmark-gfm + libcmark-gfm-extensions) + +_install_target(SwiftFormat) diff --git a/Sources/_SwiftFormatInstructionCounter/CMakeLists.txt b/Sources/_SwiftFormatInstructionCounter/CMakeLists.txt new file mode 100644 index 000000000..77f863786 --- /dev/null +++ b/Sources/_SwiftFormatInstructionCounter/CMakeLists.txt @@ -0,0 +1,13 @@ +#[[ +This source file is part of the swift-format open source project + +Copyright (c) 2024 Apple Inc. and the swift-format project authors +Licensed under Apache License v2.0 with Runtime Library Exception + +See https://swift.org/LICENSE.txt for license information +#]] + +add_library(_SwiftFormatInstructionCounter STATIC + src/InstructionsExecuted.c) +target_include_directories(_SwiftFormatInstructionCounter PUBLIC + include) diff --git a/Sources/_SwiftFormatInstructionCounter/include/module.modulemap b/Sources/_SwiftFormatInstructionCounter/include/module.modulemap new file mode 100644 index 000000000..afe65db85 --- /dev/null +++ b/Sources/_SwiftFormatInstructionCounter/include/module.modulemap @@ -0,0 +1,3 @@ +module _SwiftFormatInstructionCounter { + header "InstructionsExecuted.h" +} diff --git a/Sources/swift-format/CMakeLists.txt b/Sources/swift-format/CMakeLists.txt new file mode 100644 index 000000000..982e93d71 --- /dev/null +++ b/Sources/swift-format/CMakeLists.txt @@ -0,0 +1,37 @@ +#[[ +This source file is part of the swift-format open source project + +Copyright (c) 2024 Apple Inc. and the swift-format project authors +Licensed under Apache License v2.0 with Runtime Library Exception + +See https://swift.org/LICENSE.txt for license information +#]] + +add_executable(swift-format + PrintVersion.swift + SwiftFormatCommand.swift + VersionOptions.swift + Frontend/ConfigurationLoader.swift + Frontend/FormatFrontend.swift + Frontend/Frontend.swift + Frontend/LintFrontend.swift + Subcommands/DumpConfiguration.swift + Subcommands/Format.swift + Subcommands/Lint.swift + Subcommands/LintFormatOptions.swift + Subcommands/PerformanceMeasurement.swift + Utilities/Diagnostic.swift + Utilities/DiagnosticsEngine.swift + Utilities/FileHandleTextOutputStream.swift + Utilities/FileIterator.swift + Utilities/FormatError.swift + Utilities/StderrDiagnosticPrinter.swift + Utilities/TTY.swift) +target_link_libraries(swift-format PRIVATE + _SwiftFormatInstructionCounter + ArgumentParser + SwiftFormat + SwiftParser + SwiftSyntax) + +_install_target(swift-format) diff --git a/cmake/modules/CMakeLists.txt b/cmake/modules/CMakeLists.txt new file mode 100644 index 000000000..195e3625c --- /dev/null +++ b/cmake/modules/CMakeLists.txt @@ -0,0 +1,20 @@ +#[[ +This source file is part of the swift-format open source project + +Copyright (c) 2024 Apple Inc. and the swift-format project authors +Licensed under Apache License v2.0 with Runtime Library Exception + +See https://swift.org/LICENSE.txt for license information +#]] + +set(SWIFT_FORMAT_EXPORTS_FILE + ${CMAKE_CURRENT_BINARY_DIR}/SwiftFormatExports.cmake) + +configure_file(SwiftFormatConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/SwiftFormatConfig.cmake) + +get_property(SWIFT_FORMAT_EXPORTS GLOBAL PROPERTY SWIFT_FORMAT_EXPORTS) +export(TARGETS ${SWIFT_FORMAT_EXPORTS} + NAMESPACE SwiftFormat:: + FILE ${SWIFT_FORMAT_EXPORTS_FILE} + EXPORT_LINK_INTERFACE_LIBRARIES) diff --git a/cmake/modules/SwiftFormatConfig.cmake.in b/cmake/modules/SwiftFormatConfig.cmake.in new file mode 100644 index 000000000..4c165f9cf --- /dev/null +++ b/cmake/modules/SwiftFormatConfig.cmake.in @@ -0,0 +1,12 @@ +#[[ +This source file is part of the swift-format open source project + +Copyright (c) 2024 Apple Inc. and the swift-format project authors +Licensed under Apache License v2.0 with Runtime Library Exception + +See https://swift.org/LICENSE.txt for license information +#]] + +if(NOT TARGET SwiftFormat) + include("@SWIFT_FORMAT_EXPORTS_FILE@") +endif() diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake new file mode 100644 index 000000000..35decfcca --- /dev/null +++ b/cmake/modules/SwiftSupport.cmake @@ -0,0 +1,109 @@ +#[[ +This source file is part of the swift-format open source project + +Copyright (c) 2024 Apple Inc. and the swift-format project authors +Licensed under Apache License v2.0 with Runtime Library Exception + +See https://swift.org/LICENSE.txt for license information +#]] + +# Returns the current architecture name in a variable +# +# Usage: +# get_swift_host_arch(result_var_name) +# +# If the current architecture is supported by Swift, sets ${result_var_name} +# with the sanitized host architecture name derived from CMAKE_SYSTEM_PROCESSOR. +function(get_swift_host_arch result_var_name) + if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") + set("${result_var_name}" "x86_64" PARENT_SCOPE) + elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "AArch64|aarch64|arm64|ARM64") + if(CMAKE_SYSTEM_NAME MATCHES Darwin) + set("${result_var_name}" "arm64" PARENT_SCOPE) + else() + set("${result_var_name}" "aarch64" PARENT_SCOPE) + endif() + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64") + set("${result_var_name}" "powerpc64" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64le") + set("${result_var_name}" "powerpc64le" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "s390x") + set("${result_var_name}" "s390x" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv6l") + set("${result_var_name}" "armv6" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7l") + set("${result_var_name}" "armv7" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7-a") + set("${result_var_name}" "armv7" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "amd64") + set("${result_var_name}" "amd64" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64") + set("${result_var_name}" "x86_64" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "IA64") + set("${result_var_name}" "itanium" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86") + set("${result_var_name}" "i686" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "i686") + set("${result_var_name}" "i686" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "wasm32") + set("${result_var_name}" "wasm32" PARENT_SCOPE) + else() + message(FATAL_ERROR "Unrecognized architecture on host system: ${CMAKE_SYSTEM_PROCESSOR}") + endif() +endfunction() + +# Returns the os name in a variable +# +# Usage: +# get_swift_host_os(result_var_name) +# +# +# Sets ${result_var_name} with the converted OS name derived from +# CMAKE_SYSTEM_NAME. +function(get_swift_host_os result_var_name) + if(CMAKE_SYSTEM_NAME STREQUAL Darwin) + set(${result_var_name} macosx PARENT_SCOPE) + else() + string(TOLOWER ${CMAKE_SYSTEM_NAME} cmake_system_name_lc) + set(${result_var_name} ${cmake_system_name_lc} PARENT_SCOPE) + endif() +endfunction() + +function(_install_target module) + get_swift_host_os(swift_os) + get_target_property(type ${module} TYPE) + + if(type STREQUAL STATIC_LIBRARY) + set(swift swift_static) + else() + set(swift swift) + endif() + + install(TARGETS ${module} + ARCHIVE DESTINATION lib/${swift}/${swift_os} + LIBRARY DESTINATION lib/${swift}/${swift_os} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + if(type STREQUAL EXECUTABLE) + return() + endif() + + get_swift_host_arch(swift_arch) + get_target_property(module_name ${module} Swift_MODULE_NAME) + if(NOT module_name) + set(module_name ${module}) + endif() + + if(CMAKE_SYSTEM_NAME STREQUAL Darwin) + install(FILES $/${module_name}.swiftdoc + DESTINATION lib/${swift}/${swift_os}/${module_name}.swiftmodule + RENAME ${swift_arch}.swiftdoc) + install(FILES $/${module_name}.swiftmodule + DESTINATION lib/${swift}/${swift_os}/${module_name}.swiftmodule + RENAME ${swift_arch}.swiftmodule) + else() + install(FILES + $/${module_name}.swiftdoc + $/${module_name}.swiftmodule + DESTINATION lib/${swift}/${swift_os}/${swift_arch}) + endif() +endfunction() From e4e6eaa7ebb747777902879d8717962fd176f05d Mon Sep 17 00:00:00 2001 From: TTOzzi Date: Sat, 3 Feb 2024 03:48:40 +0900 Subject: [PATCH 182/332] Ignore sentence terminators inside quotes when applying the 'BeginDocumentationCommentWithOneLineSummary' option --- ...cumentationCommentWithOneLineSummary.swift | 8 +++++++- ...tationCommentWithOneLineSummaryTests.swift | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift index 7ab8096ac..f1be5fa2f 100644 --- a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift +++ b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift @@ -130,8 +130,14 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { in: text.startIndex.. Date: Sun, 4 Feb 2024 12:54:48 +0900 Subject: [PATCH 183/332] Add examples for single smart qoutes and double straight quotes --- ...mentationCommentWithOneLineSummaryTests.swift | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift index fa94c2965..e41425930 100644 --- a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift +++ b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift @@ -147,15 +147,25 @@ final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTe /// Creates an instance with the same raw value as `x` failing iff `x.kind != Subject.kind`. struct TestBackTick {} + /// A set of `Diagnostic` that can answer the question ‘was there an error?’ in O(1). + struct TestSingleSmartQuotes {} + /// A set of `Diagnostic` that can answer the question 'was there an error?' in O(1). - struct TestSingleQuotes {} + struct TestSingleStraightQuotes {} /// A set of `Diagnostic` that can answer the question “was there an error?” in O(1). - struct TestDoubleQuotes {} + struct TestDoubleSmartQuotes {} + + /// A set of `Diagnostic` that can answer the question "was there an error?" in O(1). + struct TestDoubleStraightQuotes {} /// A set of `Diagnostic` that can answer the question “was there /// an error?” in O(1). - struct TestTwoLinesDoubleQuotes {} + struct TestTwoLinesDoubleSmartQuotes {} + + /// A set of `Diagnostic` that can answer the question "was there + /// an error?" in O(1). + struct TestTwoLinesDoubleStraightQuotes {} """ ) } From c0a3702c488dd8c9ef42cda628e276a3d0bfe514 Mon Sep 17 00:00:00 2001 From: TTOzzi Date: Sun, 4 Feb 2024 12:57:25 +0900 Subject: [PATCH 184/332] Migrate deprecated NSLinguisticTagger API to Natural Language framework --- ...cumentationCommentWithOneLineSummary.swift | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift index f1be5fa2f..2195bd796 100644 --- a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift +++ b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift @@ -11,6 +11,9 @@ //===----------------------------------------------------------------------===// import Foundation +#if os(macOS) +import NaturalLanguage +#endif import SwiftSyntax /// All documentation comments must begin with a one-line summary of the declaration. @@ -125,19 +128,31 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { } var sentences = [String]() + var tags = [NLTag]() var tokenRanges = [Range]() - let tags = text.linguisticTags( + + let tagger = NLTagger(tagSchemes: [.lexicalClass]) + tagger.string = text + tagger.enumerateTags( in: text.startIndex.. ( From cc15dc6e2ee2723bd900865958a44993d853bc3c Mon Sep 17 00:00:00 2001 From: Julia DeMille Date: Thu, 25 Jan 2024 17:23:17 -0600 Subject: [PATCH 185/332] Find config based on cwd when reading from stdin This change is useful for formatting clients that prefer to format via stdio. The signature of `ConfigurationLoader.configuration(forSwiftFileAt:URL)` was changed to `configuration(forPath:URL)` since the function is useful not just for Swift files, but any arbitrary file path. Signed-off-by: Julia DeMille --- .../Frontend/ConfigurationLoader.swift | 6 ++--- Sources/swift-format/Frontend/Frontend.swift | 23 +++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Sources/swift-format/Frontend/ConfigurationLoader.swift b/Sources/swift-format/Frontend/ConfigurationLoader.swift index 56154b090..57d9bb63b 100644 --- a/Sources/swift-format/Frontend/ConfigurationLoader.swift +++ b/Sources/swift-format/Frontend/ConfigurationLoader.swift @@ -19,13 +19,13 @@ struct ConfigurationLoader { /// The cache of previously loaded configurations. private var cache = [String: Configuration]() - /// Returns the configuration found by searching in the directory (and ancestor directories) - /// containing the given `.swift` source file. + /// Returns the configuration found by walking up the file tree from `url`. + /// This function works for both files and directories. /// /// If no configuration file was found during the search, this method returns nil. /// /// - Throws: If a configuration file was found but an error occurred loading it. - mutating func configuration(forSwiftFileAt url: URL) throws -> Configuration? { + mutating func configuration(forPath url: URL) throws -> Configuration? { guard let configurationFileURL = Configuration.url(forConfigurationFileApplyingTo: url) else { return nil diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index 2d5274160..99ecef090 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -213,7 +213,7 @@ class Frontend { // then try to load the configuration by inferring it based on the source file path. if let swiftFileURL = swiftFileURL { do { - if let configuration = try configurationLoader.configuration(forSwiftFileAt: swiftFileURL) { + if let configuration = try configurationLoader.configuration(forPath: swiftFileURL) { self.checkForUnrecognizedRules(in: configuration) return configuration } @@ -223,11 +223,26 @@ class Frontend { "Unable to read configuration for \(swiftFileURL.path): \(error.localizedDescription)") return nil } + } else { + // If reading from stdin and no explicit configuration file was given, + // walk up the file tree from the cwd to find a config. + + let cwd = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + // Definitely a Swift file. Definitely not a directory. Shhhhhh. + do { + if let configuration = try configurationLoader.configuration(forPath: cwd) { + self.checkForUnrecognizedRules(in: configuration) + return configuration + } + } catch { + diagnosticsEngine.emitError( + "Unable to read configuration for \(cwd): \(error.localizedDescription)") + return nil + } } - // If neither path was given (for example, formatting standard input with no assumed filename) - // or if there was no configuration found by inferring it from the source file path, return the - // default configuration. + // An explicit configuration has not been given, and one cannot be found. + // Return the default configuration. return Configuration() } From 12dae5b0d74942d04fc5557e89c678c6e3d945c0 Mon Sep 17 00:00:00 2001 From: Justin Shacklette Date: Wed, 7 Feb 2024 11:33:26 -0700 Subject: [PATCH 186/332] fix readme typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05d5d9b83..db92fa2a2 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Run `brew install swift-format` to install the latest version. ### Building from source -Install swift-fromat using the following commands: +Install `swift-format` using the following commands: ```sh VERSION=509.0.0 # replace this with the version you need From 3d44a2f01054cbd567e1c073b0684e978b082872 Mon Sep 17 00:00:00 2001 From: David Monagle Date: Mon, 4 Mar 2024 22:57:34 +1100 Subject: [PATCH 187/332] Renamed plugin configuration parameter As trivial as this is, this change is to prevent a conflict with running the plugin with the `--configuration` parameter which is recognised and parsed by the Swift Package Manager and expects to be either `debug` or `release`. The replacement of `--swift-format-configuration` is verbose, but other choices looked too ambiguous or confusing whereas this is at least clear as to what the parameter is doing. --- Plugins/FormatPlugin/plugin.swift | 4 ++-- Plugins/LintPlugin/plugin.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins/FormatPlugin/plugin.swift b/Plugins/FormatPlugin/plugin.swift index e544a6464..96c77f4d2 100644 --- a/Plugins/FormatPlugin/plugin.swift +++ b/Plugins/FormatPlugin/plugin.swift @@ -40,7 +40,7 @@ extension FormatPlugin: CommandPlugin { let targetNames = argExtractor.extractOption(named: "target") let targetsToFormat = targetNames.isEmpty ? context.package.targets : try context.package.targets(named: targetNames) - let configurationFilePath = argExtractor.extractOption(named: "configuration").first + let configurationFilePath = argExtractor.extractOption(named: "swift-format-configuration").first let sourceCodeTargets = targetsToFormat.compactMap{ $0 as? SourceModuleTarget } @@ -60,7 +60,7 @@ extension FormatPlugin: XcodeCommandPlugin { let swiftFormatTool = try context.tool(named: "swift-format") var argExtractor = ArgumentExtractor(arguments) - let configurationFilePath = argExtractor.extractOption(named: "configuration").first + let configurationFilePath = argExtractor.extractOption(named: "swift-format-configuration").first try format( tool: swiftFormatTool, diff --git a/Plugins/LintPlugin/plugin.swift b/Plugins/LintPlugin/plugin.swift index 0589b7530..34644f6db 100644 --- a/Plugins/LintPlugin/plugin.swift +++ b/Plugins/LintPlugin/plugin.swift @@ -41,7 +41,7 @@ extension LintPlugin: CommandPlugin { let targetNames = argExtractor.extractOption(named: "target") let targetsToFormat = targetNames.isEmpty ? context.package.targets : try context.package.targets(named: targetNames) - let configurationFilePath = argExtractor.extractOption(named: "configuration").first + let configurationFilePath = argExtractor.extractOption(named: "swift-format-configuration").first let sourceCodeTargets = targetsToFormat.compactMap { $0 as? SourceModuleTarget } @@ -60,7 +60,7 @@ extension LintPlugin: XcodeCommandPlugin { func performCommand(context: XcodeProjectPlugin.XcodePluginContext, arguments: [String]) throws { let swiftFormatTool = try context.tool(named: "swift-format") var argExtractor = ArgumentExtractor(arguments) - let configurationFilePath = argExtractor.extractOption(named: "configuration").first + let configurationFilePath = argExtractor.extractOption(named: "swift-format-configuration").first try lint( tool: swiftFormatTool, From ac3cc940a71e2666caadd6e638641dd02e4887fa Mon Sep 17 00:00:00 2001 From: mh-mobile Date: Sun, 24 Mar 2024 15:59:25 +0900 Subject: [PATCH 188/332] Update README to specify version 510.1.0 for building from source --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db92fa2a2..6b29d73eb 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Run `brew install swift-format` to install the latest version. Install `swift-format` using the following commands: ```sh -VERSION=509.0.0 # replace this with the version you need +VERSION=510.1.0 # replace this with the version you need git clone https://github.com/apple/swift-format.git cd swift-format git checkout "tags/$VERSION" From a0bbe05031d85b78fe2433719f0ba25e8aef0123 Mon Sep 17 00:00:00 2001 From: Mizuo Nagayama <33952656+ozumin@users.noreply.github.com> Date: Tue, 26 Mar 2024 23:38:28 +0900 Subject: [PATCH 189/332] fix comment for multiElementCollectionTrailingCommas --- Sources/SwiftFormat/API/Configuration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index ec7d97679..ab8a3e952 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -167,7 +167,7 @@ public struct Configuration: Codable, Equatable { /// /// When `true` (default), the correct form is: /// ```swift - /// let MyCollection = [1, 2,] + /// let MyCollection = [1, 2] /// ... /// let MyCollection = [ /// "a": 1, From 8c4a487f11a89cb2a3e9d0ec1fe9ad5d337b848b Mon Sep 17 00:00:00 2001 From: Mizuo Nagayama <33952656+ozumin@users.noreply.github.com> Date: Tue, 26 Mar 2024 23:50:22 +0900 Subject: [PATCH 190/332] fix method name in CommaTests.swift --- Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift index 24bd0238e..4264370c8 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift @@ -105,7 +105,7 @@ final class CommaTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - func testArraySingleLineCommasPresentDisabled() { + func testArraySingleLineCommasPresentEnabled() { let input = """ let MyCollection = [1, 2, 3,] @@ -124,7 +124,7 @@ final class CommaTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } - func testArraySingleLineCommasPresentEnabled() { + func testArraySingleLineCommasPresentDisabled() { let input = """ let MyCollection = [1, 2, 3,] From 629372aff54e96ad744f583a927edb5468196bba Mon Sep 17 00:00:00 2001 From: Takumi Muraishi Date: Wed, 27 Mar 2024 22:15:24 +0900 Subject: [PATCH 191/332] fix typo in Configuration+Testing.swift --- Sources/_SwiftFormatTestSupport/Configuration+Testing.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift index e9ab39c34..8d095767b 100644 --- a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift +++ b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift @@ -20,7 +20,7 @@ extension Configuration { /// different module than where `Configuration` is defined, we can't make this an initializer that /// would enforce that every field of `Configuration` is initialized here (we're forced to /// delegate to another initializer first, which defeats the purpose). So, users adding new - /// configuration settings shouls be sure to supply a default here for testing, otherwise they + /// configuration settings should be sure to supply a default here for testing, otherwise they /// will be implicitly relying on the real default. public static var forTesting: Configuration { var config = Configuration() From 1967bf7fe7bd76138bb3fe0d26ed584d10e02a93 Mon Sep 17 00:00:00 2001 From: Takumi Muraishi Date: Wed, 27 Mar 2024 22:15:55 +0900 Subject: [PATCH 192/332] fix typo in FileIterator.swift --- Sources/swift-format/Utilities/FileIterator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/swift-format/Utilities/FileIterator.swift b/Sources/swift-format/Utilities/FileIterator.swift index 6f88cc909..a3b2a6984 100644 --- a/Sources/swift-format/Utilities/FileIterator.swift +++ b/Sources/swift-format/Utilities/FileIterator.swift @@ -132,7 +132,7 @@ public struct FileIterator: Sequence, IteratorProtocol { case .typeRegular: // We attempt to relativize the URLs based on the current working directory, not the // directory being iterated over, so that they can be displayed better in diagnostics. Thus, - // if the user passes paths that are relative to the current working diectory, they will + // if the user passes paths that are relative to the current working directory, they will // be displayed as relative paths. Otherwise, they will still be displayed as absolute // paths. let relativePath = From d4bbe76a186c437a0a74667dfb053c2d0424d541 Mon Sep 17 00:00:00 2001 From: Takumi Muraishi Date: Wed, 27 Mar 2024 22:28:49 +0900 Subject: [PATCH 193/332] fix typo in Trivia+Convenience.swift --- Sources/SwiftFormat/Core/Trivia+Convenience.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/Core/Trivia+Convenience.swift b/Sources/SwiftFormat/Core/Trivia+Convenience.swift index 612cd4236..c1906ab6c 100644 --- a/Sources/SwiftFormat/Core/Trivia+Convenience.swift +++ b/Sources/SwiftFormat/Core/Trivia+Convenience.swift @@ -80,7 +80,7 @@ extension Trivia { }) } - /// Returns `true` if this trivia contains any backslahes (used for multiline string newline + /// Returns `true` if this trivia contains any backslashes (used for multiline string newline /// suppression). var containsBackslashes: Bool { return contains( From af841655447f4fe16ec95c347d16ef89e49733d9 Mon Sep 17 00:00:00 2001 From: Takumi Muraishi Date: Thu, 28 Mar 2024 15:10:52 +0900 Subject: [PATCH 194/332] fix annotation --- .../Rules/AlwaysUseLiteralForEmptyCollectionInit.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift index b73e0ba49..973e22771 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift @@ -86,7 +86,7 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { if replacement.typeAnnotation == nil { // Drop trailing trivia after pattern because ':' has to appear connected to it. replacement.pattern = node.pattern.with(\.trailingTrivia, []) - // Add explicit type annotiation: ': []` + // Add explicit type annotation: ': []` replacement.typeAnnotation = .init(type: type.with(\.leadingTrivia, .space) .with(\.trailingTrivia, .space)) } @@ -109,7 +109,7 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { if replacement.typeAnnotation == nil { // Drop trailing trivia after pattern because ':' has to appear connected to it. replacement.pattern = node.pattern.with(\.trailingTrivia, []) - // Add explicit type annotiation: ': []` + // Add explicit type annotation: ': []` replacement.typeAnnotation = .init(type: type.with(\.leadingTrivia, .space) .with(\.trailingTrivia, .space)) } From b04a373ff995848101eb51605f04eac144ff7c86 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Tue, 16 Apr 2024 13:32:42 -0700 Subject: [PATCH 195/332] Fix @_originallyDefinedIn argument spacing Pretty-print `@_originallyDefinedIn` with the correct spacing. --- .../PrettyPrint/TokenStreamCreator.swift | 6 ++++++ .../PrettyPrint/AttributeTests.swift | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 070de11a5..5d0f003f3 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -1799,6 +1799,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: OriginallyDefinedInAttributeArgumentsSyntax) -> SyntaxVisitorContinueKind { + after(node.colon.lastToken(viewMode: .sourceAccurate), tokens: .break(.same, size: 1)) + after(node.comma.lastToken(viewMode: .sourceAccurate), tokens: .break(.same, size: 1)) + return .visitChildren + } + override func visit(_ node: AvailabilityLabeledArgumentSyntax) -> SyntaxVisitorContinueKind { before(node.label, tokens: .open) diff --git a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift index 06aa46c8f..1289dad96 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift @@ -26,6 +26,23 @@ final class AttributeTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 60) } + func testAttributeParamSpacingInOriginallyDefinedIn() { + let input = + """ + @_originallyDefinedIn( module :"SwiftUI" , iOS 10.0 ) + func f() {} + """ + + let expected = + """ + @_originallyDefinedIn(module: "SwiftUI", iOS 10.0) + func f() {} + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 60) + } + func testAttributeBinPackedWrapping() { let input = """ From 36ba8dcc899b92833c838ff7240a753baa250dd0 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Tue, 16 Apr 2024 14:09:29 -0700 Subject: [PATCH 196/332] Fix incorrect spacing when pretty-printing @_documentation With attribute such as `@_documentation(visibility: private)`, swift-format incorrectly prints no spacing between `visibility` and `private`. --- .../PrettyPrint/TokenStreamCreator.swift | 5 +++++ .../PrettyPrint/AttributeTests.swift | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 5d0f003f3..1023d886f 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -1802,6 +1802,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: OriginallyDefinedInAttributeArgumentsSyntax) -> SyntaxVisitorContinueKind { after(node.colon.lastToken(viewMode: .sourceAccurate), tokens: .break(.same, size: 1)) after(node.comma.lastToken(viewMode: .sourceAccurate), tokens: .break(.same, size: 1)) + return .visitChildren + } + + override func visit(_ node: DocumentationAttributeArgumentSyntax) -> SyntaxVisitorContinueKind { + after(node.colon, tokens: .break(.same, size: 1)) return .visitChildren } diff --git a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift index 1289dad96..3031fc31b 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift @@ -43,6 +43,23 @@ final class AttributeTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 60) } + func testAttributeParamSpacingInDocVisibility() { + let input = + """ + @_documentation( visibility :private ) + func f() {} + """ + + let expected = + """ + @_documentation(visibility: private) + func f() {} + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 60) + } + func testAttributeBinPackedWrapping() { let input = """ From 3062c3a0b329326a7a9230aad236bd42fc215a29 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 19 Apr 2024 12:48:20 -0700 Subject: [PATCH 197/332] Add post merge Swift CI support for swift-format --- .swiftci/5_10_ubuntu2204 | 5 +++++ .swiftci/5_7_ubuntu2204 | 5 +++++ .swiftci/5_8_ubuntu2204 | 5 +++++ .swiftci/5_9_ubuntu2204 | 5 +++++ .swiftci/nightly_6_0_macos | 5 +++++ .swiftci/nightly_6_0_ubuntu2204 | 5 +++++ .swiftci/nightly_main_macos | 5 +++++ .swiftci/nightly_main_ubuntu2204 | 5 +++++ .swiftci/nightly_main_windows | 7 +++++++ 9 files changed, 47 insertions(+) create mode 100644 .swiftci/5_10_ubuntu2204 create mode 100644 .swiftci/5_7_ubuntu2204 create mode 100644 .swiftci/5_8_ubuntu2204 create mode 100644 .swiftci/5_9_ubuntu2204 create mode 100644 .swiftci/nightly_6_0_macos create mode 100644 .swiftci/nightly_6_0_ubuntu2204 create mode 100644 .swiftci/nightly_main_macos create mode 100644 .swiftci/nightly_main_ubuntu2204 create mode 100644 .swiftci/nightly_main_windows diff --git a/.swiftci/5_10_ubuntu2204 b/.swiftci/5_10_ubuntu2204 new file mode 100644 index 000000000..bc063162a --- /dev/null +++ b/.swiftci/5_10_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.10-jammy" + repo = "swift-format" + branch = "main" +} diff --git a/.swiftci/5_7_ubuntu2204 b/.swiftci/5_7_ubuntu2204 new file mode 100644 index 000000000..7b7f96233 --- /dev/null +++ b/.swiftci/5_7_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.7-jammy" + repo = "swift-format" + branch = "main" +} diff --git a/.swiftci/5_8_ubuntu2204 b/.swiftci/5_8_ubuntu2204 new file mode 100644 index 000000000..26077ad9c --- /dev/null +++ b/.swiftci/5_8_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.8-jammy" + repo = "swift-format" + branch = "main" +} diff --git a/.swiftci/5_9_ubuntu2204 b/.swiftci/5_9_ubuntu2204 new file mode 100644 index 000000000..53e0ebc31 --- /dev/null +++ b/.swiftci/5_9_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.9-jammy" + repo = "swift-format" + branch = "main" +} diff --git a/.swiftci/nightly_6_0_macos b/.swiftci/nightly_6_0_macos new file mode 100644 index 000000000..e3c0187c3 --- /dev/null +++ b/.swiftci/nightly_6_0_macos @@ -0,0 +1,5 @@ +macOSSwiftPackageJob { + swift_version = "6.0" + repo = "swift-format" + branch = "main" +} diff --git a/.swiftci/nightly_6_0_ubuntu2204 b/.swiftci/nightly_6_0_ubuntu2204 new file mode 100644 index 000000000..936388e31 --- /dev/null +++ b/.swiftci/nightly_6_0_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + nightly_docker_tag = "nightly-6.0-jammy" + repo = "swift-format" + branch = "main" +} diff --git a/.swiftci/nightly_main_macos b/.swiftci/nightly_main_macos new file mode 100644 index 000000000..72bb76b23 --- /dev/null +++ b/.swiftci/nightly_main_macos @@ -0,0 +1,5 @@ +macOSSwiftPackageJob { + swift_version = "main" + repo = "swift-format" + branch = "main" +} diff --git a/.swiftci/nightly_main_ubuntu2204 b/.swiftci/nightly_main_ubuntu2204 new file mode 100644 index 000000000..f0337280a --- /dev/null +++ b/.swiftci/nightly_main_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + nightly_docker_tag = "nightly-jammy" + repo = "swift-format" + branch = "main" +} diff --git a/.swiftci/nightly_main_windows b/.swiftci/nightly_main_windows new file mode 100644 index 000000000..2e683db28 --- /dev/null +++ b/.swiftci/nightly_main_windows @@ -0,0 +1,7 @@ +WindowsSwiftPackageWithDockerImageJob { + docker_image = "swiftlang/swift:nightly-windowsservercore-1809" + repo = "swift-format" + branch = "main" + sub_dir = "swift-collections" + label = "windows-server-2019" +} From 58c2ef514d5b8b3766d2d0957b816a8387b01fed Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 19 Apr 2024 15:29:46 -0700 Subject: [PATCH 198/332] Fix typo in the .swiftci windows testing file --- .swiftci/nightly_main_windows | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swiftci/nightly_main_windows b/.swiftci/nightly_main_windows index 2e683db28..e3bbb519b 100644 --- a/.swiftci/nightly_main_windows +++ b/.swiftci/nightly_main_windows @@ -2,6 +2,6 @@ WindowsSwiftPackageWithDockerImageJob { docker_image = "swiftlang/swift:nightly-windowsservercore-1809" repo = "swift-format" branch = "main" - sub_dir = "swift-collections" + sub_dir = "swift-format" label = "windows-server-2019" } From f93ddbde24c7b9a0353047ad6fbd056553c810dd Mon Sep 17 00:00:00 2001 From: Fawkes Wei Date: Mon, 29 Apr 2024 16:02:21 +0200 Subject: [PATCH 199/332] Update RuleDocumentation.md Fix link to Configuration.md --- Documentation/RuleDocumentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index cc3ae92de..654bc8b04 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -4,7 +4,7 @@ Use the rules below in the `rules` block of your `.swift-format` configuration file, as described in -[Configuration](Documentation/Configuration.md). All of these rules can be +[Configuration](Configuration.md). All of these rules can be applied in the linter, but only some of them can format your source code automatically. From 2dd68a061c5958d52dbb62e89eede2af9be1cca4 Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Mon, 29 Apr 2024 10:44:24 -0400 Subject: [PATCH 200/332] Remove check for prioritizeKeepingFunctionOutputTogether on enum decl. --- .../SwiftFormat/PrettyPrint/TokenStreamCreator.swift | 8 -------- .../SwiftFormatTests/PrettyPrint/EnumDeclTests.swift | 12 ++++++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 1023d886f..0b96cf024 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -1350,14 +1350,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: EnumCaseParameterClauseSyntax) -> SyntaxVisitorContinueKind { - // Prioritize keeping ") throws -> " together. We can only do this if the function - // has arguments. - if !node.parameters.isEmpty && config.prioritizeKeepingFunctionOutputTogether { - // Due to visitation order, this .open corresponds to a .close added in FunctionDeclSyntax - // or SubscriptDeclSyntax. - before(node.rightParen, tokens: .open) - } - return .visitChildren } diff --git a/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift index 675d75eda..1561d8e8d 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift @@ -559,4 +559,16 @@ final class EnumDeclTests: PrettyPrintTestCase { let input = "enum Foo { var bar: Int }" assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 50) } + + func testEnumWithPrioritizeKeepingFunctionOutputTogetherFlag() { + let input = """ + enum Error { + case alreadyOpen(Int) + } + + """ + var config = Configuration.forTesting + config.prioritizeKeepingFunctionOutputTogether = true + assertPrettyPrintEqual(input: input, expected: input, linelength: 50, configuration: config) + } } From 53238b5358da1f52162ea0ade2b75ec2dc1bd576 Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Mon, 29 Apr 2024 13:08:40 -0400 Subject: [PATCH 201/332] Make sure there is a break after an #endif. You wouldn't normally allow a line break just before the comma in a parameter list, but if the preceding line is an #endif, we have to include the break or we'll generate invalid code. --- .../PrettyPrint/TokenStreamCreator.swift | 1 + .../PrettyPrint/IfConfigTests.swift | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 1023d886f..76592a48d 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -3911,6 +3911,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(clause.poundKeyword, tokens: .break(.contextual, size: 0)) } before(postfixIfExpr.config.poundEndif, tokens: .break(.contextual, size: 0)) + after(postfixIfExpr.config.poundEndif, tokens: .break(.same, size: 0)) return insertContextualBreaks(base, isTopLevel: false) } else if let callingExpr = expr.asProtocol(CallingExprSyntaxProtocol.self) { diff --git a/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift index 4f8026ab4..5a6113529 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift @@ -516,4 +516,19 @@ final class IfConfigTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) } + + func testPostfixPoundIfInParameterList() { + let input = + """ + print( + 32 + #if true + .foo + #endif + , 22 + ) + + """ + assertPrettyPrintEqual(input: input, expected: input, linelength: 45) + } } From 6cf4ea0257feef10b12a6990f2d7c704b34b2db4 Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Tue, 7 May 2024 08:51:12 -0400 Subject: [PATCH 202/332] Merge adjacent .line and .docLine comments into a single element. --- Sources/SwiftFormat/PrettyPrint/Comment.swift | 7 +++-- .../PrettyPrint/TokenStreamCreator.swift | 31 +++++++++++++++---- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/Comment.swift b/Sources/SwiftFormat/PrettyPrint/Comment.swift index 8e119b819..60d63af10 100644 --- a/Sources/SwiftFormat/PrettyPrint/Comment.swift +++ b/Sources/SwiftFormat/PrettyPrint/Comment.swift @@ -64,7 +64,7 @@ struct Comment { switch kind { case .line, .docLine: - self.text = [text.trimmingTrailingWhitespace()] + self.text = [text] self.text[0].removeFirst(kind.prefixLength) self.length = self.text.reduce(0, { $0 + $1.count + kind.prefixLength + 1 }) @@ -88,8 +88,9 @@ struct Comment { func print(indent: [Indent]) -> String { switch self.kind { case .line, .docLine: - let separator = "\n" + kind.prefix - return kind.prefix + self.text.joined(separator: separator) + let separator = "\n" + indent.indentation() + kind.prefix + let trimmedLines = self.text.map { $0.trimmingTrailingWhitespace() } + return kind.prefix + trimmedLines.joined(separator: separator) case .block, .docBlock: let separator = "\n" return kind.prefix + self.text.joined(separator: separator) + "*/" diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index cf02ce209..81fb56707 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -3386,12 +3386,31 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func appendToken(_ token: Token) { if let last = tokens.last { switch (last, token) { - case (.comment(let c1, _), .comment(let c2, _)) - where c1.kind == .docLine && c2.kind == .docLine: - var newComment = c1 - newComment.addText(c2.text) - tokens[tokens.count - 1] = .comment(newComment, wasEndOfLine: false) - return + case (.break(.same, _, .soft(let count, _)), .comment(let c2, _)) + where count == 1 && (c2.kind == .docLine || c2.kind == .line): + // we are search for the pattern of [line comment] - [soft break 1] - [line comment] + // where the comment type is the same; these can be merged into a single comment + if let nextToLast = tokens.dropLast().last, + case let .comment(c1, false) = nextToLast, + c1.kind == c2.kind + { + var mergedComment = c1 + mergedComment.addText(c2.text) + tokens.removeLast() // remove the soft break + // replace the original comment with the merged one + tokens[tokens.count - 1] = .comment(mergedComment, wasEndOfLine: false) + + // need to fix lastBreakIndex because we just removed the last break + lastBreakIndex = tokens.lastIndex(where: { + switch $0 { + case .break: return true + default: return false + } + }) + canMergeNewlinesIntoLastBreak = false + + return + } // If we see a pair of spaces where one or both are flexible, combine them into a new token // with the maximum of their counts. From b065fa319e616110ac03f16ca647188475b3c5f7 Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Wed, 8 May 2024 14:40:51 -0400 Subject: [PATCH 203/332] Improvements based on feedback and further testing. --- .../PrettyPrint/TokenStreamCreator.swift | 8 ++- .../PrettyPrint/CommentTests.swift | 72 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 81fb56707..08f1aa4f0 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -3384,10 +3384,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// This function also handles collapsing neighboring tokens in situations where that is /// desired, like merging adjacent comments and newlines. private func appendToken(_ token: Token) { + func breakAllowsCommentMerge(_ breakKind: BreakKind) -> Bool { + return breakKind == .same || breakKind == .continue || breakKind == .contextual + } + if let last = tokens.last { switch (last, token) { - case (.break(.same, _, .soft(let count, _)), .comment(let c2, _)) - where count == 1 && (c2.kind == .docLine || c2.kind == .line): + case (.break(let breakKind, _, .soft(1, _)), .comment(let c2, _)) + where breakAllowsCommentMerge(breakKind) && (c2.kind == .docLine || c2.kind == .line): // we are search for the pattern of [line comment] - [soft break 1] - [line comment] // where the comment type is the same; these can be merged into a single comment if let nextToLast = tokens.dropLast().last, diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift index 6525cdf60..84403f64d 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift @@ -83,6 +83,8 @@ final class CommentTests: PrettyPrintTestCase { func testLineComments() { let input = """ + // Line Comment0 + // Line Comment1 // Line Comment2 let a = 123 @@ -93,6 +95,7 @@ final class CommentTests: PrettyPrintTestCase { // Comment 4 let reallyLongVariableName = 123 // This comment should not wrap + // and should not combine with this comment func MyFun() { // just a comment @@ -135,6 +138,8 @@ final class CommentTests: PrettyPrintTestCase { let expected = """ + // Line Comment0 + // Line Comment1 // Line Comment2 let a = 123 @@ -145,6 +150,7 @@ final class CommentTests: PrettyPrintTestCase { // Comment 4 let reallyLongVariableName = 123 // This comment should not wrap + // and should not combine with this comment func MyFun() { // just a comment @@ -208,6 +214,13 @@ final class CommentTests: PrettyPrintTestCase { let c = [123, 456 // small comment ] + // Multiline comment + let d = [123, + // comment line 1 + // comment line 2 + 456 + ] + /* Array comment */ let a = [456, /* small comment */ 789] @@ -236,6 +249,14 @@ final class CommentTests: PrettyPrintTestCase { 123, 456, // small comment ] + // Multiline comment + let d = [ + 123, + // comment line 1 + // comment line 2 + 456, + ] + /* Array comment */ let a = [ 456, /* small comment */ @@ -760,4 +781,55 @@ final class CommentTests: PrettyPrintTestCase { ] ) } + + func testLineWithDocLineComment() { + // none of these should be merged if/when there is comment formatting + let input = + """ + /// Doc line comment + // Line comment + /// Doc line comment + // Line comment + + // Another line comment + + """ + assertPrettyPrintEqual(input: input, expected: input, linelength: 80) + } + + func testNonmergeableComments() { + // none of these should be merged if/when there is comment formatting + let input = + """ + let x = 1 // end of line comment + // + + let y = // eol comment + 1 // another + + 2 // and another + + """ + + assertPrettyPrintEqual(input: input, expected: input, linelength: 80) + } + + func testMergeableComments() { + // these examples should be merged and formatted if/when there is comment formatting + let input = + """ + let z = + // one comment + // and another comment + 1 + 2 + + let w = [1, 2, 3] + .foo() + // this comment + // could be merged with this one + .bar() + + """ + + assertPrettyPrintEqual(input: input, expected: input, linelength: 80) + } } From eec64033cfcebb51c2b4c322ebb9233bade545fa Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Tue, 28 May 2024 11:03:01 -0400 Subject: [PATCH 204/332] Handle indented block comments with ASCII art correctly. --- .../Core/DocumentationCommentText.swift | 53 +++++---- .../Core/DocumentationCommentTextTests.swift | 20 +++- ...tationCommentWithOneLineSummaryTests.swift | 102 +++++++++--------- 3 files changed, 105 insertions(+), 70 deletions(-) diff --git a/Sources/SwiftFormat/Core/DocumentationCommentText.swift b/Sources/SwiftFormat/Core/DocumentationCommentText.swift index 44ef1a61a..32dd82a18 100644 --- a/Sources/SwiftFormat/Core/DocumentationCommentText.swift +++ b/Sources/SwiftFormat/Core/DocumentationCommentText.swift @@ -67,24 +67,7 @@ public struct DocumentationCommentText { // comment. We have to copy it into an array since `Trivia` doesn't support bidirectional // indexing. let triviaArray = Array(trivia) - let commentStartIndex: Array.Index - if - let lastNonDocCommentIndex = triviaArray.lastIndex(where: { - switch $0 { - case .docBlockComment, .docLineComment, - .newlines(1), .carriageReturns(1), .carriageReturnLineFeeds(1), - .spaces, .tabs: - return false - default: - return true - } - }), - lastNonDocCommentIndex != trivia.endIndex - { - commentStartIndex = triviaArray.index(after: lastNonDocCommentIndex) - } else { - commentStartIndex = triviaArray.startIndex - } + let commentStartIndex = findCommentStartIndex(triviaArray) // Determine the indentation level of the first line of the comment. This is used to adjust // block comments, whose text spans multiple lines. @@ -216,3 +199,37 @@ private func asciiArtLength(of string: Substring, leadingSpaces: Int) -> Int { } return 0 } + +/// Returns the start index of the earliest comment in the Trivia if we work backwards and +/// skip through comments, newlines, and whitespace. Then we advance a bit forward to be sure +/// the returned index is actually a comment and not whitespace. +private func findCommentStartIndex(_ triviaArray: Array) -> Array.Index { + func firstCommentIndex(_ slice: ArraySlice) -> Array.Index { + return slice.firstIndex(where: { + switch $0 { + case .docLineComment, .docBlockComment: + return true + default: + return false + } + }) ?? slice.endIndex + } + + if + let lastNonDocCommentIndex = triviaArray.lastIndex(where: { + switch $0 { + case .docBlockComment, .docLineComment, + .newlines(1), .carriageReturns(1), .carriageReturnLineFeeds(1), + .spaces, .tabs: + return false + default: + return true + } + }) + { + let nextIndex = triviaArray.index(after: lastNonDocCommentIndex) + return firstCommentIndex(triviaArray[nextIndex...]) + } else { + return firstCommentIndex(triviaArray[...]) + } +} diff --git a/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift b/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift index adda6e4f1..4a1f8302f 100644 --- a/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift +++ b/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift @@ -54,7 +54,25 @@ final class DocumentationCommentTextTests: XCTestCase { """ ) } - + + func testIndentedDocBlockCommentWithASCIIArt() throws { + let decl: DeclSyntax = """ + /** + * A simple doc comment. + */ + func f() {} + """ + let commentText = try XCTUnwrap(DocumentationCommentText(extractedFrom: decl.leadingTrivia)) + XCTAssertEqual(commentText.introducer, .block) + XCTAssertEqual( + commentText.text, + """ + A simple doc comment. + + """ + ) + } + func testDocBlockCommentWithoutASCIIArt() throws { let decl: DeclSyntax = """ /** diff --git a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift index e41425930..cfeaa09dd 100644 --- a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift +++ b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift @@ -14,33 +14,33 @@ final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTe assertLint( BeginDocumentationCommentWithOneLineSummary.self, """ - /// Returns a bottle of Dr Pepper from the vending machine. - public func drPepper(from vendingMachine: VendingMachine) -> Soda {} + /// Returns a bottle of Dr Pepper from the vending machine. + public func drPepper(from vendingMachine: VendingMachine) -> Soda {} - /// Contains a comment as description that needs a sentence - /// of two lines of code. - public var twoLinesForOneSentence = "test" + /// Contains a comment as description that needs a sentence + /// of two lines of code. + public var twoLinesForOneSentence = "test" - /// The background color of the view. - var backgroundColor: UIColor + /// The background color of the view. + var backgroundColor: UIColor - /// Returns the sum of the numbers. - /// - /// - Parameter numbers: The numbers to sum. - /// - Returns: The sum of the numbers. - func sum(_ numbers: [Int]) -> Int { - // ... - } + /// Returns the sum of the numbers. + /// + /// - Parameter numbers: The numbers to sum. + /// - Returns: The sum of the numbers. + func sum(_ numbers: [Int]) -> Int { + // ... + } - /// This docline should not succeed. - /// There are two sentences without a blank line between them. - 1️⃣struct Test {} + /// This docline should not succeed. + /// There are two sentences without a blank line between them. + 1️⃣struct Test {} - /// This docline should not succeed. There are two sentences. - 2️⃣public enum Token { case comma, semicolon, identifier } + /// This docline should not succeed. There are two sentences. + 2️⃣public enum Token { case comma, semicolon, identifier } - /// Should fail because it doesn't have a period - 3️⃣public class testNoPeriod {} + /// Should fail because it doesn't have a period + 3️⃣public class testNoPeriod {} """, findings: [ FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), @@ -54,36 +54,36 @@ final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTe assertLint( BeginDocumentationCommentWithOneLineSummary.self, """ - /** - * Returns the numeric value. - * - * - Parameters: - * - digit: The Unicode scalar whose numeric value should be returned. - * - radix: The radix, between 2 and 36, used to compute the numeric value. - * - Returns: The numeric value of the scalar.*/ - func numericValue(of digit: UnicodeScalar, radix: Int = 10) -> Int {} - - /** - * This block comment contains a sentence summary - * of two lines of code. - */ - public var twoLinesForOneSentence = "test" - - /** - * This block comment should not succeed, struct. - * There are two sentences without a blank line between them. - */ - 1️⃣struct TestStruct {} - - /** - This block comment should not succeed, class. - Add a blank comment after the first line. - */ - 2️⃣public class TestClass {} - /** This block comment should not succeed, enum. There are two sentences. */ - 3️⃣public enum testEnum {} - /** Should fail because it doesn't have a period */ - 4️⃣public class testNoPeriod {} + /** + * Returns the numeric value. + * + * - Parameters: + * - digit: The Unicode scalar whose numeric value should be returned. + * - radix: The radix, between 2 and 36, used to compute the numeric value. + * - Returns: The numeric value of the scalar.*/ + func numericValue(of digit: UnicodeScalar, radix: Int = 10) -> Int {} + + /** + * This block comment contains a sentence summary + * of two lines of code. + */ + public var twoLinesForOneSentence = "test" + + /** + * This block comment should not succeed, struct. + * There are two sentences without a blank line between them. + */ + 1️⃣struct TestStruct {} + + /** + This block comment should not succeed, class. + Add a blank comment after the first line. + */ + 2️⃣public class TestClass {} + /** This block comment should not succeed, enum. There are two sentences. */ + 3️⃣public enum testEnum {} + /** Should fail because it doesn't have a period */ + 4️⃣public class testNoPeriod {} """, findings: [ FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This block comment should not succeed, struct.""#), From 66400670c77c7ac91adb9e526b267e7003288a6d Mon Sep 17 00:00:00 2001 From: David Ewing Date: Thu, 21 Mar 2024 14:52:40 -0600 Subject: [PATCH 205/332] Support for formatting a selection (given as an array of ranges) . MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The basic idea here is to insert `enableFormatting` and `disableFormatting` tokens into the print stream when we enter or leave the selection. When formatting is enabled, we print out the tokens as usual. When formatting is disabled, we turn off any output until the next `enableFormatting` token. When that token is hit, we write the original source text from the location of the last `disableFormatting` to the current location. Note that this means that all the APIs need the original source text to be passed in. A `Selection` is represented as an enum with an `.infinite` case, and a `.ranges` case to indicate either selecting the entire file, or an array of start/end utf-8 offsets. The offset pairs are given with `Range`, matching the (now common) usage in swift-syntax. For testing, allow marked text to use `⏩` and `⏪` to deliniate the start/end of a range of a selection. The command line now takes an `--offsets` option of comma-separated "start:end" pairs to set the selection for formatting. --- Sources/SwiftFormat/API/Selection.swift | 63 +++ Sources/SwiftFormat/API/SwiftFormatter.swift | 31 +- Sources/SwiftFormat/API/SwiftLinter.swift | 6 +- Sources/SwiftFormat/Core/Context.swift | 11 +- .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 85 ++++- Sources/SwiftFormat/PrettyPrint/Token.swift | 9 + .../PrettyPrint/TokenStreamCreator.swift | 81 +++- .../DiagnosingTestCase.swift | 2 + .../_SwiftFormatTestSupport/MarkedText.swift | 22 +- .../Frontend/FormatFrontend.swift | 4 +- Sources/swift-format/Frontend/Frontend.swift | 31 +- .../Subcommands/LintFormatOptions.swift | 32 +- .../WhitespaceLinterPerformanceTests.swift | 3 +- .../PrettyPrint/IgnoreNodeTests.swift | 2 +- .../PrettyPrint/PrettyPrintTestCase.swift | 24 +- .../PrettyPrint/SelectionTests.swift | 359 ++++++++++++++++++ .../PrettyPrint/WhitespaceTestCase.swift | 1 + .../Rules/LintOrFormatRuleTestCase.swift | 14 +- 18 files changed, 722 insertions(+), 58 deletions(-) create mode 100644 Sources/SwiftFormat/API/Selection.swift create mode 100644 Tests/SwiftFormatTests/PrettyPrint/SelectionTests.swift diff --git a/Sources/SwiftFormat/API/Selection.swift b/Sources/SwiftFormat/API/Selection.swift new file mode 100644 index 000000000..b3d51aad0 --- /dev/null +++ b/Sources/SwiftFormat/API/Selection.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation +import SwiftSyntax + +/// The selection as given on the command line - an array of offets and lengths +public enum Selection { + case infinite + case ranges([Range]) + + /// Create a selection from an array of utf8 ranges. An empty array means an infinite selection. + public init(offsetPairs: [Range]) { + if offsetPairs.isEmpty { + self = .infinite + } else { + let ranges = offsetPairs.map { + AbsolutePosition(utf8Offset: $0.lowerBound) ..< AbsolutePosition(utf8Offset: $0.upperBound) + } + self = .ranges(ranges) + } + } + + public func contains(_ position: AbsolutePosition) -> Bool { + switch self { + case .infinite: + return true + case .ranges(let ranges): + return ranges.contains { $0.contains(position) } + } + } + + public func overlapsOrTouches(_ range: Range) -> Bool { + switch self { + case .infinite: + return true + case .ranges(let ranges): + return ranges.contains { $0.overlapsOrTouches(range) } + } + } +} + + +public extension Syntax { + /// return true if the node is _completely_ inside any range in the selection + func isInsideSelection(_ selection: Selection) -> Bool { + switch selection { + case .infinite: + return true + case .ranges(let ranges): + return ranges.contains { return $0.lowerBound <= position && endPosition <= $0.upperBound } + } + } +} diff --git a/Sources/SwiftFormat/API/SwiftFormatter.swift b/Sources/SwiftFormat/API/SwiftFormatter.swift index 9230bdd8f..9c8f6f416 100644 --- a/Sources/SwiftFormat/API/SwiftFormatter.swift +++ b/Sources/SwiftFormat/API/SwiftFormatter.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -21,6 +21,9 @@ public final class SwiftFormatter { /// The configuration settings that control the formatter's behavior. public let configuration: Configuration + /// the ranges of text to format + public var selection: Selection = .infinite + /// An optional callback that will be notified with any findings encountered during formatting. public let findingConsumer: ((Finding) -> Void)? @@ -70,6 +73,7 @@ public final class SwiftFormatter { try format( source: String(contentsOf: url, encoding: .utf8), assumingFileURL: url, + selection: .infinite, to: &outputStream, parsingDiagnosticHandler: parsingDiagnosticHandler) } @@ -86,6 +90,7 @@ public final class SwiftFormatter { /// - url: A file URL denoting the filename/path that should be assumed for this syntax tree, /// which is associated with any diagnostics emitted during formatting. If this is nil, a /// dummy value will be used. + /// - selection: The ranges to format /// - outputStream: A value conforming to `TextOutputStream` to which the formatted output will /// be written. /// - parsingDiagnosticHandler: An optional callback that will be notified if there are any @@ -94,6 +99,7 @@ public final class SwiftFormatter { public func format( source: String, assumingFileURL url: URL?, + selection: Selection, to outputStream: inout Output, parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil ) throws { @@ -108,8 +114,8 @@ public final class SwiftFormatter { assumingFileURL: url, parsingDiagnosticHandler: parsingDiagnosticHandler) try format( - syntax: sourceFile, operatorTable: .standardOperators, assumingFileURL: url, source: source, - to: &outputStream) + syntax: sourceFile, source: source, operatorTable: .standardOperators, assumingFileURL: url, + selection: selection, to: &outputStream) } /// Formats the given Swift syntax tree and writes the result to an output stream. @@ -122,32 +128,26 @@ public final class SwiftFormatter { /// /// - Parameters: /// - syntax: The Swift syntax tree to be converted to source code and formatted. + /// - source: The original Swift source code used to build the syntax tree. /// - operatorTable: The table that defines the operators and their precedence relationships. /// This must be the same operator table that was used to fold the expressions in the `syntax` /// argument. /// - url: A file URL denoting the filename/path that should be assumed for this syntax tree, /// which is associated with any diagnostics emitted during formatting. If this is nil, a /// dummy value will be used. + /// - selection: The ranges to format /// - outputStream: A value conforming to `TextOutputStream` to which the formatted output will /// be written. /// - Throws: If an unrecoverable error occurs when formatting the code. public func format( - syntax: SourceFileSyntax, operatorTable: OperatorTable, assumingFileURL url: URL?, - to outputStream: inout Output - ) throws { - try format( - syntax: syntax, operatorTable: operatorTable, assumingFileURL: url, source: nil, - to: &outputStream) - } - - private func format( - syntax: SourceFileSyntax, operatorTable: OperatorTable, - assumingFileURL url: URL?, source: String?, to outputStream: inout Output + syntax: SourceFileSyntax, source: String, operatorTable: OperatorTable, + assumingFileURL url: URL?, selection: Selection, to outputStream: inout Output ) throws { let assumedURL = url ?? URL(fileURLWithPath: "source") let context = Context( configuration: configuration, operatorTable: operatorTable, findingConsumer: findingConsumer, - fileURL: assumedURL, sourceFileSyntax: syntax, source: source, ruleNameCache: ruleNameCache) + fileURL: assumedURL, selection: selection, sourceFileSyntax: syntax, source: source, + ruleNameCache: ruleNameCache) let pipeline = FormatPipeline(context: context) let transformedSyntax = pipeline.rewrite(Syntax(syntax)) @@ -158,6 +158,7 @@ public final class SwiftFormatter { let printer = PrettyPrinter( context: context, + source: source, node: transformedSyntax, printTokenStream: debugOptions.contains(.dumpTokenStream), whitespaceOnly: false) diff --git a/Sources/SwiftFormat/API/SwiftLinter.swift b/Sources/SwiftFormat/API/SwiftLinter.swift index 4806f19df..79568e2cb 100644 --- a/Sources/SwiftFormat/API/SwiftLinter.swift +++ b/Sources/SwiftFormat/API/SwiftLinter.swift @@ -119,17 +119,18 @@ public final class SwiftLinter { /// - Throws: If an unrecoverable error occurs when formatting the code. public func lint( syntax: SourceFileSyntax, + source: String, operatorTable: OperatorTable, assumingFileURL url: URL ) throws { - try lint(syntax: syntax, operatorTable: operatorTable, assumingFileURL: url, source: nil) + try lint(syntax: syntax, operatorTable: operatorTable, assumingFileURL: url, source: source) } private func lint( syntax: SourceFileSyntax, operatorTable: OperatorTable, assumingFileURL url: URL, - source: String? + source: String ) throws { let context = Context( configuration: configuration, operatorTable: operatorTable, findingConsumer: findingConsumer, @@ -145,6 +146,7 @@ public final class SwiftLinter { // pretty-printer. let printer = PrettyPrinter( context: context, + source: source, node: Syntax(syntax), printTokenStream: debugOptions.contains(.dumpTokenStream), whitespaceOnly: true) diff --git a/Sources/SwiftFormat/Core/Context.swift b/Sources/SwiftFormat/Core/Context.swift index 29e69b0dc..8fde15b3e 100644 --- a/Sources/SwiftFormat/Core/Context.swift +++ b/Sources/SwiftFormat/Core/Context.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -39,6 +39,9 @@ public final class Context { /// The configuration for this run of the pipeline, provided by a configuration JSON file. let configuration: Configuration + /// The optional ranges to process + let selection: Selection + /// Defines the operators and their precedence relationships that were used during parsing. let operatorTable: OperatorTable @@ -66,6 +69,7 @@ public final class Context { operatorTable: OperatorTable, findingConsumer: ((Finding) -> Void)?, fileURL: URL, + selection: Selection = .infinite, sourceFileSyntax: SourceFileSyntax, source: String? = nil, ruleNameCache: [ObjectIdentifier: String] @@ -74,6 +78,7 @@ public final class Context { self.operatorTable = operatorTable self.findingEmitter = FindingEmitter(consumer: findingConsumer) self.fileURL = fileURL + self.selection = selection self.importsXCTest = .notDetermined let tree = source.map { Parser.parse(source: $0) } ?? sourceFileSyntax self.sourceLocationConverter = @@ -86,8 +91,10 @@ public final class Context { } /// Given a rule's name and the node it is examining, determine if the rule is disabled at this - /// location or not. + /// location or not. Also makes sure the entire node is contained inside any selection. func isRuleEnabled(_ rule: R.Type, node: Syntax) -> Bool { + guard node.isInsideSelection(selection) else { return false } + let loc = node.startLocation(converter: self.sourceLocationConverter) assert( diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 1201f84c2..57c7a2a63 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import SwiftSyntax +import Foundation /// PrettyPrinter takes a Syntax node and outputs a well-formatted, re-indented reproduction of the /// code as a String. @@ -66,6 +67,19 @@ public class PrettyPrinter { private var configuration: Configuration { return context.configuration } private let maxLineLength: Int private var tokens: [Token] + private var source: String + + /// keep track of where formatting was disabled in the original source + /// + /// To format a selection, we insert `enableFormatting`/`disableFormatting` tokens into the + /// stream when entering/exiting a selection range. Those tokens include utf8 offsets into the + /// original source. When enabling formatting, we copy the text between `disabledPosition` and the + /// current position to `outputBuffer`. From then on, we continue to format until the next + /// `disableFormatting` token. + private var disabledPosition: AbsolutePosition? = nil + /// true if we're currently formatting + private var writingIsEnabled: Bool { disabledPosition == nil } + private var outputBuffer: String = "" /// The number of spaces remaining on the current line. @@ -172,11 +186,14 @@ public class PrettyPrinter { /// - printTokenStream: Indicates whether debug information about the token stream should be /// printed to standard output. /// - whitespaceOnly: Whether only whitespace changes should be made. - public init(context: Context, node: Syntax, printTokenStream: Bool, whitespaceOnly: Bool) { + public init(context: Context, source: String, node: Syntax, printTokenStream: Bool, whitespaceOnly: Bool) { self.context = context + self.source = source let configuration = context.configuration self.tokens = node.makeTokenStream( - configuration: configuration, operatorTable: context.operatorTable) + configuration: configuration, + selection: context.selection, + operatorTable: context.operatorTable) self.maxLineLength = configuration.lineLength self.spaceRemaining = self.maxLineLength self.printTokenStream = printTokenStream @@ -216,7 +233,9 @@ public class PrettyPrinter { } guard numberToPrint > 0 else { return } - writeRaw(String(repeating: "\n", count: numberToPrint)) + if writingIsEnabled { + writeRaw(String(repeating: "\n", count: numberToPrint)) + } lineNumber += numberToPrint isAtStartOfLine = true consecutiveNewlineCount += numberToPrint @@ -238,13 +257,17 @@ public class PrettyPrinter { /// leading spaces that are required before the text itself. private func write(_ text: String) { if isAtStartOfLine { - writeRaw(currentIndentation.indentation()) + if writingIsEnabled { + writeRaw(currentIndentation.indentation()) + } spaceRemaining = maxLineLength - currentIndentation.length(in: configuration) isAtStartOfLine = false - } else if pendingSpaces > 0 { + } else if pendingSpaces > 0 && writingIsEnabled { writeRaw(String(repeating: " ", count: pendingSpaces)) } - writeRaw(text) + if writingIsEnabled { + writeRaw(text) + } consecutiveNewlineCount = 0 pendingSpaces = 0 } @@ -523,7 +546,9 @@ public class PrettyPrinter { } case .verbatim(let verbatim): - writeRaw(verbatim.print(indent: currentIndentation)) + if writingIsEnabled { + writeRaw(verbatim.print(indent: currentIndentation)) + } consecutiveNewlineCount = 0 pendingSpaces = 0 lastBreak = false @@ -569,6 +594,40 @@ public class PrettyPrinter { write(",") spaceRemaining -= 1 } + + case .enableFormatting(let enabledPosition): + // if we're not disabled, we ignore the token + if let disabledPosition { + let start = source.utf8.index(source.utf8.startIndex, offsetBy: disabledPosition.utf8Offset) + let end: String.Index + if let enabledPosition { + end = source.utf8.index(source.utf8.startIndex, offsetBy: enabledPosition.utf8Offset) + } else { + end = source.endIndex + } + var text = String(source[start..() - init(configuration: Configuration, operatorTable: OperatorTable) { + /// Tracks whether we last considered ourselves inside the selection + private var isInsideSelection = true + + init(configuration: Configuration, selection: Selection, operatorTable: OperatorTable) { self.config = configuration + self.selection = selection self.operatorTable = operatorTable self.maxlinelength = config.lineLength super.init(viewMode: .all) } func makeStream(from node: Syntax) -> [Token] { + // if we have a selection, then we start outside of it + if case .ranges = selection { + appendToken(.disableFormatting(AbsolutePosition(utf8Offset: 0))) + isInsideSelection = false + } + // Because `walk` takes an `inout` argument, and we're a class, we have to do the following // dance to pass ourselves in. self.walk(node) + + // Make sure we output any trailing text after the last selection range + if case .ranges = selection { + appendToken(.enableFormatting(nil)) + } defer { tokens = [] } return tokens } @@ -2719,11 +2735,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { extractLeadingTrivia(token) closeScopeTokens.forEach(appendToken) + generateEnableFormattingIfNecessary( + token.positionAfterSkippingLeadingTrivia ..< token.endPositionBeforeTrailingTrivia + ) + if !ignoredTokens.contains(token) { // Otherwise, it's just a regular token, so add the text as-is. appendToken(.syntax(token.presence == .present ? token.text : "")) } + generateDisableFormattingIfNecessary(token.endPositionBeforeTrailingTrivia) + appendTrailingTrivia(token) appendAfterTokensAndTrailingComments(token) @@ -2731,6 +2753,22 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .skipChildren } + private func generateEnableFormattingIfNecessary(_ range: Range) { + if case .infinite = selection { return } + if !isInsideSelection && selection.overlapsOrTouches(range) { + appendToken(.enableFormatting(range.lowerBound)) + isInsideSelection = true + } + } + + private func generateDisableFormattingIfNecessary(_ position: AbsolutePosition) { + if case .infinite = selection { return } + if isInsideSelection && !selection.contains(position) { + appendToken(.disableFormatting(position)) + isInsideSelection = false + } + } + /// Appends the before-tokens of the given syntax token to the token stream. private func appendBeforeTokens(_ token: TokenSyntax) { if let before = beforeMap.removeValue(forKey: token) { @@ -3194,11 +3232,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func extractLeadingTrivia(_ token: TokenSyntax) { var isStartOfFile: Bool let trivia: Trivia + var position = token.position if let previousToken = token.previousToken(viewMode: .sourceAccurate) { isStartOfFile = false // Find the first non-whitespace in the previous token's trailing and peel those off. let (_, prevTrailingComments) = partitionTrailingTrivia(previousToken.trailingTrivia) - trivia = Trivia(pieces: prevTrailingComments) + token.leadingTrivia + let prevTrivia = Trivia(pieces: prevTrailingComments) + trivia = prevTrivia + token.leadingTrivia + position -= prevTrivia.sourceLength } else { isStartOfFile = true trivia = token.leadingTrivia @@ -3229,7 +3270,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { switch piece { case .lineComment(let text): if index > 0 || isStartOfFile { + generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength) appendToken(.comment(Comment(kind: .line, text: text), wasEndOfLine: false)) + generateDisableFormattingIfNecessary(position + piece.sourceLength) appendNewlines(.soft) isStartOfFile = false } @@ -3237,7 +3280,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { case .blockComment(let text): if index > 0 || isStartOfFile { + generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength) appendToken(.comment(Comment(kind: .block, text: text), wasEndOfLine: false)) + generateDisableFormattingIfNecessary(position + piece.sourceLength) // There is always a break after the comment to allow a discretionary newline after it. var breakSize = 0 if index + 1 < trivia.endIndex { @@ -3252,13 +3297,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { requiresNextNewline = false case .docLineComment(let text): + generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength) appendToken(.comment(Comment(kind: .docLine, text: text), wasEndOfLine: false)) + generateDisableFormattingIfNecessary(position + piece.sourceLength) appendNewlines(.soft) isStartOfFile = false requiresNextNewline = true case .docBlockComment(let text): + generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength) appendToken(.comment(Comment(kind: .docBlock, text: text), wasEndOfLine: false)) + generateDisableFormattingIfNecessary(position + piece.sourceLength) appendNewlines(.soft) isStartOfFile = false requiresNextNewline = false @@ -3297,6 +3346,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { default: break } + position += piece.sourceLength } } @@ -3432,7 +3482,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { case .break: lastBreakIndex = tokens.endIndex canMergeNewlinesIntoLastBreak = true - case .open, .printerControl, .contextualBreakingStart: + case .open, .printerControl, .contextualBreakingStart, .enableFormatting, .disableFormatting: break default: canMergeNewlinesIntoLastBreak = false @@ -3997,10 +4047,17 @@ private func isNestedInPostfixIfConfig(node: Syntax) -> Bool { extension Syntax { /// Creates a pretty-printable token stream for the provided Syntax node. - func makeTokenStream(configuration: Configuration, operatorTable: OperatorTable) -> [Token] { - let commentsMoved = CommentMovingRewriter().rewrite(self) - return TokenStreamCreator(configuration: configuration, operatorTable: operatorTable) - .makeStream(from: commentsMoved) + func makeTokenStream( + configuration: Configuration, + selection: Selection, + operatorTable: OperatorTable + ) -> [Token] { + let commentsMoved = CommentMovingRewriter(selection: selection).rewrite(self) + return TokenStreamCreator( + configuration: configuration, + selection: selection, + operatorTable: operatorTable + ).makeStream(from: commentsMoved) } } @@ -4010,6 +4067,12 @@ extension Syntax { /// For example, comments after binary operators are relocated to be before the operator, which /// results in fewer line breaks with the comment closer to the relevant tokens. class CommentMovingRewriter: SyntaxRewriter { + init(selection: Selection = .infinite) { + self.selection = selection + } + + var selection: Selection + override func visit(_ node: SourceFileSyntax) -> SourceFileSyntax { if shouldFormatterIgnore(file: node) { return node @@ -4018,14 +4081,14 @@ class CommentMovingRewriter: SyntaxRewriter { } override func visit(_ node: CodeBlockItemSyntax) -> CodeBlockItemSyntax { - if shouldFormatterIgnore(node: Syntax(node)) { + if shouldFormatterIgnore(node: Syntax(node)) || !Syntax(node).isInsideSelection(selection) { return node } return super.visit(node) } override func visit(_ node: MemberBlockItemSyntax) -> MemberBlockItemSyntax { - if shouldFormatterIgnore(node: Syntax(node)) { + if shouldFormatterIgnore(node: Syntax(node)) || !Syntax(node).isInsideSelection(selection) { return node } return super.visit(node) diff --git a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift index f7a9b25a8..1c8054d23 100644 --- a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift +++ b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift @@ -15,6 +15,7 @@ open class DiagnosingTestCase: XCTestCase { public func makeContext( sourceFileSyntax: SourceFileSyntax, configuration: Configuration? = nil, + selection: Selection, findingConsumer: @escaping (Finding) -> Void ) -> Context { let context = Context( @@ -22,6 +23,7 @@ open class DiagnosingTestCase: XCTestCase { operatorTable: .standardOperators, findingConsumer: findingConsumer, fileURL: URL(fileURLWithPath: "/tmp/test.swift"), + selection: selection, sourceFileSyntax: sourceFileSyntax, ruleNameCache: ruleNameCache) return context diff --git a/Sources/_SwiftFormatTestSupport/MarkedText.swift b/Sources/_SwiftFormatTestSupport/MarkedText.swift index e43c8ccf8..8ad07572e 100644 --- a/Sources/_SwiftFormatTestSupport/MarkedText.swift +++ b/Sources/_SwiftFormatTestSupport/MarkedText.swift @@ -10,6 +10,9 @@ // //===----------------------------------------------------------------------===// +import SwiftSyntax +import SwiftFormat + /// Encapsulates the locations of emoji markers extracted from source text. public struct MarkedText { /// A mapping from marker names to the UTF-8 offset where the marker was found in the string. @@ -18,23 +21,35 @@ public struct MarkedText { /// The text with all markers removed. public let textWithoutMarkers: String + /// If the marked text contains "⏩" and "⏪", they're used to create a selection + public var selection: Selection + /// Creates a new `MarkedText` value by extracting emoji markers from the given text. public init(textWithMarkers markedText: String) { var text = "" var markers = [String: Int]() var lastIndex = markedText.startIndex + var offsets = [Range]() + var lastRangeStart = 0 for marker in findMarkedRanges(in: markedText) { text += markedText[lastIndex.."), - configuration: configuration) + configuration: configuration, + selection: selection) processFile(fileToProcess) } @@ -162,7 +176,16 @@ class Frontend { return nil } - return FileToProcess(fileHandle: sourceFile, url: url, configuration: configuration) + var selection: Selection = .infinite + if let offsets = lintFormatOptions.offsets { + selection = Selection(offsetPairs: offsets) + } + return FileToProcess( + fileHandle: sourceFile, + url: url, + configuration: configuration, + selection: selection + ) } /// Returns the configuration that applies to the given `.swift` source file, when an explicit diff --git a/Sources/swift-format/Subcommands/LintFormatOptions.swift b/Sources/swift-format/Subcommands/LintFormatOptions.swift index 4e98d1e14..f0eca2010 100644 --- a/Sources/swift-format/Subcommands/LintFormatOptions.swift +++ b/Sources/swift-format/Subcommands/LintFormatOptions.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -26,6 +26,17 @@ struct LintFormatOptions: ParsableArguments { """) var configuration: String? + /// A list of comma-separated "start:end" pairs specifying UTF-8 offsets of the ranges to format. + /// + /// If not specified, the whole file will be formatted. + @Option( + name: .long, + help: """ + A list of comma-separated "start:end" pairs specifying UTF-8 offsets of the ranges to format. + """) + var offsets: [Range]? + + /// The filename for the source code when reading from standard input, to include in diagnostic /// messages. /// @@ -94,6 +105,10 @@ struct LintFormatOptions: ParsableArguments { throw ValidationError("'--assume-filename' is only valid when reading from stdin") } + if offsets?.isEmpty == false && paths.count > 1 { + throw ValidationError("'--offsets' is only valid when processing a single file") + } + if !paths.isEmpty && !recursive { for path in paths { var isDir: ObjCBool = false @@ -109,3 +124,18 @@ struct LintFormatOptions: ParsableArguments { } } } + +extension [Range] : @retroactive ExpressibleByArgument { + public init?(argument: String) { + let pairs = argument.components(separatedBy: ",") + let ranges: [Range] = pairs.compactMap { + let pair = $0.components(separatedBy: ":") + if pair.count == 2, let start = Int(pair[0]), let end = Int(pair[1]), start <= end { + return start ..< end + } else { + return nil + } + } + self = ranges + } +} diff --git a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift index 938fdad49..960033604 100644 --- a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift +++ b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift @@ -58,7 +58,8 @@ final class WhitespaceLinterPerformanceTests: DiagnosingTestCase { /// - expected: The formatted text. private func performWhitespaceLint(input: String, expected: String) { let sourceFileSyntax = Parser.parse(source: input) - let context = makeContext(sourceFileSyntax: sourceFileSyntax, findingConsumer: { _ in }) + let context = makeContext(sourceFileSyntax: sourceFileSyntax, selection: .infinite, + findingConsumer: { _ in }) let linter = WhitespaceLinter(user: input, formatted: expected, context: context) linter.lint() } diff --git a/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift index 4e51f4de5..3153ccd80 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift @@ -1,5 +1,5 @@ final class IgnoreNodeTests: PrettyPrintTestCase { - func atestIgnoreCodeBlockListItems() { + func testIgnoreCodeBlockListItems() { let input = """ x = 4 + 5 // This comment stays here. diff --git a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift index 3aa54af95..eaa33ac3a 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift @@ -43,6 +43,7 @@ class PrettyPrintTestCase: DiagnosingTestCase { let (formatted, context) = prettyPrintedSource( markedInput.textWithoutMarkers, configuration: configuration, + selection: markedInput.selection, whitespaceOnly: whitespaceOnly, findingConsumer: { emittedFindings.append($0) }) assertStringsEqualWithDiff( @@ -64,14 +65,18 @@ class PrettyPrintTestCase: DiagnosingTestCase { // Idempotency check: Running the formatter multiple times should not change the outcome. // Assert that running the formatter again on the previous result keeps it the same. - let (reformatted, _) = prettyPrintedSource( - formatted, - configuration: configuration, - whitespaceOnly: whitespaceOnly, - findingConsumer: { _ in } // Ignore findings during the idempotence check. - ) - assertStringsEqualWithDiff( - reformatted, formatted, "Pretty printer is not idempotent", file: file, line: line) + // But if we have ranges, they aren't going to be valid for the formatted text. + if case .infinite = markedInput.selection { + let (reformatted, _) = prettyPrintedSource( + formatted, + configuration: configuration, + selection: markedInput.selection, + whitespaceOnly: whitespaceOnly, + findingConsumer: { _ in } // Ignore findings during the idempotence check. + ) + assertStringsEqualWithDiff( + reformatted, formatted, "Pretty printer is not idempotent", file: file, line: line) + } } /// Returns the given source code reformatted with the pretty printer. @@ -86,6 +91,7 @@ class PrettyPrintTestCase: DiagnosingTestCase { private func prettyPrintedSource( _ source: String, configuration: Configuration, + selection: Selection, whitespaceOnly: Bool, findingConsumer: @escaping (Finding) -> Void ) -> (String, Context) { @@ -96,9 +102,11 @@ class PrettyPrintTestCase: DiagnosingTestCase { let context = makeContext( sourceFileSyntax: sourceFileSyntax, configuration: configuration, + selection: selection, findingConsumer: findingConsumer) let printer = PrettyPrinter( context: context, + source: source, node: Syntax(sourceFileSyntax), printTokenStream: false, whitespaceOnly: whitespaceOnly) diff --git a/Tests/SwiftFormatTests/PrettyPrint/SelectionTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SelectionTests.swift new file mode 100644 index 000000000..bd08d5ba3 --- /dev/null +++ b/Tests/SwiftFormatTests/PrettyPrint/SelectionTests.swift @@ -0,0 +1,359 @@ +import SwiftFormat +import XCTest + +final class SelectionTests: PrettyPrintTestCase { + func testSelectAll() { + let input = + """ + ⏩func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + // do stuff + } + }⏪ + """ + + let expected = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + // do stuff + } + } + """ + + // The line length ends on the last paren of .Stuff() + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } + + func testSelectComment() { + let input = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + ⏩// do stuff⏪ + } + } + """ + + let expected = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + // do stuff + } + } + """ + + // The line length ends on the last paren of .Stuff() + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } + + func testInsertionPointBeforeComment() { + let input = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + ⏩⏪// do stuff + } + } + """ + + let expected = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + // do stuff + } + } + """ + + // The line length ends on the last paren of .Stuff() + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } + + func testSpacesInline() { + let input = + """ + func foo() { + if let SomeReallyLongVar ⏩ = ⏪Some.More.Stuff(), let a = myfunc() { + // do stuff + } + } + """ + + let expected = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + // do stuff + } + } + """ + + // The line length ends on the last paren of .Stuff() + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } + + func testSpacesFullLine() { + let input = + """ + func foo() { + ⏩if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() {⏪ + // do stuff + } + } + """ + + let expected = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + // do stuff + } + } + """ + + // The line length ends on the last paren of .Stuff() + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } + + func testWrapInline() { + let input = + """ + func foo() { + if let SomeReallyLongVar = ⏩Some.More.Stuff(), let a = myfunc()⏪ { + // do stuff + } + } + """ + + let expected = + """ + func foo() { + if let SomeReallyLongVar = Some.More + .Stuff(), let a = myfunc() { + // do stuff + } + } + """ + + // The line length ends on the last paren of .Stuff() + assertPrettyPrintEqual(input: input, expected: expected, linelength: 44) + } + + func testCommentsOnly() { + let input = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + ⏩// do stuff + // do more stuff⏪ + var i = 0 + } + } + """ + + let expected = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + // do stuff + // do more stuff + var i = 0 + } + } + """ + + // The line length ends on the last paren of .Stuff() + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } + + func testVarOnly() { + let input = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + // do stuff + // do more stuff + ⏩⏪var i = 0 + } + } + """ + + let expected = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + // do stuff + // do more stuff + var i = 0 + } + } + """ + + // The line length ends on the last paren of .Stuff() + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } + + // MARK: - multiple selection ranges + func testFirstCommentAndVar() { + let input = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + ⏩⏪// do stuff + // do more stuff + ⏩⏪var i = 0 + } + } + """ + + let expected = + """ + func foo() { + if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() { + // do stuff + // do more stuff + var i = 0 + } + } + """ + + // The line length ends on the last paren of .Stuff() + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } + + // from AccessorTests (but with some Selection ranges) + func testBasicAccessors() { + let input = + """ + ⏩struct MyStruct { + var memberValue: Int + var someValue: Int { get { return memberValue + 2 } set(newValue) { memberValue = newValue } } + }⏪ + struct MyStruct { + var memberValue: Int + var someValue: Int { @objc get { return memberValue + 2 } @objc(isEnabled) set(newValue) { memberValue = newValue } } + } + struct MyStruct { + var memberValue: Int + var memberValue2: Int + var someValue: Int { + get { + let A = 123 + return A + } + set(newValue) { + memberValue = newValue && otherValue + ⏩memberValue2 = newValue / 2 && andableValue⏪ + } + } + } + struct MyStruct { + var memberValue: Int + var SomeValue: Int { return 123 } + var AnotherValue: Double { + let out = 1.23 + return out + } + } + """ + + let expected = + """ + struct MyStruct { + var memberValue: Int + var someValue: Int { + get { return memberValue + 2 } + set(newValue) { memberValue = newValue } + } + } + struct MyStruct { + var memberValue: Int + var someValue: Int { @objc get { return memberValue + 2 } @objc(isEnabled) set(newValue) { memberValue = newValue } } + } + struct MyStruct { + var memberValue: Int + var memberValue2: Int + var someValue: Int { + get { + let A = 123 + return A + } + set(newValue) { + memberValue = newValue && otherValue + memberValue2 = + newValue / 2 && andableValue + } + } + } + struct MyStruct { + var memberValue: Int + var SomeValue: Int { return 123 } + var AnotherValue: Double { + let out = 1.23 + return out + } + } + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } + + // from CommentTests (but with some Selection ranges) + func testContainerLineComments() { + let input = + """ + // Array comment + let a = [⏩4⏪56, // small comment + 789] + + // Dictionary comment + let b = ["abc": ⏩456, // small comment + "def": 789]⏪ + + // Trailing comment + let c = [123, 456 // small comment + ] + + ⏩/* Array comment */ + let a = [456, /* small comment */ + 789] + + /* Dictionary comment */ + let b = ["abc": 456, /* small comment */ + "def": 789]⏪ + """ + + let expected = + """ + // Array comment + let a = [ + 456, // small comment + 789] + + // Dictionary comment + let b = ["abc": 456, // small comment + "def": 789, + ] + + // Trailing comment + let c = [123, 456 // small comment + ] + + /* Array comment */ + let a = [ + 456, /* small comment */ + 789, + ] + + /* Dictionary comment */ + let b = [ + "abc": 456, /* small comment */ + "def": 789, + ] + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } +} diff --git a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift index 1add23434..78c49752e 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift @@ -39,6 +39,7 @@ class WhitespaceTestCase: DiagnosingTestCase { let context = makeContext( sourceFileSyntax: sourceFileSyntax, configuration: configuration, + selection: .infinite, findingConsumer: { emittedFindings.append($0) }) let linter = WhitespaceLinter( user: markedText.textWithoutMarkers, formatted: expected, context: context) diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index e989bd804..814952e6d 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -27,7 +27,8 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { line: UInt = #line ) { let markedText = MarkedText(textWithMarkers: markedSource) - let tree = Parser.parse(source: markedText.textWithoutMarkers) + let unmarkedSource = markedText.textWithoutMarkers + let tree = Parser.parse(source: unmarkedSource) let sourceFileSyntax = try! OperatorTable.standardOperators.foldAll(tree).as(SourceFileSyntax.self)! @@ -39,6 +40,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { let context = makeContext( sourceFileSyntax: sourceFileSyntax, configuration: configuration, + selection: .infinite, findingConsumer: { emittedFindings.append($0) }) let linter = type.init(context: context) linter.walk(sourceFileSyntax) @@ -60,6 +62,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { pipeline.debugOptions.insert(.disablePrettyPrint) try! pipeline.lint( syntax: sourceFileSyntax, + source: unmarkedSource, operatorTable: OperatorTable.standardOperators, assumingFileURL: URL(string: file.description)!) @@ -96,7 +99,8 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { line: UInt = #line ) { let markedInput = MarkedText(textWithMarkers: input) - let tree = Parser.parse(source: markedInput.textWithoutMarkers) + let originalSource: String = markedInput.textWithoutMarkers + let tree = Parser.parse(source: originalSource) let sourceFileSyntax = try! OperatorTable.standardOperators.foldAll(tree).as(SourceFileSyntax.self)! @@ -108,6 +112,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { let context = makeContext( sourceFileSyntax: sourceFileSyntax, configuration: configuration, + selection: .infinite, findingConsumer: { emittedFindings.append($0) }) let formatter = formatType.init(context: context) @@ -129,6 +134,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { // misplacing trivia in a way that the pretty-printer isn't able to handle). let prettyPrintedSource = PrettyPrinter( context: context, + source: originalSource, node: Syntax(actual), printTokenStream: false, whitespaceOnly: false @@ -148,8 +154,8 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { pipeline.debugOptions.insert(.disablePrettyPrint) var pipelineActual = "" try! pipeline.format( - syntax: sourceFileSyntax, operatorTable: OperatorTable.standardOperators, - assumingFileURL: nil, to: &pipelineActual) + syntax: sourceFileSyntax, source: originalSource, operatorTable: OperatorTable.standardOperators, + assumingFileURL: nil, selection: .infinite, to: &pipelineActual) assertStringsEqualWithDiff(pipelineActual, expected) assertFindings( expected: findings, markerLocations: markedInput.markers, From 88beb8553c4b7500a3c99ea3738a0ea55528d5ae Mon Sep 17 00:00:00 2001 From: David Ewing Date: Mon, 3 Jun 2024 16:08:56 -0400 Subject: [PATCH 206/332] Some small refectorings and updates from review feedback. Add a few more test cases. For formatting a selection (). --- Sources/SwiftFormat/API/Selection.swift | 8 +- Sources/SwiftFormat/API/SwiftFormatter.swift | 3 - Sources/SwiftFormat/CMakeLists.txt | 1 + Sources/SwiftFormat/Core/Context.swift | 4 +- Sources/SwiftFormat/Core/LintPipeline.swift | 4 +- .../SwiftFormat/Core/SyntaxFormatRule.swift | 2 +- .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 81 +++++++++---------- .../PrettyPrint/TokenStreamCreator.swift | 2 +- .../SwiftFormat/Rules/OrderedImports.swift | 2 +- .../_SwiftFormatTestSupport/MarkedText.swift | 2 +- Sources/swift-format/Frontend/Frontend.swift | 4 +- .../Subcommands/LintFormatOptions.swift | 8 +- .../PrettyPrint/SelectionTests.swift | 52 ++++++++++-- 13 files changed, 102 insertions(+), 71 deletions(-) diff --git a/Sources/SwiftFormat/API/Selection.swift b/Sources/SwiftFormat/API/Selection.swift index b3d51aad0..9ea599db3 100644 --- a/Sources/SwiftFormat/API/Selection.swift +++ b/Sources/SwiftFormat/API/Selection.swift @@ -19,11 +19,11 @@ public enum Selection { case ranges([Range]) /// Create a selection from an array of utf8 ranges. An empty array means an infinite selection. - public init(offsetPairs: [Range]) { - if offsetPairs.isEmpty { + public init(offsetRanges: [Range]) { + if offsetRanges.isEmpty { self = .infinite } else { - let ranges = offsetPairs.map { + let ranges = offsetRanges.map { AbsolutePosition(utf8Offset: $0.lowerBound) ..< AbsolutePosition(utf8Offset: $0.upperBound) } self = .ranges(ranges) @@ -51,7 +51,7 @@ public enum Selection { public extension Syntax { - /// return true if the node is _completely_ inside any range in the selection + /// - Returns: `true` if the node is _completely_ inside any range in the selection func isInsideSelection(_ selection: Selection) -> Bool { switch selection { case .infinite: diff --git a/Sources/SwiftFormat/API/SwiftFormatter.swift b/Sources/SwiftFormat/API/SwiftFormatter.swift index 9c8f6f416..e91030b3c 100644 --- a/Sources/SwiftFormat/API/SwiftFormatter.swift +++ b/Sources/SwiftFormat/API/SwiftFormatter.swift @@ -21,9 +21,6 @@ public final class SwiftFormatter { /// The configuration settings that control the formatter's behavior. public let configuration: Configuration - /// the ranges of text to format - public var selection: Selection = .infinite - /// An optional callback that will be notified with any findings encountered during formatting. public let findingConsumer: ((Finding) -> Void)? diff --git a/Sources/SwiftFormat/CMakeLists.txt b/Sources/SwiftFormat/CMakeLists.txt index 4ce86a341..cb3998722 100644 --- a/Sources/SwiftFormat/CMakeLists.txt +++ b/Sources/SwiftFormat/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(SwiftFormat API/Finding.swift API/FindingCategorizing.swift API/Indent.swift + API/Selection.swift API/SwiftFormatError.swift API/SwiftFormatter.swift API/SwiftLinter.swift diff --git a/Sources/SwiftFormat/Core/Context.swift b/Sources/SwiftFormat/Core/Context.swift index 8fde15b3e..2bbb900ac 100644 --- a/Sources/SwiftFormat/Core/Context.swift +++ b/Sources/SwiftFormat/Core/Context.swift @@ -39,7 +39,7 @@ public final class Context { /// The configuration for this run of the pipeline, provided by a configuration JSON file. let configuration: Configuration - /// The optional ranges to process + /// The selection to process let selection: Selection /// Defines the operators and their precedence relationships that were used during parsing. @@ -92,7 +92,7 @@ public final class Context { /// Given a rule's name and the node it is examining, determine if the rule is disabled at this /// location or not. Also makes sure the entire node is contained inside any selection. - func isRuleEnabled(_ rule: R.Type, node: Syntax) -> Bool { + func shouldFormat(_ rule: R.Type, node: Syntax) -> Bool { guard node.isInsideSelection(selection) else { return false } let loc = node.startLocation(converter: self.sourceLocationConverter) diff --git a/Sources/SwiftFormat/Core/LintPipeline.swift b/Sources/SwiftFormat/Core/LintPipeline.swift index 3eb10072d..58d9f6d13 100644 --- a/Sources/SwiftFormat/Core/LintPipeline.swift +++ b/Sources/SwiftFormat/Core/LintPipeline.swift @@ -28,7 +28,7 @@ extension LintPipeline { func visitIfEnabled( _ visitor: (Rule) -> (Node) -> SyntaxVisitorContinueKind, for node: Node ) { - guard context.isRuleEnabled(Rule.self, node: Syntax(node)) else { return } + guard context.shouldFormat(Rule.self, node: Syntax(node)) else { return } let ruleId = ObjectIdentifier(Rule.self) guard self.shouldSkipChildren[ruleId] == nil else { return } let rule = self.rule(Rule.self) @@ -54,7 +54,7 @@ extension LintPipeline { // more importantly because the `visit` methods return protocol refinements of `Syntax` that // cannot currently be expressed as constraints without duplicating this function for each of // them individually. - guard context.isRuleEnabled(Rule.self, node: Syntax(node)) else { return } + guard context.shouldFormat(Rule.self, node: Syntax(node)) else { return } guard self.shouldSkipChildren[ObjectIdentifier(Rule.self)] == nil else { return } let rule = self.rule(Rule.self) _ = visitor(rule)(node) diff --git a/Sources/SwiftFormat/Core/SyntaxFormatRule.swift b/Sources/SwiftFormat/Core/SyntaxFormatRule.swift index 767e59fcf..92fc7c835 100644 --- a/Sources/SwiftFormat/Core/SyntaxFormatRule.swift +++ b/Sources/SwiftFormat/Core/SyntaxFormatRule.swift @@ -32,7 +32,7 @@ public class SyntaxFormatRule: SyntaxRewriter, Rule { public override func visitAny(_ node: Syntax) -> Syntax? { // If the rule is not enabled, then return the node unmodified; otherwise, returning nil tells // SwiftSyntax to continue with the standard dispatch. - guard context.isRuleEnabled(type(of: self), node: node) else { return node } + guard context.shouldFormat(type(of: self), node: node) else { return node } return nil } } diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 57c7a2a63..0b4ff792a 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -69,7 +69,7 @@ public class PrettyPrinter { private var tokens: [Token] private var source: String - /// keep track of where formatting was disabled in the original source + /// Keep track of where formatting was disabled in the original source /// /// To format a selection, we insert `enableFormatting`/`disableFormatting` tokens into the /// stream when entering/exiting a selection range. Those tokens include utf8 offsets into the @@ -77,8 +77,6 @@ public class PrettyPrinter { /// current position to `outputBuffer`. From then on, we continue to format until the next /// `disableFormatting` token. private var disabledPosition: AbsolutePosition? = nil - /// true if we're currently formatting - private var writingIsEnabled: Bool { disabledPosition == nil } private var outputBuffer: String = "" @@ -204,7 +202,9 @@ public class PrettyPrinter { /// /// No further processing is performed on the string. private func writeRaw(_ str: S) { - outputBuffer.append(String(str)) + if disabledPosition == nil { + outputBuffer.append(String(str)) + } } /// Writes newlines into the output stream, taking into account any preexisting consecutive @@ -233,9 +233,7 @@ public class PrettyPrinter { } guard numberToPrint > 0 else { return } - if writingIsEnabled { - writeRaw(String(repeating: "\n", count: numberToPrint)) - } + writeRaw(String(repeating: "\n", count: numberToPrint)) lineNumber += numberToPrint isAtStartOfLine = true consecutiveNewlineCount += numberToPrint @@ -257,17 +255,13 @@ public class PrettyPrinter { /// leading spaces that are required before the text itself. private func write(_ text: String) { if isAtStartOfLine { - if writingIsEnabled { - writeRaw(currentIndentation.indentation()) - } + writeRaw(currentIndentation.indentation()) spaceRemaining = maxLineLength - currentIndentation.length(in: configuration) isAtStartOfLine = false - } else if pendingSpaces > 0 && writingIsEnabled { + } else if pendingSpaces > 0 { writeRaw(String(repeating: " ", count: pendingSpaces)) } - if writingIsEnabled { - writeRaw(text) - } + writeRaw(text) consecutiveNewlineCount = 0 pendingSpaces = 0 } @@ -546,9 +540,7 @@ public class PrettyPrinter { } case .verbatim(let verbatim): - if writingIsEnabled { - writeRaw(verbatim.print(indent: currentIndentation)) - } + writeRaw(verbatim.print(indent: currentIndentation)) consecutiveNewlineCount = 0 pendingSpaces = 0 lastBreak = false @@ -596,38 +588,37 @@ public class PrettyPrinter { } case .enableFormatting(let enabledPosition): - // if we're not disabled, we ignore the token - if let disabledPosition { - let start = source.utf8.index(source.utf8.startIndex, offsetBy: disabledPosition.utf8Offset) - let end: String.Index - if let enabledPosition { - end = source.utf8.index(source.utf8.startIndex, offsetBy: enabledPosition.utf8Offset) - } else { - end = source.endIndex - } - var text = String(source[start.. SourceFileSyntax { if shouldFormatterIgnore(file: node) { diff --git a/Sources/SwiftFormat/Rules/OrderedImports.swift b/Sources/SwiftFormat/Rules/OrderedImports.swift index 292bbd472..6d2475a1b 100644 --- a/Sources/SwiftFormat/Rules/OrderedImports.swift +++ b/Sources/SwiftFormat/Rules/OrderedImports.swift @@ -310,7 +310,7 @@ fileprivate func generateLines(codeBlockItemList: CodeBlockItemListSyntax, conte if currentLine.syntaxNode != nil { appendNewLine() } - let sortable = context.isRuleEnabled(OrderedImports.self, node: Syntax(block)) + let sortable = context.shouldFormat(OrderedImports.self, node: Syntax(block)) var blockWithoutTrailingTrivia = block blockWithoutTrailingTrivia.trailingTrivia = [] currentLine.syntaxNode = .importCodeBlock(blockWithoutTrailingTrivia, sortable: sortable) diff --git a/Sources/_SwiftFormatTestSupport/MarkedText.swift b/Sources/_SwiftFormatTestSupport/MarkedText.swift index 8ad07572e..071a7540a 100644 --- a/Sources/_SwiftFormatTestSupport/MarkedText.swift +++ b/Sources/_SwiftFormatTestSupport/MarkedText.swift @@ -49,7 +49,7 @@ public struct MarkedText { self.markers = markers self.textWithoutMarkers = text - self.selection = Selection(offsetPairs: offsets) + self.selection = Selection(offsetRanges: offsets) } } diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index f010ff582..d9cbd4e94 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -127,7 +127,7 @@ class Frontend { var selection: Selection = .infinite if let offsets = lintFormatOptions.offsets { - selection = Selection(offsetPairs: offsets) + selection = Selection(offsetRanges: offsets) } let fileToProcess = FileToProcess( fileHandle: FileHandle.standardInput, @@ -178,7 +178,7 @@ class Frontend { var selection: Selection = .infinite if let offsets = lintFormatOptions.offsets { - selection = Selection(offsetPairs: offsets) + selection = Selection(offsetRanges: offsets) } return FileToProcess( fileHandle: sourceFile, diff --git a/Sources/swift-format/Subcommands/LintFormatOptions.swift b/Sources/swift-format/Subcommands/LintFormatOptions.swift index f0eca2010..85bcc7f38 100644 --- a/Sources/swift-format/Subcommands/LintFormatOptions.swift +++ b/Sources/swift-format/Subcommands/LintFormatOptions.swift @@ -125,7 +125,7 @@ struct LintFormatOptions: ParsableArguments { } } -extension [Range] : @retroactive ExpressibleByArgument { +extension [Range] { public init?(argument: String) { let pairs = argument.components(separatedBy: ",") let ranges: [Range] = pairs.compactMap { @@ -139,3 +139,9 @@ extension [Range] : @retroactive ExpressibleByArgument { self = ranges } } + +#if compiler(>=6) +extension [Range] : @retroactive ExpressibleByArgument {} +#else +extension [Range] : ExpressibleByArgument {} +#endif diff --git a/Tests/SwiftFormatTests/PrettyPrint/SelectionTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SelectionTests.swift index bd08d5ba3..fb95b2a6e 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/SelectionTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/SelectionTests.swift @@ -21,7 +21,6 @@ final class SelectionTests: PrettyPrintTestCase { } """ - // The line length ends on the last paren of .Stuff() assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) } @@ -44,7 +43,6 @@ final class SelectionTests: PrettyPrintTestCase { } """ - // The line length ends on the last paren of .Stuff() assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) } @@ -67,7 +65,6 @@ final class SelectionTests: PrettyPrintTestCase { } """ - // The line length ends on the last paren of .Stuff() assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) } @@ -90,7 +87,6 @@ final class SelectionTests: PrettyPrintTestCase { } """ - // The line length ends on the last paren of .Stuff() assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) } @@ -113,7 +109,6 @@ final class SelectionTests: PrettyPrintTestCase { } """ - // The line length ends on the last paren of .Stuff() assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) } @@ -164,7 +159,6 @@ final class SelectionTests: PrettyPrintTestCase { } """ - // The line length ends on the last paren of .Stuff() assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) } @@ -191,7 +185,50 @@ final class SelectionTests: PrettyPrintTestCase { } """ - // The line length ends on the last paren of .Stuff() + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } + + func testSingleLineFunc() { + let input = + """ + func foo() ⏩{}⏪ + """ + + let expected = + """ + func foo() {} + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } + + func testSingleLineFunc2() { + let input = + """ + func foo() /**/ ⏩{}⏪ + """ + + let expected = + """ + func foo() /**/ {} + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + } + + func testSimpleFunc() { + let input = + """ + func foo() /**/ + ⏩{}⏪ + """ + + let expected = + """ + func foo() /**/ + {} + """ + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) } @@ -219,7 +256,6 @@ final class SelectionTests: PrettyPrintTestCase { } """ - // The line length ends on the last paren of .Stuff() assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) } From 0c0977dc4645439ae02954047e5af5b51eae78f6 Mon Sep 17 00:00:00 2001 From: David Ewing Date: Tue, 4 Jun 2024 10:22:50 -0400 Subject: [PATCH 207/332] Change the `--offsets` argument to take a single pair of offsets, and support passing multiple of them. --- Sources/swift-format/Frontend/Frontend.swift | 12 ++------ .../Subcommands/LintFormatOptions.swift | 28 ++++++++----------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index d9cbd4e94..247d682d3 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -125,15 +125,11 @@ class Frontend { return } - var selection: Selection = .infinite - if let offsets = lintFormatOptions.offsets { - selection = Selection(offsetRanges: offsets) - } let fileToProcess = FileToProcess( fileHandle: FileHandle.standardInput, url: URL(fileURLWithPath: lintFormatOptions.assumeFilename ?? ""), configuration: configuration, - selection: selection) + selection: Selection(offsetRanges: lintFormatOptions.offsets)) processFile(fileToProcess) } @@ -176,15 +172,11 @@ class Frontend { return nil } - var selection: Selection = .infinite - if let offsets = lintFormatOptions.offsets { - selection = Selection(offsetRanges: offsets) - } return FileToProcess( fileHandle: sourceFile, url: url, configuration: configuration, - selection: selection + selection: Selection(offsetRanges: lintFormatOptions.offsets) ) } diff --git a/Sources/swift-format/Subcommands/LintFormatOptions.swift b/Sources/swift-format/Subcommands/LintFormatOptions.swift index 85bcc7f38..098ad25d1 100644 --- a/Sources/swift-format/Subcommands/LintFormatOptions.swift +++ b/Sources/swift-format/Subcommands/LintFormatOptions.swift @@ -32,10 +32,10 @@ struct LintFormatOptions: ParsableArguments { @Option( name: .long, help: """ - A list of comma-separated "start:end" pairs specifying UTF-8 offsets of the ranges to format. + A "start:end" pair specifying UTF-8 offsets of the range to format. Multiple ranges can be + formatted by specifying several --offsets arguments. """) - var offsets: [Range]? - + var offsets: [Range] = [] /// The filename for the source code when reading from standard input, to include in diagnostic /// messages. @@ -105,7 +105,7 @@ struct LintFormatOptions: ParsableArguments { throw ValidationError("'--assume-filename' is only valid when reading from stdin") } - if offsets?.isEmpty == false && paths.count > 1 { + if !offsets.isEmpty && paths.count > 1 { throw ValidationError("'--offsets' is only valid when processing a single file") } @@ -125,23 +125,19 @@ struct LintFormatOptions: ParsableArguments { } } -extension [Range] { +extension Range { public init?(argument: String) { - let pairs = argument.components(separatedBy: ",") - let ranges: [Range] = pairs.compactMap { - let pair = $0.components(separatedBy: ":") - if pair.count == 2, let start = Int(pair[0]), let end = Int(pair[1]), start <= end { - return start ..< end - } else { - return nil - } + let pair = argument.components(separatedBy: ":") + if pair.count == 2, let start = Int(pair[0]), let end = Int(pair[1]), start <= end { + self = start ..< end + } else { + return nil } - self = ranges } } #if compiler(>=6) -extension [Range] : @retroactive ExpressibleByArgument {} +extension Range : @retroactive ExpressibleByArgument {} #else -extension [Range] : ExpressibleByArgument {} +extension Range : ExpressibleByArgument {} #endif From 2541a149a583c964ea60f967891e8272373d89e1 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 14 Jun 2024 03:02:00 +0000 Subject: [PATCH 208/332] Fix `@_expose` attribute argument spacing Pretty-print the `@_expose` with the correct spacing. It was formatted as `@_expose(wasm,"foo")` instead of `@_expose(wasm, "foo")`. --- .../PrettyPrint/TokenStreamCreator.swift | 5 ++++ .../PrettyPrint/AttributeTests.swift | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 08f1aa4f0..95525d058 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -1802,6 +1802,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } + override func visit(_ node: ExposeAttributeArgumentsSyntax) -> SyntaxVisitorContinueKind { + after(node.comma, tokens: .break(.same, size: 1)) + return .visitChildren + } + override func visit(_ node: AvailabilityLabeledArgumentSyntax) -> SyntaxVisitorContinueKind { before(node.label, tokens: .open) diff --git a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift index 3031fc31b..3ff5db02d 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift @@ -444,4 +444,28 @@ final class AttributeTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 100) } + + func testAttributeParamSpacingInExpose() { + let input = + """ + @_expose( wasm , "foo" ) + func f() {} + + @_expose( Cxx , "bar") + func b() {} + + """ + + let expected = + """ + @_expose(wasm, "foo") + func f() {} + + @_expose(Cxx, "bar") + func b() {} + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 100) + } } From 95d85a2180cb1c5214735082d3810b92c86671fc Mon Sep 17 00:00:00 2001 From: Neil Jones Date: Tue, 18 Jun 2024 10:48:16 +0900 Subject: [PATCH 209/332] add support for riscv64 --- cmake/modules/SwiftSupport.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake index 35decfcca..c37055a33 100644 --- a/cmake/modules/SwiftSupport.cmake +++ b/cmake/modules/SwiftSupport.cmake @@ -47,6 +47,8 @@ function(get_swift_host_arch result_var_name) set("${result_var_name}" "i686" PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "wasm32") set("${result_var_name}" "wasm32" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "riscv64") + set("${result_var_name}" "riscv64" PARENT_SCOPE) else() message(FATAL_ERROR "Unrecognized architecture on host system: ${CMAKE_SYSTEM_PROCESSOR}") endif() From ae0922db4e41cb18c2e055e563854bcc15629e09 Mon Sep 17 00:00:00 2001 From: Paris Date: Mon, 24 Jun 2024 11:17:00 -0700 Subject: [PATCH 210/332] Delete CODE_OF_CONDUCT.md deleting in favor of the organization wide coc; this file present means that the repo is opt-ing out of that --- CODE_OF_CONDUCT.md | 55 ---------------------------------------------- 1 file changed, 55 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 2b0a60355..000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,55 +0,0 @@ -# Code of Conduct -To be a truly great community, Swift.org needs to welcome developers from all walks of life, -with different backgrounds, and with a wide range of experience. A diverse and friendly -community will have more great ideas, more unique perspectives, and produce more great -code. We will work diligently to make the Swift community welcoming to everyone. - -To give clarity of what is expected of our members, Swift.org has adopted the code of conduct -defined by [contributor-covenant.org](https://www.contributor-covenant.org). This document is used across many open source -communities, and we think it articulates our values well. The full text is copied below: - -### Contributor Code of Conduct v1.3 -As contributors and maintainers of this project, and in the interest of fostering an open and -welcoming community, we pledge to respect all people who contribute through reporting -issues, posting feature requests, updating documentation, submitting pull requests or patches, -and other activities. - -We are committed to making participation in this project a harassment-free experience for -everyone, regardless of level of experience, gender, gender identity and expression, sexual -orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or -nationality. - -Examples of unacceptable behavior by participants include: -- The use of sexualized language or imagery -- Personal attacks -- Trolling or insulting/derogatory comments -- Public or private harassment -- Publishing other’s private information, such as physical or electronic addresses, without explicit permission -- Other unethical or unprofessional conduct - -Project maintainers have the right and responsibility to remove, edit, or reject comments, -commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of -Conduct, or to ban temporarily or permanently any contributor for other behaviors that they -deem inappropriate, threatening, offensive, or harmful. - -By adopting this Code of Conduct, project maintainers commit themselves to fairly and -consistently applying these principles to every aspect of managing this project. Project -maintainers who do not follow or enforce the Code of Conduct may be permanently removed -from the project team. - -This code of conduct applies both within project spaces and in public spaces when an -individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by -contacting a project maintainer at [conduct@swift.org](mailto:conduct@swift.org). All complaints will be reviewed and -investigated and will result in a response that is deemed necessary and appropriate to the -circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter -of an incident. - -*This policy is adapted from the Contributor Code of Conduct [version 1.3.0](http://contributor-covenant.org/version/1/3/0/).* - -### Reporting -A working group of community members is committed to promptly addressing any [reported -issues](mailto:conduct@swift.org). Working group members are volunteers appointed by the project lead, with a -preference for individuals with varied backgrounds and perspectives. Membership is expected -to change regularly, and may grow or shrink. From 981c130a79862ba9c267fd689576d40ecaa4982d Mon Sep 17 00:00:00 2001 From: Paris Date: Mon, 24 Jun 2024 11:18:09 -0700 Subject: [PATCH 211/332] Delete CONTRIBUTING.md contains old language; moving towards a unified strategy with CONTRIBUTING files. --- CONTRIBUTING.md | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 9f01e1f3b..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,11 +0,0 @@ -By submitting a pull request, you represent that you have the right to license -your contribution to Apple and the community, and agree by submitting the patch -that your contributions are licensed under the [Swift -license](https://swift.org/LICENSE.txt). - ---- - -Before submitting the pull request, please make sure you have [tested your -changes](https://github.com/apple/swift/blob/main/docs/ContinuousIntegration.md) -and that they follow the Swift project [guidelines for contributing -code](https://swift.org/contributing/#contributing-code). From 1ca1b53cae94802a4442f27fcdc87299eedc6911 Mon Sep 17 00:00:00 2001 From: Paris Date: Mon, 24 Jun 2024 11:22:58 -0700 Subject: [PATCH 212/332] Update README.md adding in a contribution section from the removal of the contributing.md file --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 6b29d73eb..09a5144d4 100644 --- a/README.md +++ b/README.md @@ -251,3 +251,24 @@ been merged into `main`. If you are interested in developing `swift-format`, there is additional documentation about that [here](Documentation/Development.md). + +## Contributing + +Contributions to Swift are welcomed and encouraged! Please see the +[Contributing to Swift guide](https://swift.org/contributing/). + +Before submitting the pull request, please make sure you have [tested your + changes](https://github.com/apple/swift/blob/main/docs/ContinuousIntegration.md) + and that they follow the Swift project [guidelines for contributing + code](https://swift.org/contributing/#contributing-code). + +To be a truly great community, [Swift.org](https://swift.org/) needs to welcome +developers from all walks of life, with different backgrounds, and with a wide +range of experience. A diverse and friendly community will have more great +ideas, more unique perspectives, and produce more great code. We will work +diligently to make the Swift community welcoming to everyone. + +To give clarity of what is expected of our members, Swift has adopted the +code of conduct defined by the Contributor Covenant. This document is used +across many open source communities, and we think it articulates our values +well. For more, see the [Code of Conduct](https://swift.org/code-of-conduct/). From cef7eca0f466474c69e3b7420c59bbaf459aaff5 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 25 Jun 2024 04:36:28 -0700 Subject: [PATCH 213/332] Update links for repositories moved to the swiftlang org on GitHub --- CMakeLists.txt | 2 +- Documentation/PrettyPrinter.md | 2 +- Package.swift | 2 +- README.md | 8 ++++---- Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 00a9f061b..28bd4e530 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,7 +68,7 @@ endif() find_package(SwiftSyntax CONFIG) if(NOT SwiftSyntax_FOUND) FetchContent_Declare(Syntax - GIT_REPOSITORY https://github.com/apple/swift-syntax + GIT_REPOSITORY https://github.com/swiftlang/swift-syntax GIT_TAG main) list(APPEND _SF_VENDOR_DEPENDENCIES Syntax) endif() diff --git a/Documentation/PrettyPrinter.md b/Documentation/PrettyPrinter.md index bfc81aacc..2883d65a5 100644 --- a/Documentation/PrettyPrinter.md +++ b/Documentation/PrettyPrinter.md @@ -263,7 +263,7 @@ if someCondition { ### Token Generation Token generation begins with the abstract syntax tree (AST) of the Swift source -file, provided by the [SwiftSyntax](https://github.com/apple/swift-syntax) +file, provided by the [SwiftSyntax](https://github.com/swiftlang/swift-syntax) library. We have overloaded a `visit` method for each of the different kinds of syntax nodes. Most of these nodes are higher-level, and are composed of other nodes. For example, `FunctionDeclSyntax` contains diff --git a/Package.swift b/Package.swift index 9a1995039..e1f123b36 100644 --- a/Package.swift +++ b/Package.swift @@ -163,7 +163,7 @@ var dependencies: [Package.Dependency] { return [ .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2"), .package(url: "https://github.com/apple/swift-markdown.git", from: "0.2.0"), - .package(url: "https://github.com/apple/swift-syntax.git", branch: "main"), + .package(url: "https://github.com/swiftlang/swift-syntax.git", branch: "main"), ] } } diff --git a/README.md b/README.md index 6b29d73eb..cb05fc9a4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # swift-format `swift-format` provides the formatting technology for -[SourceKit-LSP](https://github.com/apple/sourcekit-lsp) and the building +[SourceKit-LSP](https://github.com/swiftlang/sourcekit-lsp) and the building blocks for doing code formatting transformations. This package can be used as a [command line tool](#command-line-usage) @@ -18,7 +18,7 @@ invoked via an [API](#api-usage). ### Swift 5.8 and later As of Swift 5.8, swift-format depends on the version of -[SwiftSyntax](https://github.com/apple/swift-syntax) whose parser has been +[SwiftSyntax](https://github.com/swiftlang/swift-syntax) whose parser has been rewritten in Swift and no longer has dependencies on libraries in the Swift toolchain. @@ -34,7 +34,7 @@ SwiftSyntax; the 5.8 release of swift-format is `508.0.0`, not `0.50800.0`. ### Swift 5.7 and earlier `swift-format` versions 0.50700.0 and earlier depend on versions of -[SwiftSyntax](https://github.com/apple/swift-syntax) that used a standalone +[SwiftSyntax](https://github.com/swiftlang/swift-syntax) that used a standalone parsing library distributed as part of the Swift toolchain. When using these versions, you should check out and build `swift-format` from the release tag or branch that is compatible with the version of Swift you are using. @@ -74,7 +74,7 @@ Install `swift-format` using the following commands: ```sh VERSION=510.1.0 # replace this with the version you need -git clone https://github.com/apple/swift-format.git +git clone https://github.com/swiftlang/swift-format.git cd swift-format git checkout "tags/$VERSION" swift build -c release diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 163457514..9f71a4fbf 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -853,7 +853,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: YieldStmtSyntax) -> SyntaxVisitorContinueKind { - // As of https://github.com/apple/swift-syntax/pull/895, the token following a `yield` keyword + // As of https://github.com/swiftlang/swift-syntax/pull/895, the token following a `yield` keyword // *must* be on the same line, so we cannot break here. after(node.yieldKeyword, tokens: .space) return .visitChildren From 56fa13b526dc344f079e999de6c31a21261e9c85 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 25 Jun 2024 04:55:33 -0700 Subject: [PATCH 214/332] Update README.md to mention that swift-format is included in Xcode 16 --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6b29d73eb..3706fa875 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,11 @@ For example, if you are using Xcode 13.3 (Swift 5.6), you will need ## Getting swift-format If you are mainly interested in using swift-format (rather than developing it), -then you can get swift-format either via [Homebrew](https://brew.sh/) or by checking out the -source and building it. +then you can get it in three different ways: + +### Included in Xcode + +Xcode 16 and above include swift-format in the toolchain. You can run `swift-format` from anywhere on the system using `swift format` (notice the space instead of dash). To find the path at which `swift-format` is installed, run `xcrun --find swift-format`. ### Installing via Homebrew From a8ff3ca6045e1fbef3ef76ea73382028421fe450 Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Tue, 25 Jun 2024 10:55:23 -0400 Subject: [PATCH 215/332] Split the PrettyPrint class into two pieces. This change breaks up the PrettyPrint class into one piece that is responsible for tracking the state of the pretty printing algorithm - processing the tokens, tracking the break stack, comma delimited region stack, etc.; and another piece that is responsible for assembling the output and tracking the state of the output - current line, column position, and indentation. --- .../PrettyPrint/Indent+Length.swift | 10 +- .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 175 +++++------------- .../PrettyPrint/PrettyPrintBuffer.swift | 140 ++++++++++++++ 3 files changed, 194 insertions(+), 131 deletions(-) create mode 100644 Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift diff --git a/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift b/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift index 062d50a07..3094735be 100644 --- a/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift +++ b/Sources/SwiftFormat/PrettyPrint/Indent+Length.swift @@ -22,10 +22,10 @@ extension Indent { return String(repeating: character, count: count) } - func length(in configuration: Configuration) -> Int { + func length(tabWidth: Int) -> Int { switch self { case .spaces(let count): return count - case .tabs(let count): return count * configuration.tabWidth + case .tabs(let count): return count * tabWidth } } } @@ -36,6 +36,10 @@ extension Array where Element == Indent { } func length(in configuration: Configuration) -> Int { - return reduce(into: 0) { $0 += $1.length(in: configuration) } + return self.length(tabWidth: configuration.tabWidth) + } + + func length(tabWidth: Int) -> Int { + return reduce(into: 0) { $0 += $1.length(tabWidth: tabWidth) } } } diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 0b4ff792a..5ab608a8a 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -76,12 +76,13 @@ public class PrettyPrinter { /// original source. When enabling formatting, we copy the text between `disabledPosition` and the /// current position to `outputBuffer`. From then on, we continue to format until the next /// `disableFormatting` token. - private var disabledPosition: AbsolutePosition? = nil - - private var outputBuffer: String = "" + private var disabledPosition: AbsolutePosition? = nil { + didSet { + outputBuffer.isEnabled = disabledPosition == nil + } + } - /// The number of spaces remaining on the current line. - private var spaceRemaining: Int + private var outputBuffer: PrettyPrintBuffer /// Keep track of the token lengths. private var lengths = [Int]() @@ -103,7 +104,11 @@ public class PrettyPrinter { /// Keeps track of the line numbers and indentation states of the open (and unclosed) breaks seen /// so far. - private var activeOpenBreaks: [ActiveOpenBreak] = [] + private var activeOpenBreaks: [ActiveOpenBreak] = [] { + didSet { + outputBuffer.currentIndentation = currentIndentation + } + } /// Stack of the active breaking contexts. private var activeBreakingContexts: [ActiveBreakingContext] = [] @@ -111,11 +116,14 @@ public class PrettyPrinter { /// The most recently ended breaking context, used to force certain following `contextual` breaks. private var lastEndedBreakingContext: ActiveBreakingContext? = nil - /// Keeps track of the current line number being printed. - private var lineNumber: Int = 1 - /// Indicates whether or not the current line being printed is a continuation line. - private var currentLineIsContinuation = false + private var currentLineIsContinuation = false { + didSet { + if oldValue != currentLineIsContinuation { + outputBuffer.currentIndentation = currentIndentation + } + } + } /// Keeps track of the continuation line state as you go into and out of open-close break groups. private var continuationStack: [Bool] = [] @@ -124,18 +132,6 @@ public class PrettyPrinter { /// corresponding end token are encountered. private var commaDelimitedRegionStack: [Int] = [] - /// Keeps track of the most recent number of consecutive newlines that have been printed. - /// - /// This value is reset to zero whenever non-newline content is printed. - private var consecutiveNewlineCount = 0 - - /// Keeps track of the most recent number of spaces that should be printed before the next text - /// token. - private var pendingSpaces = 0 - - /// Indicates whether or not the printer is currently at the beginning of a line. - private var isAtStartOfLine = true - /// Tracks how many printer control tokens to suppress firing breaks are active. private var activeBreakSuppressionCount = 0 @@ -173,7 +169,7 @@ public class PrettyPrinter { /// line number to increase by one by the time we reach the break, when we really wish to consider /// the break as being located at the end of the previous line. private var openCloseBreakCompensatingLineNumber: Int { - return isAtStartOfLine ? lineNumber - 1 : lineNumber + return outputBuffer.lineNumber - (outputBuffer.isAtStartOfLine ? 1 : 0) } /// Creates a new PrettyPrinter with the provided formatting configuration. @@ -193,77 +189,9 @@ public class PrettyPrinter { selection: context.selection, operatorTable: context.operatorTable) self.maxLineLength = configuration.lineLength - self.spaceRemaining = self.maxLineLength self.printTokenStream = printTokenStream self.whitespaceOnly = whitespaceOnly - } - - /// Append the given string to the output buffer. - /// - /// No further processing is performed on the string. - private func writeRaw(_ str: S) { - if disabledPosition == nil { - outputBuffer.append(String(str)) - } - } - - /// Writes newlines into the output stream, taking into account any preexisting consecutive - /// newlines and the maximum allowed number of blank lines. - /// - /// This function does some implicit collapsing of consecutive newlines to ensure that the - /// results are consistent when breaks and explicit newlines coincide. For example, imagine a - /// break token that fires (thus creating a single non-discretionary newline) because it is - /// followed by a group that contains 2 discretionary newlines that were found in the user's - /// source code at that location. In that case, the break "overlaps" with the discretionary - /// newlines and it will write a newline before we get to the discretionaries. Thus, we have to - /// subtract the previously written newlines during the second call so that we end up with the - /// correct number overall. - /// - /// - Parameter newlines: The number and type of newlines to write. - private func writeNewlines(_ newlines: NewlineBehavior) { - let numberToPrint: Int - switch newlines { - case .elective: - numberToPrint = consecutiveNewlineCount == 0 ? 1 : 0 - case .soft(let count, _): - // We add 1 to the max blank lines because it takes 2 newlines to create the first blank line. - numberToPrint = min(count, configuration.maximumBlankLines + 1) - consecutiveNewlineCount - case .hard(let count): - numberToPrint = count - } - - guard numberToPrint > 0 else { return } - writeRaw(String(repeating: "\n", count: numberToPrint)) - lineNumber += numberToPrint - isAtStartOfLine = true - consecutiveNewlineCount += numberToPrint - pendingSpaces = 0 - } - - /// Request that the given number of spaces be printed out before the next text token. - /// - /// Spaces are printed only when the next text token is printed in order to prevent us from - /// printing lines that are only whitespace or have trailing whitespace. - private func enqueueSpaces(_ count: Int) { - pendingSpaces += count - spaceRemaining -= count - } - - /// Writes the given text to the output stream. - /// - /// Before printing the text, this function will print any line-leading indentation or interior - /// leading spaces that are required before the text itself. - private func write(_ text: String) { - if isAtStartOfLine { - writeRaw(currentIndentation.indentation()) - spaceRemaining = maxLineLength - currentIndentation.length(in: configuration) - isAtStartOfLine = false - } else if pendingSpaces > 0 { - writeRaw(String(repeating: " ", count: pendingSpaces)) - } - writeRaw(text) - consecutiveNewlineCount = 0 - pendingSpaces = 0 + self.outputBuffer = PrettyPrintBuffer(maximumBlankLines: configuration.maximumBlankLines, tabWidth: configuration.tabWidth) } /// Print out the provided token, and apply line-wrapping and indentation as needed. @@ -285,7 +213,7 @@ public class PrettyPrinter { switch token { case .contextualBreakingStart: - activeBreakingContexts.append(ActiveBreakingContext(lineNumber: lineNumber)) + activeBreakingContexts.append(ActiveBreakingContext(lineNumber: outputBuffer.lineNumber)) // Discard the last finished breaking context to keep it from effecting breaks inside of the // new context. The discarded context has already either had an impact on the contextual break @@ -306,7 +234,7 @@ public class PrettyPrinter { // the group. case .open(let breaktype): // Determine if the break tokens in this group need to be forced. - if (length > spaceRemaining || lastBreak), case .consistent = breaktype { + if (shouldBreak(length) || lastBreak), case .consistent = breaktype { forceBreakStack.append(true) } else { forceBreakStack.append(false) @@ -348,7 +276,7 @@ public class PrettyPrinter { // scope), so we need the continuation indentation to persist across all the lines in that // scope. Additionally, continuation open breaks must indent when the break fires. let continuationBreakWillFire = openKind == .continuation - && (isAtStartOfLine || length > spaceRemaining || mustBreak) + && (outputBuffer.isAtStartOfLine || shouldBreak(length) || mustBreak) let contributesContinuationIndent = currentLineIsContinuation || continuationBreakWillFire activeOpenBreaks.append( @@ -377,7 +305,7 @@ public class PrettyPrinter { if matchingOpenBreak.contributesBlockIndent { // The actual line number is used, instead of the compensating line number. When the close // break is at the start of a new line, the block indentation isn't carried to the new line. - let currentLine = lineNumber + let currentLine = outputBuffer.lineNumber // When two or more open breaks are encountered on the same line, only the final open // break is allowed to increase the block indent, avoiding multiple block indents. As the // open breaks on that line are closed, the new final open break must be enabled again to @@ -395,7 +323,7 @@ public class PrettyPrinter { // If it's a mandatory breaking close, then we must break (regardless of line length) if // the break is on a different line than its corresponding open break. mustBreak = openedOnDifferentLine - } else if spaceRemaining == 0 { + } else if shouldBreak(1) { // If there is no room left on the line, then we must force this break to fire so that the // next token that comes along (typically a closing bracket of some kind) ends up on the // next line. @@ -453,13 +381,13 @@ public class PrettyPrinter { // context includes a multiline trailing closure or multiline function argument list. if let lastBreakingContext = lastEndedBreakingContext { if configuration.lineBreakAroundMultilineExpressionChainComponents { - mustBreak = lastBreakingContext.lineNumber != lineNumber + mustBreak = lastBreakingContext.lineNumber != outputBuffer.lineNumber } } // Wait for a contextual break to fire and then update the breaking behavior for the rest of // the contextual breaks in this scope to match the behavior of the one that fired. - let willFire = (!isAtStartOfLine && length > spaceRemaining) || mustBreak + let willFire = shouldBreak(length) || mustBreak if willFire { // Update the active breaking context according to the most recently finished breaking // context so all following contextual breaks in this scope to have matching behavior. @@ -468,7 +396,7 @@ public class PrettyPrinter { case .unset = activeContext.contextualBreakingBehavior { activeBreakingContexts[activeBreakingContexts.count - 1].contextualBreakingBehavior = - (closedContext.lineNumber == lineNumber) ? .continuation : .maintain + (closedContext.lineNumber == outputBuffer.lineNumber) ? .continuation : .maintain } } @@ -499,12 +427,12 @@ public class PrettyPrinter { } let suppressBreaking = isBreakingSuppressed && !overrideBreakingSuppressed - if !suppressBreaking && ((!isAtStartOfLine && length > spaceRemaining) || mustBreak) { + if !suppressBreaking && (shouldBreak(length) || mustBreak) { currentLineIsContinuation = isContinuationIfBreakFires - writeNewlines(newline) + outputBuffer.writeNewlines(newline) lastBreak = true } else { - if isAtStartOfLine { + if outputBuffer.isAtStartOfLine { // Make sure that the continuation status is correct even at the beginning of a line // (for example, after a newline token). This is necessary because a discretionary newline // might be inserted into the token stream before a continuation break, and the length of @@ -512,39 +440,33 @@ public class PrettyPrinter { // treat the line as a continuation. currentLineIsContinuation = isContinuationIfBreakFires } - enqueueSpaces(size) + outputBuffer.enqueueSpaces(size) lastBreak = false } // Print out the number of spaces according to the size, and adjust spaceRemaining. case .space(let size, _): - enqueueSpaces(size) + outputBuffer.enqueueSpaces(size) // Print any indentation required, followed by the text content of the syntax token. case .syntax(let text): guard !text.isEmpty else { break } lastBreak = false - write(text) - spaceRemaining -= text.count + outputBuffer.write(text) case .comment(let comment, let wasEndOfLine): lastBreak = false - write(comment.print(indent: currentIndentation)) if wasEndOfLine { - if comment.length > spaceRemaining && !isBreakingSuppressed { + if shouldBreak(comment.length) && !isBreakingSuppressed { diagnose(.moveEndOfLineComment, category: .endOfLineComment) } - } else { - spaceRemaining -= comment.length } + outputBuffer.write(comment.print(indent: currentIndentation)) case .verbatim(let verbatim): - writeRaw(verbatim.print(indent: currentIndentation)) - consecutiveNewlineCount = 0 - pendingSpaces = 0 + outputBuffer.writeVerbatim(verbatim.print(indent: currentIndentation), length) lastBreak = false - spaceRemaining -= length case .printerControl(let kind): switch kind { @@ -583,8 +505,7 @@ public class PrettyPrinter { let shouldWriteComma = whitespaceOnly ? hasTrailingComma : shouldHaveTrailingComma if shouldWriteComma { - write(",") - spaceRemaining -= 1 + outputBuffer.write(",") } case .enableFormatting(let enabledPosition): @@ -607,14 +528,7 @@ public class PrettyPrinter { } self.disabledPosition = nil - writeRaw(text) - if text.hasSuffix("\n") { - isAtStartOfLine = true - consecutiveNewlineCount = 1 - } else { - isAtStartOfLine = false - consecutiveNewlineCount = 0 - } + outputBuffer.writeVerbatimAfterEnablingFormatting(text) case .disableFormatting(let newPosition): assert(disabledPosition == nil) @@ -622,6 +536,11 @@ public class PrettyPrinter { } } + private func shouldBreak(_ length: Int) -> Bool { + let spaceRemaining = configuration.lineLength - outputBuffer.column + return !outputBuffer.isAtStartOfLine && length > spaceRemaining + } + /// Scan over the array of Tokens and calculate their lengths. /// /// This method is based on the `scan` function described in Derek Oppen's "Pretty Printing" paper @@ -748,7 +667,7 @@ public class PrettyPrinter { fatalError("At least one .break(.open) was not matched by a .break(.close)") } - return outputBuffer + return outputBuffer.output } /// Used to track the indentation level for the debug token stream output. @@ -843,11 +762,11 @@ public class PrettyPrinter { /// Emits a finding with the given message and category at the current location in `outputBuffer`. private func diagnose(_ message: Finding.Message, category: PrettyPrintFindingCategory) { // Add 1 since columns uses 1-based indices. - let column = maxLineLength - spaceRemaining + 1 + let column = outputBuffer.column + 1 context.findingEmitter.emit( message, category: category, - location: Finding.Location(file: context.fileURL.path, line: lineNumber, column: column)) + location: Finding.Location(file: context.fileURL.path, line: outputBuffer.lineNumber, column: column)) } } diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift new file mode 100644 index 000000000..c975b1f7e --- /dev/null +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift @@ -0,0 +1,140 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation + +struct PrettyPrintBuffer { + let maximumBlankLines: Int + let tabWidth: Int + + var isEnabled: Bool = true + + /// Indicates whether or not the printer is currently at the beginning of a line. + private(set) var isAtStartOfLine: Bool = true + + /// Keeps track of the most recent number of consecutive newlines that have been printed. + /// + /// This value is reset to zero whenever non-newline content is printed. + private(set) var consecutiveNewlineCount: Int = 0 + + /// Keeps track of the current line number being printed. + private(set) var lineNumber: Int = 1 + + /// Keeps track of the most recent number of spaces that should be printed before the next text + /// token. + private(set) var pendingSpaces: Int = 0 + + /// Current column position of the printer. If we just printed a newline and nothing else, it + /// will still point to the position of the previous line. + private(set) var column: Int + + var currentIndentation: [Indent] + + private(set) var output: String = "" + + init(maximumBlankLines: Int, tabWidth: Int, column: Int = 0) { + self.maximumBlankLines = maximumBlankLines + self.tabWidth = tabWidth + self.currentIndentation = [] + self.column = column + } + + /// Writes newlines into the output stream, taking into account any preexisting consecutive + /// newlines and the maximum allowed number of blank lines. + /// + /// This function does some implicit collapsing of consecutive newlines to ensure that the + /// results are consistent when breaks and explicit newlines coincide. For example, imagine a + /// break token that fires (thus creating a single non-discretionary newline) because it is + /// followed by a group that contains 2 discretionary newlines that were found in the user's + /// source code at that location. In that case, the break "overlaps" with the discretionary + /// newlines and it will write a newline before we get to the discretionaries. Thus, we have to + /// subtract the previously written newlines during the second call so that we end up with the + /// correct number overall. + /// + /// - Parameter newlines: The number and type of newlines to write. + mutating func writeNewlines(_ newlines: NewlineBehavior) { + let numberToPrint: Int + switch newlines { + case .elective: + numberToPrint = consecutiveNewlineCount == 0 ? 1 : 0 + case .soft(let count, _): + // We add 1 to the max blank lines because it takes 2 newlines to create the first blank line. + numberToPrint = min(count, maximumBlankLines + 1) - consecutiveNewlineCount + case .hard(let count): + numberToPrint = count + } + + guard numberToPrint > 0 else { return } + writeRaw(String(repeating: "\n", count: numberToPrint)) + lineNumber += numberToPrint + isAtStartOfLine = true + consecutiveNewlineCount += numberToPrint + pendingSpaces = 0 + column = 0 + } + + /// Writes the given text to the output stream. + /// + /// Before printing the text, this function will print any line-leading indentation or interior + /// leading spaces that are required before the text itself. + mutating func write(_ text: String) { + if isAtStartOfLine { + writeRaw(currentIndentation.indentation()) + column = currentIndentation.length(tabWidth: tabWidth) + isAtStartOfLine = false + } else if pendingSpaces > 0 { + writeRaw(String(repeating: " ", count: pendingSpaces)) + } + writeRaw(text) + consecutiveNewlineCount = 0 + pendingSpaces = 0 + column += text.count + } + + /// Request that the given number of spaces be printed out before the next text token. + /// + /// Spaces are printed only when the next text token is printed in order to prevent us from + /// printing lines that are only whitespace or have trailing whitespace. + mutating func enqueueSpaces(_ count: Int) { + pendingSpaces += count + column += count + } + + mutating func writeVerbatim(_ verbatim: String, _ length: Int) { + writeRaw(verbatim) + consecutiveNewlineCount = 0 + pendingSpaces = 0 + column += length + } + + /// Calls writeRaw, but also updates some state variables that are normally tracked by + /// higher level functions. This is used when we switch from disabled formatting to + /// enabled formatting, writing all the previous information as-is. + mutating func writeVerbatimAfterEnablingFormatting(_ str: S) { + writeRaw(str) + if str.hasSuffix("\n") { + isAtStartOfLine = true + consecutiveNewlineCount = 1 + } else { + isAtStartOfLine = false + consecutiveNewlineCount = 0 + } + } + + /// Append the given string to the output buffer. + /// + /// No further processing is performed on the string. + private mutating func writeRaw(_ str: S) { + guard isEnabled else { return } + output.append(String(str)) + } +} From 415b8ea939267d18aa198ba73705a450d8846a4a Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Wed, 26 Jun 2024 12:02:47 -0400 Subject: [PATCH 216/332] Add missing comments for new struct and its properties. --- .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 18 ++++++++++-------- .../PrettyPrint/PrettyPrintBuffer.swift | 10 ++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 5ab608a8a..ada574f1c 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -234,7 +234,7 @@ public class PrettyPrinter { // the group. case .open(let breaktype): // Determine if the break tokens in this group need to be forced. - if (shouldBreak(length) || lastBreak), case .consistent = breaktype { + if (!canFit(length) || lastBreak), case .consistent = breaktype { forceBreakStack.append(true) } else { forceBreakStack.append(false) @@ -276,7 +276,7 @@ public class PrettyPrinter { // scope), so we need the continuation indentation to persist across all the lines in that // scope. Additionally, continuation open breaks must indent when the break fires. let continuationBreakWillFire = openKind == .continuation - && (outputBuffer.isAtStartOfLine || shouldBreak(length) || mustBreak) + && (outputBuffer.isAtStartOfLine || !canFit(length) || mustBreak) let contributesContinuationIndent = currentLineIsContinuation || continuationBreakWillFire activeOpenBreaks.append( @@ -323,7 +323,7 @@ public class PrettyPrinter { // If it's a mandatory breaking close, then we must break (regardless of line length) if // the break is on a different line than its corresponding open break. mustBreak = openedOnDifferentLine - } else if shouldBreak(1) { + } else if !canFit() { // If there is no room left on the line, then we must force this break to fire so that the // next token that comes along (typically a closing bracket of some kind) ends up on the // next line. @@ -387,7 +387,7 @@ public class PrettyPrinter { // Wait for a contextual break to fire and then update the breaking behavior for the rest of // the contextual breaks in this scope to match the behavior of the one that fired. - let willFire = shouldBreak(length) || mustBreak + let willFire = !canFit(length) || mustBreak if willFire { // Update the active breaking context according to the most recently finished breaking // context so all following contextual breaks in this scope to have matching behavior. @@ -427,7 +427,7 @@ public class PrettyPrinter { } let suppressBreaking = isBreakingSuppressed && !overrideBreakingSuppressed - if !suppressBreaking && (shouldBreak(length) || mustBreak) { + if !suppressBreaking && (!canFit(length) || mustBreak) { currentLineIsContinuation = isContinuationIfBreakFires outputBuffer.writeNewlines(newline) lastBreak = true @@ -458,7 +458,7 @@ public class PrettyPrinter { lastBreak = false if wasEndOfLine { - if shouldBreak(comment.length) && !isBreakingSuppressed { + if !(canFit(comment.length) || isBreakingSuppressed) { diagnose(.moveEndOfLineComment, category: .endOfLineComment) } } @@ -536,9 +536,11 @@ public class PrettyPrinter { } } - private func shouldBreak(_ length: Int) -> Bool { + /// Indicates whether the current line can fit a string of the given length. If no length + /// is given, it indicates whether the current line can accomodate *any* text. + private func canFit(_ length: Int = 1) -> Bool { let spaceRemaining = configuration.lineLength - outputBuffer.column - return !outputBuffer.isAtStartOfLine && length > spaceRemaining + return outputBuffer.isAtStartOfLine || length <= spaceRemaining } /// Scan over the array of Tokens and calculate their lengths. diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift index c975b1f7e..02a2a7ac6 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift @@ -12,10 +12,18 @@ import Foundation +/// Used by the PrettyPrint class to actually assemble the output string. This struct +/// tracks state specific to the output (line number, column, etc.) rather than the pretty +/// printing algorithm itself. struct PrettyPrintBuffer { + /// The maximum number of consecutive blank lines that may appear in a file. let maximumBlankLines: Int + + /// The width of the horizontal tab in spaces. let tabWidth: Int + /// If true, output is generated as normal. If false, the various state variables are + /// updated as normal but nothing is appended to the output (used by selection formatting). var isEnabled: Bool = true /// Indicates whether or not the printer is currently at the beginning of a line. @@ -37,8 +45,10 @@ struct PrettyPrintBuffer { /// will still point to the position of the previous line. private(set) var column: Int + /// The current indentation level to be used when text is appended to a new line. var currentIndentation: [Indent] + /// The accumulated output of the pretty printer. private(set) var output: String = "" init(maximumBlankLines: Int, tabWidth: Int, column: Int = 0) { From dce1e601759d1ceea6e646552dfd3ff5763b1601 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 27 Jun 2024 21:53:18 +0200 Subject: [PATCH 217/332] Add PrettyPrintBuffer.swift to CMakeLists.txt --- Sources/SwiftFormat/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SwiftFormat/CMakeLists.txt b/Sources/SwiftFormat/CMakeLists.txt index cb3998722..062e12dc7 100644 --- a/Sources/SwiftFormat/CMakeLists.txt +++ b/Sources/SwiftFormat/CMakeLists.txt @@ -46,6 +46,7 @@ add_library(SwiftFormat PrettyPrint/Comment.swift PrettyPrint/Indent+Length.swift PrettyPrint/PrettyPrint.swift + PrettyPrint/PrettyPrintBuffer.swift PrettyPrint/PrettyPrintFindingCategory.swift PrettyPrint/Token.swift PrettyPrint/TokenStreamCreator.swift From d0954cbad476f1548508133ac71b556cce6971b0 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 9 Jul 2024 16:07:03 -0600 Subject: [PATCH 218/332] Add swift-format rule warning for retroactive conformances. This provides a lint rule whenever an `@retroactive` appears in a source file. I've marked the rule as opt-out, but I'm also wondering if we have a syntax for opting out of a rule in general. --- Documentation/RuleDocumentation.md | 9 +++++ .../Core/Pipelines+Generated.swift | 10 +++++ .../Core/RuleNameCache+Generated.swift | 1 + .../Core/RuleRegistry+Generated.swift | 1 + .../Rules/AvoidRetroactiveConformances.swift | 38 +++++++++++++++++++ .../AvoidRetroactiveConformancesTests.swift | 17 +++++++++ 6 files changed, 76 insertions(+) create mode 100644 Sources/SwiftFormat/Rules/AvoidRetroactiveConformances.swift create mode 100644 Tests/SwiftFormatTests/Rules/AvoidRetroactiveConformancesTests.swift diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index 654bc8b04..d9fa83760 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -14,6 +14,7 @@ Here's the list of available rules: - [AlwaysUseLiteralForEmptyCollectionInit](#AlwaysUseLiteralForEmptyCollectionInit) - [AlwaysUseLowerCamelCase](#AlwaysUseLowerCamelCase) - [AmbiguousTrailingClosureOverload](#AmbiguousTrailingClosureOverload) +- [AvoidRetroactiveConformances](#AvoidRetroactiveConformances) - [BeginDocumentationCommentWithOneLineSummary](#BeginDocumentationCommentWithOneLineSummary) - [DoNotUseSemicolons](#DoNotUseSemicolons) - [DontRepeatTypeInStaticProperties](#DontRepeatTypeInStaticProperties) @@ -93,6 +94,14 @@ Lint: If two overloaded functions with one closure parameter appear in the same `AmbiguousTrailingClosureOverload` is a linter-only rule. +### AvoidRetroactiveConformances + +`@retroactive` conformances are forbidden. + +Lint: Using `@retroactive` results in a lint error. + +`AvoidRetroactiveConformances` is a linter-only rule. + ### BeginDocumentationCommentWithOneLineSummary All documentation comments must begin with a one-line summary of the declaration. diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 9707ee80f..c0af02e39 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -64,6 +64,14 @@ class LintPipeline: SyntaxVisitor { onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) } + override func visit(_ node: AttributeSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AvoidRetroactiveConformances.visit, for: node) + return .visitChildren + } + override func visitPost(_ node: AttributeSyntax) { + onVisitPost(rule: AvoidRetroactiveConformances.self, for: node) + } + override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) @@ -193,12 +201,14 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AvoidRetroactiveConformances.visit, for: node) visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) visitIfEnabled(NoAccessLevelOnExtensionDeclaration.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } override func visitPost(_ node: ExtensionDeclSyntax) { + onVisitPost(rule: AvoidRetroactiveConformances.self, for: node) onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) onVisitPost(rule: NoAccessLevelOnExtensionDeclaration.self, for: node) onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) diff --git a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift index b19b88d6b..ac542c1d4 100644 --- a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift @@ -19,6 +19,7 @@ public let ruleNameCache: [ObjectIdentifier: String] = [ ObjectIdentifier(AlwaysUseLiteralForEmptyCollectionInit.self): "AlwaysUseLiteralForEmptyCollectionInit", ObjectIdentifier(AlwaysUseLowerCamelCase.self): "AlwaysUseLowerCamelCase", ObjectIdentifier(AmbiguousTrailingClosureOverload.self): "AmbiguousTrailingClosureOverload", + ObjectIdentifier(AvoidRetroactiveConformances.self): "AvoidRetroactiveConformances", ObjectIdentifier(BeginDocumentationCommentWithOneLineSummary.self): "BeginDocumentationCommentWithOneLineSummary", ObjectIdentifier(DoNotUseSemicolons.self): "DoNotUseSemicolons", ObjectIdentifier(DontRepeatTypeInStaticProperties.self): "DontRepeatTypeInStaticProperties", diff --git a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift index 9fb96fcf0..a5aeeab1f 100644 --- a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift @@ -18,6 +18,7 @@ "AlwaysUseLiteralForEmptyCollectionInit": false, "AlwaysUseLowerCamelCase": true, "AmbiguousTrailingClosureOverload": true, + "AvoidRetroactiveConformances": true, "BeginDocumentationCommentWithOneLineSummary": false, "DoNotUseSemicolons": true, "DontRepeatTypeInStaticProperties": true, diff --git a/Sources/SwiftFormat/Rules/AvoidRetroactiveConformances.swift b/Sources/SwiftFormat/Rules/AvoidRetroactiveConformances.swift new file mode 100644 index 000000000..1573d3de2 --- /dev/null +++ b/Sources/SwiftFormat/Rules/AvoidRetroactiveConformances.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// `@retroactive` conformances are forbidden. +/// +/// Lint: Using `@retroactive` results in a lint error. +@_spi(Rules) +public final class AvoidRetroactiveConformances: SyntaxLintRule { + public override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { + if let inheritanceClause = node.inheritanceClause { + walk(inheritanceClause) + } + return .skipChildren + } + public override func visit(_ type: AttributeSyntax) -> SyntaxVisitorContinueKind { + if let identifier = type.attributeName.as(IdentifierTypeSyntax.self) { + if identifier.name.text == "retroactive" { + diagnose(.doNotUseRetroactive, on: type) + } + } + return .skipChildren + } +} + +extension Finding.Message { + fileprivate static let doNotUseRetroactive: Finding.Message = "do not declare retroactive conformances" +} diff --git a/Tests/SwiftFormatTests/Rules/AvoidRetroactiveConformancesTests.swift b/Tests/SwiftFormatTests/Rules/AvoidRetroactiveConformancesTests.swift new file mode 100644 index 000000000..2616d2581 --- /dev/null +++ b/Tests/SwiftFormatTests/Rules/AvoidRetroactiveConformancesTests.swift @@ -0,0 +1,17 @@ +import _SwiftFormatTestSupport + +@_spi(Rules) import SwiftFormat + +final class AvoidRetroactiveConformancesTests: LintOrFormatRuleTestCase { + func testRetroactiveConformanceIsDiagnosed() { + assertLint( + AvoidRetroactiveConformances.self, + """ + extension Int: 1️⃣@retroactive Identifiable {} + """, + findings: [ + FindingSpec("1️⃣", message: "do not declare retroactive conformances"), + ] + ) + } +} From e99495c4872a435b9440d7805a634bea7a839a88 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Thu, 11 Jul 2024 14:49:11 -0600 Subject: [PATCH 219/332] Add source file to CMakeLists --- Sources/SwiftFormat/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SwiftFormat/CMakeLists.txt b/Sources/SwiftFormat/CMakeLists.txt index 062e12dc7..7ea0c97f6 100644 --- a/Sources/SwiftFormat/CMakeLists.txt +++ b/Sources/SwiftFormat/CMakeLists.txt @@ -57,6 +57,7 @@ add_library(SwiftFormat Rules/AlwaysUseLiteralForEmptyCollectionInit.swift Rules/AlwaysUseLowerCamelCase.swift Rules/AmbiguousTrailingClosureOverload.swift + Rules/AvoidRetroactiveConformances.swift Rules/BeginDocumentationCommentWithOneLineSummary.swift Rules/DoNotUseSemicolons.swift Rules/DontRepeatTypeInStaticProperties.swift From 1c9d74317759b240a04d1f064833189fe04394a9 Mon Sep 17 00:00:00 2001 From: Tigran Hambardzumyan Date: Sat, 13 Jul 2024 20:56:33 +0400 Subject: [PATCH 220/332] Add check to allow underscores in test functions marked with @Test attribute --- Documentation/RuleDocumentation.md | 1 + .../Rules/AlwaysUseLowerCamelCase.swift | 6 +++++- .../Rules/AlwaysUseLowerCamelCaseTests.swift | 20 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index d9fa83760..9d6f8d657 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -79,6 +79,7 @@ Underscores (except at the beginning of an identifier) are disallowed. This rule does not apply to test code, defined as code which: * Contains the line `import XCTest` + * The function is marked with `@Test` attribute Lint: If an identifier contains underscores or begins with a capital letter, a lint error is raised. diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift index e24d23178..8e11938ce 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift @@ -100,7 +100,11 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { // We allow underscores in test names, because there's an existing convention of using // underscores to separate phrases in very detailed test names. - let allowUnderscores = testCaseFuncs.contains(node) + let allowUnderscores = testCaseFuncs.contains(node) || node.attributes.contains { + // Allow underscore for test functions with the `@Test` attribute. + $0.as(AttributeSyntax.self)?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "Test" + } + diagnoseLowerCamelCaseViolations( node.name, allowUnderscores: allowUnderscores, description: identifierDescription(for: node)) diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift index 59201e42e..750183609 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift @@ -210,4 +210,24 @@ final class AlwaysUseLowerCamelCaseTests: LintOrFormatRuleTestCase { ] ) } + + func testIgnoresFunctionsWithTestAttributes() { + assertLint( + AlwaysUseLowerCamelCase.self, + """ + @Test + func function_With_Test_Attribute() {} + @Test("Description for test functions", + .tags(.testTag)) + func function_With_Test_Attribute_And_Args() {} + func 1️⃣function_Without_Test_Attribute() {} + @objc + func 2️⃣function_With_Non_Test_Attribute() {} + """, + findings: [ + FindingSpec("1️⃣", message: "rename the function 'function_Without_Test_Attribute' using lowerCamelCase"), + FindingSpec("2️⃣", message: "rename the function 'function_With_Non_Test_Attribute' using lowerCamelCase"), + ] + ) + } } From 5a63aee663a33b860c6a3bdfe8c5c15944908158 Mon Sep 17 00:00:00 2001 From: Tigran Hambardzumyan Date: Sun, 14 Jul 2024 18:00:07 +0400 Subject: [PATCH 221/332] Add Swift Testing checks In the test code validation added checks to consider new Swift Testing syntax as well. --- Documentation/RuleDocumentation.md | 3 +++ .../Core/SyntaxProtocol+Convenience.swift | 15 +++++++++++++++ .../SwiftFormat/Rules/NeverForceUnwrap.swift | 4 ++++ .../SwiftFormat/Rules/NeverUseForceTry.swift | 2 ++ ...NeverUseImplicitlyUnwrappedOptionals.swift | 2 ++ .../Rules/NeverForceUnwrapTests.swift | 19 +++++++++++++++++++ .../Rules/NeverUseForceTryTests.swift | 16 ++++++++++++++++ ...UseImplicitlyUnwrappedOptionalsTests.swift | 16 ++++++++++++++++ 8 files changed, 77 insertions(+) diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index d9fa83760..521f64723 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -188,6 +188,7 @@ Force-unwraps are strongly discouraged and must be documented. This rule does not apply to test code, defined as code which: * Contains the line `import XCTest` + * The function is marked with `@Test` attribute Lint: If a force unwrap is used, a lint warning is raised. @@ -199,6 +200,7 @@ Force-try (`try!`) is forbidden. This rule does not apply to test code, defined as code which: * Contains the line `import XCTest` + * The function is marked with `@Test` attribute Lint: Using `try!` results in a lint error. @@ -214,6 +216,7 @@ Certain properties (e.g. `@IBOutlet`) tied to the UI lifecycle are ignored. This rule does not apply to test code, defined as code which: * Contains the line `import XCTest` + * The function is marked with `@Test` attribute TODO: Create exceptions for other UI elements (ex: viewDidLoad) diff --git a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift index 033410212..02b9a328a 100644 --- a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift +++ b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift @@ -149,6 +149,21 @@ extension SyntaxProtocol { } return leadingTrivia.hasAnyComments } + + /// Indicates whether the node has any function ancestor marked with `@Test` attribute. + var hasTestAncestor: Bool { + var parent = self.parent + while let existingParent = parent { + if let functionDecl = existingParent.as(FunctionDeclSyntax.self), + functionDecl.attributes.contains(where: { + $0.as(AttributeSyntax.self)?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "Test" + }) { + return true + } + parent = existingParent.parent + } + return false + } } extension SyntaxCollection { diff --git a/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift index 29937b987..05a7b935c 100644 --- a/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift +++ b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift @@ -34,6 +34,8 @@ public final class NeverForceUnwrap: SyntaxLintRule { public override func visit(_ node: ForceUnwrapExprSyntax) -> SyntaxVisitorContinueKind { guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren } + // Allow force unwrapping if it is in a function marked with @Test attribute. + if node.hasTestAncestor { return .skipChildren } diagnose(.doNotForceUnwrap(name: node.expression.trimmedDescription), on: node) return .skipChildren } @@ -44,6 +46,8 @@ public final class NeverForceUnwrap: SyntaxLintRule { guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren } guard let questionOrExclamation = node.questionOrExclamationMark else { return .skipChildren } guard questionOrExclamation.tokenKind == .exclamationMark else { return .skipChildren } + // Allow force cast if it is in a function marked with @Test attribute. + if node.hasTestAncestor { return .skipChildren } diagnose(.doNotForceCast(name: node.type.trimmedDescription), on: node) return .skipChildren } diff --git a/Sources/SwiftFormat/Rules/NeverUseForceTry.swift b/Sources/SwiftFormat/Rules/NeverUseForceTry.swift index 68d81f652..2be281cd1 100644 --- a/Sources/SwiftFormat/Rules/NeverUseForceTry.swift +++ b/Sources/SwiftFormat/Rules/NeverUseForceTry.swift @@ -36,6 +36,8 @@ public final class NeverUseForceTry: SyntaxLintRule { public override func visit(_ node: TryExprSyntax) -> SyntaxVisitorContinueKind { guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren } guard let mark = node.questionOrExclamationMark else { return .visitChildren } + // Allow force try if it is in a function marked with @Test attribute. + if node.hasTestAncestor { return .skipChildren } if mark.tokenKind == .exclamationMark { diagnose(.doNotForceTry, on: node.tryKeyword) } diff --git a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift index 65635cc74..ff5fad1b5 100644 --- a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -39,6 +39,8 @@ public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule { public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren } + // Allow implicitly unwrapping if it is in a function marked with @Test attribute. + if node.hasTestAncestor { return .skipChildren } // Ignores IBOutlet variables for attribute in node.attributes { if (attribute.as(AttributeSyntax.self))?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "IBOutlet" { diff --git a/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift b/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift index bf68c4589..7ce22b686 100644 --- a/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift @@ -40,4 +40,23 @@ final class NeverForceUnwrapTests: LintOrFormatRuleTestCase { findings: [] ) } + + func testIgnoreTestAttributeFunction() { + assertLint( + NeverForceUnwrap.self, + """ + @Test + func testSomeFunc() { + var b = a as! Int + } + @Test + func testAnotherFunc() { + func nestedFunc() { + let c = someValue()! + } + } + """, + findings: [] + ) + } } diff --git a/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift index 2be59ac47..0fd51be28 100644 --- a/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift @@ -38,4 +38,20 @@ final class NeverUseForceTryTests: LintOrFormatRuleTestCase { findings: [] ) } + + func testAllowForceTryInTestAttributeFunction() { + assertLint( + NeverUseForceTry.self, + """ + @Test + func testSomeFunc() { + let document = try! Document(path: "important.data") + func nestedFunc() { + let x = try! someThrowingFunction() + } + } + """, + findings: [] + ) + } } diff --git a/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift index 6a210b826..c79ee3db0 100644 --- a/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift @@ -35,4 +35,20 @@ final class NeverUseImplicitlyUnwrappedOptionalsTests: LintOrFormatRuleTestCase findings: [] ) } + + func testIgnoreTestAttrinuteFunction() { + assertLint( + NeverUseImplicitlyUnwrappedOptionals.self, + """ + @Test + func testSomeFunc() { + var s: String! + func nestedFunc() { + var f: Foo! + } + } + """, + findings: [] + ) + } } From 77779acc5919e0f3021b3694943c13c1460614c9 Mon Sep 17 00:00:00 2001 From: Tigran Hambardzumyan Date: Mon, 15 Jul 2024 19:06:13 +0400 Subject: [PATCH 222/332] Move attribute lookup to separate extension --- Sources/SwiftFormat/CMakeLists.txt | 1 + .../WithAttributesSyntax+Convenience.swift | 28 +++++++++++++++++++ .../Rules/AlwaysUseLowerCamelCase.swift | 5 +--- .../Rules/AlwaysUseLowerCamelCaseTests.swift | 2 +- 4 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift diff --git a/Sources/SwiftFormat/CMakeLists.txt b/Sources/SwiftFormat/CMakeLists.txt index 7ea0c97f6..2564f90b8 100644 --- a/Sources/SwiftFormat/CMakeLists.txt +++ b/Sources/SwiftFormat/CMakeLists.txt @@ -42,6 +42,7 @@ add_library(SwiftFormat Core/SyntaxLintRule.swift Core/SyntaxProtocol+Convenience.swift Core/Trivia+Convenience.swift + Core/WithAttributesSyntax+Convenience.swift Core/WithSemicolonSyntax.swift PrettyPrint/Comment.swift PrettyPrint/Indent+Length.swift diff --git a/Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift b/Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift new file mode 100644 index 000000000..4e6c0a44b --- /dev/null +++ b/Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +extension WithAttributesSyntax { + /// Indicates whether the node has attribute with the given `name`. + /// + /// - Parameter name: The name of the attribute to lookup. + /// - Returns: True if the node has an attribute with the given `name`, otherwise false. + func hasAttribute(_ name: String) -> Bool { + attributes.contains { attribute in + let attributeName = attribute.as(AttributeSyntax.self)?.attributeName + return attributeName?.as(IdentifierTypeSyntax.self)?.name.text == name + // support @Module.Attribute syntax as well + || attributeName?.as(MemberTypeSyntax.self)?.name.text == name + } + } +} diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift index 8e11938ce..d248cb020 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift @@ -100,10 +100,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { // We allow underscores in test names, because there's an existing convention of using // underscores to separate phrases in very detailed test names. - let allowUnderscores = testCaseFuncs.contains(node) || node.attributes.contains { - // Allow underscore for test functions with the `@Test` attribute. - $0.as(AttributeSyntax.self)?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "Test" - } + let allowUnderscores = testCaseFuncs.contains(node) || node.hasAttribute("Test") diagnoseLowerCamelCaseViolations( node.name, allowUnderscores: allowUnderscores, diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift index 750183609..89eba4765 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift @@ -217,7 +217,7 @@ final class AlwaysUseLowerCamelCaseTests: LintOrFormatRuleTestCase { """ @Test func function_With_Test_Attribute() {} - @Test("Description for test functions", + @Testing.Test("Description for test functions", .tags(.testTag)) func function_With_Test_Attribute_And_Args() {} func 1️⃣function_Without_Test_Attribute() {} From c7d80181e0c2601ea752db3ee7cd814a525b83da Mon Sep 17 00:00:00 2001 From: Tigran Hambardzumyan Date: Mon, 15 Jul 2024 19:09:16 +0400 Subject: [PATCH 223/332] Use new extension for attribute lookup --- Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift index 02b9a328a..f1dbf2a90 100644 --- a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift +++ b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift @@ -154,10 +154,7 @@ extension SyntaxProtocol { var hasTestAncestor: Bool { var parent = self.parent while let existingParent = parent { - if let functionDecl = existingParent.as(FunctionDeclSyntax.self), - functionDecl.attributes.contains(where: { - $0.as(AttributeSyntax.self)?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "Test" - }) { + if let functionDecl = existingParent.as(FunctionDeclSyntax.self), functionDecl.hasAttribute("Test") { return true } parent = existingParent.parent From b10cf160fa020b01d2e36fbdf9a806cd8b46238e Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Mon, 15 Jul 2024 11:44:58 -0700 Subject: [PATCH 224/332] Add a break after `do` when it has typed throws Pretty-printing of `do` statements with typed throws was skipping the space between the `do` and `throws`. Make sure to add it in. --- .../PrettyPrint/TokenStreamCreator.swift | 3 +++ .../PrettyPrint/DoStmtTests.swift | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 9f71a4fbf..8e7dc617b 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -707,6 +707,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: DoStmtSyntax) -> SyntaxVisitorContinueKind { + if node.throwsClause != nil { + after(node.doKeyword, tokens: .break(.same, size: 1)) + } arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) return .visitChildren } diff --git a/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift index fdb491adb..ddeb3ea6a 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift @@ -57,5 +57,30 @@ final class DoStmtTests: PrettyPrintTestCase { """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) } + + func testDoTypedThrowsStmt() { + let input = + """ + do throws(FooError) { + foo() + } + """ + + assertPrettyPrintEqual(input: input, expected: + """ + do + throws(FooError) { + foo() + } + + """, linelength: 18) + assertPrettyPrintEqual(input: input, expected: + """ + do throws(FooError) { + foo() + } + + """, linelength: 25) + } } From 8019514fa1462b64be8272a90f34e6bf841d9b2d Mon Sep 17 00:00:00 2001 From: Tigran Hambardzumyan Date: Thu, 18 Jul 2024 22:19:14 +0400 Subject: [PATCH 225/332] Update hasAttribute extension signature to check module name as well. --- .../Core/SyntaxProtocol+Convenience.swift | 3 +- .../WithAttributesSyntax+Convenience.swift | 29 ++++++++++++------- .../Rules/AlwaysUseLowerCamelCase.swift | 2 +- .../Rules/AlwaysUseLowerCamelCaseTests.swift | 3 ++ 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift index f1dbf2a90..94147ef05 100644 --- a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift +++ b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift @@ -154,7 +154,8 @@ extension SyntaxProtocol { var hasTestAncestor: Bool { var parent = self.parent while let existingParent = parent { - if let functionDecl = existingParent.as(FunctionDeclSyntax.self), functionDecl.hasAttribute("Test") { + if let functionDecl = existingParent.as(FunctionDeclSyntax.self), + functionDecl.hasAttribute("Test", inModule: "Testing") { return true } parent = existingParent.parent diff --git a/Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift b/Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift index 4e6c0a44b..55616c6ab 100644 --- a/Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift +++ b/Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift @@ -13,16 +13,23 @@ import SwiftSyntax extension WithAttributesSyntax { - /// Indicates whether the node has attribute with the given `name`. - /// - /// - Parameter name: The name of the attribute to lookup. - /// - Returns: True if the node has an attribute with the given `name`, otherwise false. - func hasAttribute(_ name: String) -> Bool { - attributes.contains { attribute in - let attributeName = attribute.as(AttributeSyntax.self)?.attributeName - return attributeName?.as(IdentifierTypeSyntax.self)?.name.text == name - // support @Module.Attribute syntax as well - || attributeName?.as(MemberTypeSyntax.self)?.name.text == name - } + /// Indicates whether the node has attribute with the given `name`. + /// + /// - Parameter name: The name of the attribute to lookup. + /// - Parameter module: The module name to lookup the attribute in. + /// - Returns: True if the node has an attribute with the given `name`, otherwise false. + func hasAttribute(_ name: String, inModule module: String) -> Bool { + attributes.contains { attribute in + let attributeName = attribute.as(AttributeSyntax.self)?.attributeName + if let identifier = attributeName?.as(IdentifierTypeSyntax.self) { + return identifier.name.text == name + } + // support @Module.Attribute syntax as well + if let memberType = attributeName?.as(MemberTypeSyntax.self) { + return memberType.name.text == name + && memberType.baseType.as(IdentifierTypeSyntax.self)?.name.text == module + } + return false } + } } diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift index d248cb020..368d8d69d 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift @@ -100,7 +100,7 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { // We allow underscores in test names, because there's an existing convention of using // underscores to separate phrases in very detailed test names. - let allowUnderscores = testCaseFuncs.contains(node) || node.hasAttribute("Test") + let allowUnderscores = testCaseFuncs.contains(node) || node.hasAttribute("Test", inModule: "Testing") diagnoseLowerCamelCaseViolations( node.name, allowUnderscores: allowUnderscores, diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift index 89eba4765..1dbca2191 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift @@ -223,10 +223,13 @@ final class AlwaysUseLowerCamelCaseTests: LintOrFormatRuleTestCase { func 1️⃣function_Without_Test_Attribute() {} @objc func 2️⃣function_With_Non_Test_Attribute() {} + @Foo.Test + func 3️⃣function_With_Test_Attribute_From_Foo_Module() {} """, findings: [ FindingSpec("1️⃣", message: "rename the function 'function_Without_Test_Attribute' using lowerCamelCase"), FindingSpec("2️⃣", message: "rename the function 'function_With_Non_Test_Attribute' using lowerCamelCase"), + FindingSpec("3️⃣", message: "rename the function 'function_With_Test_Attribute_From_Foo_Module' using lowerCamelCase"), ] ) } From d2d5dc3c5754bc17df950d2710f6cfda09bb5074 Mon Sep 17 00:00:00 2001 From: Tigran Hambardzumyan Date: Fri, 19 Jul 2024 01:23:26 +0400 Subject: [PATCH 226/332] updating the doc for hasAttribute --- .../SwiftFormat/Core/WithAttributesSyntax+Convenience.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift b/Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift index 55616c6ab..f5938d01e 100644 --- a/Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift +++ b/Sources/SwiftFormat/Core/WithAttributesSyntax+Convenience.swift @@ -13,7 +13,8 @@ import SwiftSyntax extension WithAttributesSyntax { - /// Indicates whether the node has attribute with the given `name`. + /// Indicates whether the node has attribute with the given `name` and `module`. + /// The `module` is only considered if the attribute is written as `@Module.Attribute`. /// /// - Parameter name: The name of the attribute to lookup. /// - Parameter module: The module name to lookup the attribute in. @@ -22,10 +23,11 @@ extension WithAttributesSyntax { attributes.contains { attribute in let attributeName = attribute.as(AttributeSyntax.self)?.attributeName if let identifier = attributeName?.as(IdentifierTypeSyntax.self) { + // @Attribute syntax return identifier.name.text == name } - // support @Module.Attribute syntax as well if let memberType = attributeName?.as(MemberTypeSyntax.self) { + // @Module.Attribute syntax return memberType.name.text == name && memberType.baseType.as(IdentifierTypeSyntax.self)?.name.text == module } From 38d8e103a11b816c729e5989fa043f07f78195b7 Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Fri, 19 Jul 2024 14:26:49 -0400 Subject: [PATCH 227/332] Improve formatting of macro decls with attributes. Whitespace and newlines were being lost in the token stream when prefixing a macro with an attribute. Call the common attribute handling code, and add a break before the left brace of the decl. --- .../SwiftFormat/PrettyPrint/TokenStreamCreator.swift | 6 ++++++ .../PrettyPrint/MacroCallTests.swift | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 9f71a4fbf..90c861474 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -1324,6 +1324,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: MacroExpansionDeclSyntax) -> SyntaxVisitorContinueKind { + arrangeAttributeList(node.attributes) + + before( + node.trailingClosure?.leftBrace, + tokens: .break(.same, newlines: .elective(ignoresDiscretionary: true))) + arrangeFunctionCallArgumentList( node.arguments, leftDelimiter: node.leftParen, diff --git a/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift b/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift index dffbef001..0616c66e3 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift @@ -114,4 +114,16 @@ final class MacroCallTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) } + + func testMacroDeclWithAttributesAndArguments() { + let input = """ + @nonsenseAttribute + @available(iOS 17.0, *) + #Preview("Name") { + EmptyView() + } + + """ + assertPrettyPrintEqual(input: input, expected: input, linelength: 45) + } } From 4a99f1c212625f924285c91c37452d23d1e6700c Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sat, 20 Jul 2024 08:25:18 -0700 Subject: [PATCH 228/332] Add option to customize number of spaces leading `//` comments Add an option `spacesBeforeLineComments` that allows customization of number of spaces between the first non-trivial token and a line comment with the `//` prefix. Defaults to 2, the historical default value. --- Documentation/Configuration.md | 3 + .../API/Configuration+Default.swift | 1 + Sources/SwiftFormat/API/Configuration.swift | 8 + .../PrettyPrint/TokenStreamCreator.swift | 2 +- .../PrettyPrint/CommentTests.swift | 223 ++++++++++++++++++ 5 files changed, 236 insertions(+), 1 deletion(-) diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 1035286ce..84d7ae9a7 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -31,6 +31,9 @@ top-level keys and values: lines that are allowed to be present in a source file. Any number larger than this will be collapsed down to the maximum. +* `spacesBeforeEndOfLineComments` _(number)_: The number of spaces between + the last token on a non-empty line and a line comment starting with `//`. + * `respectsExistingLineBreaks` _(boolean)_: Indicates whether or not existing line breaks in the source code should be honored (if they are valid according to the style guidelines being enforced). If this settings is diff --git a/Sources/SwiftFormat/API/Configuration+Default.swift b/Sources/SwiftFormat/API/Configuration+Default.swift index 3f0123fb4..52a91873e 100644 --- a/Sources/SwiftFormat/API/Configuration+Default.swift +++ b/Sources/SwiftFormat/API/Configuration+Default.swift @@ -26,6 +26,7 @@ extension Configuration { self.lineLength = 100 self.tabWidth = 8 self.indentation = .spaces(2) + self.spacesBeforeEndOfLineComments = 2 self.respectsExistingLineBreaks = true self.lineBreakBeforeControlFlowKeywords = false self.lineBreakBeforeEachArgument = false diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index ab8a3e952..d1259bacf 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -28,6 +28,7 @@ public struct Configuration: Codable, Equatable { case version case maximumBlankLines case lineLength + case spacesBeforeEndOfLineComments case tabWidth case indentation case respectsExistingLineBreaks @@ -66,6 +67,9 @@ public struct Configuration: Codable, Equatable { /// The maximum length of a line of source code, after which the formatter will break lines. public var lineLength: Int + // Number of spaces that preceeds line comments. + public var spacesBeforeEndOfLineComments: Int + /// The width of the horizontal tab in spaces. /// /// This value is used when converting indentation types (for example, from tabs into spaces). @@ -225,6 +229,9 @@ public struct Configuration: Codable, Equatable { self.lineLength = try container.decodeIfPresent(Int.self, forKey: .lineLength) ?? defaults.lineLength + self.spacesBeforeEndOfLineComments = + try container.decodeIfPresent(Int.self, forKey: .spacesBeforeEndOfLineComments) + ?? defaults.spacesBeforeEndOfLineComments self.tabWidth = try container.decodeIfPresent(Int.self, forKey: .tabWidth) ?? defaults.tabWidth @@ -288,6 +295,7 @@ public struct Configuration: Codable, Equatable { try container.encode(version, forKey: .version) try container.encode(maximumBlankLines, forKey: .maximumBlankLines) try container.encode(lineLength, forKey: .lineLength) + try container.encode(spacesBeforeEndOfLineComments, forKey: .spacesBeforeEndOfLineComments) try container.encode(tabWidth, forKey: .tabWidth) try container.encode(indentation, forKey: .indentation) try container.encode(respectsExistingLineBreaks, forKey: .respectsExistingLineBreaks) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 8e7dc617b..468dc519c 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -3171,7 +3171,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return ( true, [ - .space(size: 2, flexible: true), + .space(size: config.spacesBeforeEndOfLineComments, flexible: true), .comment(Comment(kind: .line, text: text), wasEndOfLine: true), // There must be a break with a soft newline after the comment, but it's impossible to // know which kind of break must be used. Adding this newline is deferred until the diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift index 84403f64d..64f9aa039 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift @@ -1,4 +1,5 @@ import _SwiftFormatTestSupport +import SwiftFormat final class CommentTests: PrettyPrintTestCase { func testDocumentationComments() { @@ -199,6 +200,152 @@ final class CommentTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) } + func testLineCommentsWithCustomLeadingSpaces() { + let pairs: [(String, String)] = [ + ( + """ + // Line Comment0 + + // Line Comment1 + // Line Comment2 + let a = 123 + let b = "456" // End of line comment + let c = "More content" + + """, + """ + // Line Comment0 + + // Line Comment1 + // Line Comment2 + let a = 123 + let b = "456" // End of line comment + let c = "More content" + + """ + ), + ( + """ + // Comment 3 + // Comment 4 + + let reallyLongVariableName = 123 // This comment should not wrap + // and should not combine with this comment + + func MyFun() { + // just a comment + } + """, + """ + // Comment 3 + // Comment 4 + + let reallyLongVariableName = 123 // This comment should not wrap + // and should not combine with this comment + + func MyFun() { + // just a comment + } + + """ + ), + ( + """ + func MyFun() { + // Comment 1 + // Comment 2 + let a = 123 + + let b = 456 // Comment 3 + } + + func MyFun() { + let c = 789 // Comment 4 + // Comment 5 + } + """, + """ + func MyFun() { + // Comment 1 + // Comment 2 + let a = 123 + + let b = 456 // Comment 3 + } + + func MyFun() { + let c = 789 // Comment 4 + // Comment 5 + } + + """ + ), + ( + """ + let a = myfun(123 // Cmt 7 + ) + let a = myfun(var1: 123 // Cmt 7 + ) + + guard condition else { return // Cmt 6 + } + + switch myvar { + case .one, .two, // three + .four: + dostuff() + default: () + } + + """, + """ + let a = myfun( + 123 // Cmt 7 + ) + let a = myfun( + var1: 123 // Cmt 7 + ) + + guard condition else { + return // Cmt 6 + } + + switch myvar { + case .one, .two, // three + .four: + dostuff() + default: () + } + + """ + ), + ( + """ + let a = 123 + // comment + b + c + + let d = 123 + // Trailing Comment + """, + """ + let a = + 123 // comment + + b + c + + let d = 123 + // Trailing Comment + + """ + ), + ] + + var config = Configuration.forTesting + config.spacesBeforeEndOfLineComments = 3 + for (input, expected) in pairs { + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45, configuration: config) + } + } + func testContainerLineComments() { let input = """ @@ -274,6 +421,82 @@ final class CommentTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) } + func testContainerLineCommentsWithCustomLeadingSpaces() { + let input = + """ + // Array comment + let a = [456, // small comment + 789] + + // Dictionary comment + let b = ["abc": 456, // small comment + "def": 789] + + // Trailing comment + let c = [123, 456 // small comment + ] + + // Multiline comment + let d = [123, + // comment line 1 + // comment line 2 + 456 + ] + + /* Array comment */ + let a = [456, /* small comment */ + 789] + + /* Dictionary comment */ + let b = ["abc": 456, /* small comment */ + "def": 789] + """ + + let expected = + """ + // Array comment + let a = [ + 456, // small comment + 789, + ] + + // Dictionary comment + let b = [ + "abc": 456, // small comment + "def": 789, + ] + + // Trailing comment + let c = [ + 123, 456, // small comment + ] + + // Multiline comment + let d = [ + 123, + // comment line 1 + // comment line 2 + 456, + ] + + /* Array comment */ + let a = [ + 456, /* small comment */ + 789, + ] + + /* Dictionary comment */ + let b = [ + "abc": 456, /* small comment */ + "def": 789, + ] + + """ + var config = Configuration.forTesting + config.spacesBeforeEndOfLineComments = 1 + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } + func testDocumentationBlockComments() { let input = """ From ba2a1927a519c848ef0254d1de88c2558132670f Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Tue, 23 Jul 2024 17:39:48 -0700 Subject: [PATCH 229/332] Update Sources/SwiftFormat/API/Configuration.swift Co-authored-by: Ben Barham --- Sources/SwiftFormat/API/Configuration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index d1259bacf..7669a1ed6 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -67,7 +67,7 @@ public struct Configuration: Codable, Equatable { /// The maximum length of a line of source code, after which the formatter will break lines. public var lineLength: Int - // Number of spaces that preceeds line comments. + /// Number of spaces that precede line comments. public var spacesBeforeEndOfLineComments: Int /// The width of the horizontal tab in spaces. From bb7524de9614d1ea7260653b0952d6fe4d6a6aa7 Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Thu, 25 Jul 2024 13:09:07 -0400 Subject: [PATCH 230/332] Fix missing break in nested IfConfig decls. Fixes #779. --- .../PrettyPrint/TokenStreamCreator.swift | 2 ++ .../PrettyPrint/IfConfigTests.swift | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 8e7dc617b..6cf77877a 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -1453,6 +1453,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: IfConfigDeclSyntax) -> SyntaxVisitorContinueKind { + // there has to be a break after an #endif + after(node.poundEndif, tokens: .break(.same, size: 0)) return .visitChildren } diff --git a/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift index 5a6113529..4f4a85b3b 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift @@ -531,4 +531,30 @@ final class IfConfigTests: PrettyPrintTestCase { """ assertPrettyPrintEqual(input: input, expected: input, linelength: 45) } + + func testNestedPoundIfInSwitchStatement() { + let input = + """ + switch self { + #if os(iOS) || os(tvOS) || os(watchOS) + case .a: + return 40 + #if os(iOS) || os(tvOS) + case .e: + return 30 + #endif + #if os(iOS) + case .g: + return 2 + #endif + #endif + default: + return nil + } + + """ + var configuration = Configuration.forTesting + configuration.indentConditionalCompilationBlocks = false + assertPrettyPrintEqual(input: input, expected: input, linelength: 45, configuration: configuration) + } } From 9d1f88c03e45c3493b6c3ae0328198f682517009 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sun, 28 Jul 2024 13:27:33 -0700 Subject: [PATCH 231/332] Ignore CMakeLists.txt in SwiftPM Suppress some warnings from SwiftPM by excluding CMakeLists.txt from targets. Closes #716 --- Package.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index e1f123b36..18c697e77 100644 --- a/Package.swift +++ b/Package.swift @@ -41,7 +41,8 @@ let package = Package( dependencies: dependencies, targets: [ .target( - name: "_SwiftFormatInstructionCounter" + name: "_SwiftFormatInstructionCounter", + exclude: ["CMakeLists.txt"] ), .target( @@ -53,7 +54,8 @@ let package = Package( .product(name: "SwiftOperators", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), - ] + ], + exclude: ["CMakeLists.txt"] ), .target( name: "_SwiftFormatTestSupport", @@ -105,6 +107,7 @@ let package = Package( .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), ], + exclude: ["CMakeLists.txt"], linkerSettings: swiftformatLinkSettings ), From 3155cb5b9c854e4f62c7ca19ed2476a0379df8c7 Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Thu, 1 Aug 2024 11:35:57 -0400 Subject: [PATCH 232/332] Upgrade the DontRepeatTypeInStaticProperties rule. - Using the visit method to examine all the variable declarations should allow specific decls to be ignored. - Modify the PipelineGenerator to produce output matching the current generated files. --- Documentation/RuleDocumentation.md | 2 +- .../Core/Pipelines+Generated.swift | 12 +- .../Rules/AlwaysUseLowerCamelCase.swift | 1 + .../DontRepeatTypeInStaticProperties.swift | 112 +++++++++--------- .../SwiftFormat/Rules/NeverForceUnwrap.swift | 1 + .../SwiftFormat/Rules/NeverUseForceTry.swift | 1 + ...NeverUseImplicitlyUnwrappedOptionals.swift | 1 + .../PipelineGenerator.swift | 1 + ...ontRepeatTypeInStaticPropertiesTests.swift | 4 + 9 files changed, 66 insertions(+), 69 deletions(-) diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index 7b9a950b9..05cdeda22 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -4,7 +4,7 @@ Use the rules below in the `rules` block of your `.swift-format` configuration file, as described in -[Configuration](Configuration.md). All of these rules can be +[Configuration](Documentation/Configuration.md). All of these rules can be applied in the linter, but only some of them can format your source code automatically. diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index c0af02e39..286c1f5c8 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -76,7 +76,6 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) - visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) @@ -86,7 +85,6 @@ class LintPipeline: SyntaxVisitor { onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) - onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) onVisitPost(rule: NoLeadingUnderscores.self, for: node) onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) @@ -182,7 +180,6 @@ class LintPipeline: SyntaxVisitor { override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) - visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) visitIfEnabled(FullyIndirectEnum.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) visitIfEnabled(OneCasePerLine.visit, for: node) @@ -192,7 +189,6 @@ class LintPipeline: SyntaxVisitor { } override func visitPost(_ node: EnumDeclSyntax) { onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) - onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) onVisitPost(rule: FullyIndirectEnum.self, for: node) onVisitPost(rule: NoLeadingUnderscores.self, for: node) onVisitPost(rule: OneCasePerLine.self, for: node) @@ -202,14 +198,12 @@ class LintPipeline: SyntaxVisitor { override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AvoidRetroactiveConformances.visit, for: node) - visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) visitIfEnabled(NoAccessLevelOnExtensionDeclaration.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } override func visitPost(_ node: ExtensionDeclSyntax) { onVisitPost(rule: AvoidRetroactiveConformances.self, for: node) - onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) onVisitPost(rule: NoAccessLevelOnExtensionDeclaration.self, for: node) onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) } @@ -423,7 +417,6 @@ class LintPipeline: SyntaxVisitor { override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) - visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) @@ -432,7 +425,6 @@ class LintPipeline: SyntaxVisitor { override func visitPost(_ node: ProtocolDeclSyntax) { onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) - onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) onVisitPost(rule: NoLeadingUnderscores.self, for: node) onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) @@ -469,7 +461,6 @@ class LintPipeline: SyntaxVisitor { override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) - visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) visitIfEnabled(UseSynthesizedInitializer.visit, for: node) @@ -479,7 +470,6 @@ class LintPipeline: SyntaxVisitor { override func visitPost(_ node: StructDeclSyntax) { onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) - onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) onVisitPost(rule: NoLeadingUnderscores.self, for: node) onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) onVisitPost(rule: UseSynthesizedInitializer.self, for: node) @@ -568,6 +558,7 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) visitIfEnabled(NeverUseImplicitlyUnwrappedOptionals.visit, for: node) visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren @@ -576,6 +567,7 @@ class LintPipeline: SyntaxVisitor { onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) onVisitPost(rule: NeverUseImplicitlyUnwrappedOptionals.self, for: node) onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) } diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift index 368d8d69d..aac9deadb 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift @@ -17,6 +17,7 @@ import SwiftSyntax /// /// This rule does not apply to test code, defined as code which: /// * Contains the line `import XCTest` +/// * The function is marked with `@Test` attribute /// /// Lint: If an identifier contains underscores or begins with a capital letter, a lint error is /// raised. diff --git a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift index 0529b35a2..3bf853d2f 100644 --- a/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift +++ b/Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift @@ -22,69 +22,28 @@ import SwiftSyntax @_spi(Rules) public final class DontRepeatTypeInStaticProperties: SyntaxLintRule { - public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text) - return .skipChildren - } - - public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text) - return .skipChildren - } - - public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text) - return .skipChildren - } - - public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text) - return .skipChildren - } - - public override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { - let members = node.memberBlock.members - - switch Syntax(node.extendedType).as(SyntaxEnum.self) { - case .identifierType(let simpleType): - diagnoseStaticMembers(members, endingWith: simpleType.name.text) - case .memberType(let memberType): - // We don't need to drill recursively into this structure because types with more than two - // components are constructed left-heavy; that is, `A.B.C.D` is structured as `((A.B).C).D`, - // and the final component of the top type is what we want. - diagnoseStaticMembers(members, endingWith: memberType.name.text) - default: - // Do nothing for non-nominal types. If Swift adds support for extensions on non-nominals, - // we'll need to update this if we need to support some subset of those. - break + /// Visits the static/class properties and diagnoses any where the name has the containing + /// type name (excluding possible namespace prefixes, like `NS` or `UI`) as a suffix. + public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + guard node.modifiers.contains(anyOf: [.class, .static]), + let typeName = Syntax(node).containingDeclName + else { + return .visitChildren } - return .skipChildren - } - - /// Iterates over the static/class properties in the given member list and diagnoses any where the - /// name has the containing type name (excluding possible namespace prefixes, like `NS` or `UI`) - /// as a suffix. - private func diagnoseStaticMembers(_ members: MemberBlockItemListSyntax, endingWith typeName: String) { - for member in members { - guard - let varDecl = member.decl.as(VariableDeclSyntax.self), - varDecl.modifiers.contains(anyOf: [.class, .static]) - else { continue } - - let bareTypeName = removingPossibleNamespacePrefix(from: typeName) - - for binding in varDecl.bindings { - guard let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self) else { - continue - } + let bareTypeName = removingPossibleNamespacePrefix(from: typeName) + for binding in node.bindings { + guard let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self) else { + continue + } - let varName = identifierPattern.identifier.text - if varName.contains(bareTypeName) { - diagnose(.removeTypeFromName(name: varName, type: bareTypeName), on: identifierPattern) - } + let varName = identifierPattern.identifier.text + if varName.contains(bareTypeName) { + diagnose(.removeTypeFromName(name: varName, type: bareTypeName), on: identifierPattern) } } + + return .visitChildren } /// Returns the portion of the given string that excludes a possible Objective-C-style capitalized @@ -110,3 +69,40 @@ extension Finding.Message { "remove the suffix '\(type)' from the name of the variable '\(name)'" } } + +extension Syntax { + /// Returns the name of the immediately enclosing type of this decl if there is one, + /// otherwise nil. + fileprivate var containingDeclName: String? { + switch Syntax(self).as(SyntaxEnum.self) { + case .actorDecl(let node): + return node.name.text + case .classDecl(let node): + return node.name.text + case .enumDecl(let node): + return node.name.text + case .protocolDecl(let node): + return node.name.text + case .structDecl(let node): + return node.name.text + case .extensionDecl(let node): + switch Syntax(node.extendedType).as(SyntaxEnum.self) { + case .identifierType(let simpleType): + return simpleType.name.text + case .memberType(let memberType): + // the final component of the top type `A.B.C.D` is what we want `D`. + return memberType.name.text + default: + // Do nothing for non-nominal types. If Swift adds support for extensions on non-nominals, + // we'll need to update this if we need to support some subset of those. + return nil + } + default: + if let parent = self.parent { + return parent.containingDeclName + } + + return nil + } + } +} diff --git a/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift index 05a7b935c..f81c9953d 100644 --- a/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift +++ b/Sources/SwiftFormat/Rules/NeverForceUnwrap.swift @@ -16,6 +16,7 @@ import SwiftSyntax /// /// This rule does not apply to test code, defined as code which: /// * Contains the line `import XCTest` +/// * The function is marked with `@Test` attribute /// /// Lint: If a force unwrap is used, a lint warning is raised. @_spi(Rules) diff --git a/Sources/SwiftFormat/Rules/NeverUseForceTry.swift b/Sources/SwiftFormat/Rules/NeverUseForceTry.swift index 2be281cd1..2eada1a78 100644 --- a/Sources/SwiftFormat/Rules/NeverUseForceTry.swift +++ b/Sources/SwiftFormat/Rules/NeverUseForceTry.swift @@ -16,6 +16,7 @@ import SwiftSyntax /// /// This rule does not apply to test code, defined as code which: /// * Contains the line `import XCTest` +/// * The function is marked with `@Test` attribute /// /// Lint: Using `try!` results in a lint error. /// diff --git a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift index ff5fad1b5..7fc8ad341 100644 --- a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -18,6 +18,7 @@ import SwiftSyntax /// /// This rule does not apply to test code, defined as code which: /// * Contains the line `import XCTest` +/// * The function is marked with `@Test` attribute /// /// TODO: Create exceptions for other UI elements (ex: viewDidLoad) /// diff --git a/Sources/generate-swift-format/PipelineGenerator.swift b/Sources/generate-swift-format/PipelineGenerator.swift index e2c2c8d08..1f9b20770 100644 --- a/Sources/generate-swift-format/PipelineGenerator.swift +++ b/Sources/generate-swift-format/PipelineGenerator.swift @@ -93,6 +93,7 @@ final class PipelineGenerator: FileGenerator { handle.write( """ override func visitPost(_ node: \(nodeType)) { + """ ) for ruleName in lintRules.sorted() { diff --git a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift index af85092cf..3d961d2d6 100644 --- a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift +++ b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift @@ -32,6 +32,9 @@ final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase { extension URLSession { class var 8️⃣sharedSession: URLSession } + public actor Cookie { + static let 9️⃣chocolateChipCookie: Cookie + } """, findings: [ FindingSpec("1️⃣", message: "remove the suffix 'Color' from the name of the variable 'redColor'"), @@ -42,6 +45,7 @@ final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase { FindingSpec("6️⃣", message: "remove the suffix 'Game' from the name of the variable 'basketballGame'"), FindingSpec("7️⃣", message: "remove the suffix 'Game' from the name of the variable 'baseballGame'"), FindingSpec("8️⃣", message: "remove the suffix 'Session' from the name of the variable 'sharedSession'"), + FindingSpec("9️⃣", message: "remove the suffix 'Cookie' from the name of the variable 'chocolateChipCookie'"), ] ) } From 992d2c685f0213b310f4208e9eeebb3c16d21c03 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Thu, 1 Aug 2024 16:50:25 -0700 Subject: [PATCH 233/332] Add option to add a newline between 2 adjacent attributes (#784) Add an option that inserts hard line breaks between adjacent attributes. --- Documentation/Configuration.md | 7 ++ .../API/Configuration+Default.swift | 1 + Sources/SwiftFormat/API/Configuration.swift | 8 ++ .../PrettyPrint/TokenStreamCreator.swift | 42 ++++--- .../PrettyPrint/AttributeTests.swift | 108 ++++++++++++++++++ 5 files changed, 153 insertions(+), 13 deletions(-) diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 84d7ae9a7..d31f98a02 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -61,6 +61,13 @@ top-level keys and values: (the default), requirements will be laid out horizontally first, with line breaks only being fired when the line length would be exceeded. +* `lineBreakBetweenDeclarationAttributes` _(boolean)_: Determines the + line-breaking behavior for adjacent attributes on declarations. + If true, a line break will be added between each attribute, forcing the + attribute list to be laid out vertically. If false (the default), + attributes will be laid out horizontally first, with line breaks only + being fired when the line length would be exceeded. + * `prioritizeKeepingFunctionOutputTogether` _(boolean)_: Determines if function-like declaration outputs should be prioritized to be together with the function signature right (closing) parenthesis. If false (the default), function diff --git a/Sources/SwiftFormat/API/Configuration+Default.swift b/Sources/SwiftFormat/API/Configuration+Default.swift index 52a91873e..ca57700bb 100644 --- a/Sources/SwiftFormat/API/Configuration+Default.swift +++ b/Sources/SwiftFormat/API/Configuration+Default.swift @@ -31,6 +31,7 @@ extension Configuration { self.lineBreakBeforeControlFlowKeywords = false self.lineBreakBeforeEachArgument = false self.lineBreakBeforeEachGenericRequirement = false + self.lineBreakBetweenDeclarationAttributes = false self.prioritizeKeepingFunctionOutputTogether = false self.indentConditionalCompilationBlocks = true self.lineBreakAroundMultilineExpressionChainComponents = false diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 7669a1ed6..19af3de57 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -35,6 +35,7 @@ public struct Configuration: Codable, Equatable { case lineBreakBeforeControlFlowKeywords case lineBreakBeforeEachArgument case lineBreakBeforeEachGenericRequirement + case lineBreakBetweenDeclarationAttributes case prioritizeKeepingFunctionOutputTogether case indentConditionalCompilationBlocks case lineBreakAroundMultilineExpressionChainComponents @@ -115,6 +116,9 @@ public struct Configuration: Codable, Equatable { /// horizontally first, with line breaks only being fired when the line length would be exceeded. public var lineBreakBeforeEachGenericRequirement: Bool + /// If true, a line break will be added between adjacent attributes. + public var lineBreakBetweenDeclarationAttributes: Bool + /// Determines if function-like declaration outputs should be prioritized to be together with the /// function signature right (closing) parenthesis. /// @@ -250,6 +254,9 @@ public struct Configuration: Codable, Equatable { self.lineBreakBeforeEachGenericRequirement = try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachGenericRequirement) ?? defaults.lineBreakBeforeEachGenericRequirement + self.lineBreakBetweenDeclarationAttributes = + try container.decodeIfPresent(Bool.self, forKey: .lineBreakBetweenDeclarationAttributes) + ?? defaults.lineBreakBetweenDeclarationAttributes self.prioritizeKeepingFunctionOutputTogether = try container.decodeIfPresent(Bool.self, forKey: .prioritizeKeepingFunctionOutputTogether) ?? defaults.prioritizeKeepingFunctionOutputTogether @@ -304,6 +311,7 @@ public struct Configuration: Codable, Equatable { try container.encode(lineBreakBeforeEachGenericRequirement, forKey: .lineBreakBeforeEachGenericRequirement) try container.encode(prioritizeKeepingFunctionOutputTogether, forKey: .prioritizeKeepingFunctionOutputTogether) try container.encode(indentConditionalCompilationBlocks, forKey: .indentConditionalCompilationBlocks) + try container.encode(lineBreakBetweenDeclarationAttributes, forKey: .lineBreakBetweenDeclarationAttributes) try container.encode( lineBreakAroundMultilineExpressionChainComponents, forKey: .lineBreakAroundMultilineExpressionChainComponents) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 83d410e50..7859ee1ff 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -271,7 +271,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // `arrange*` functions here. before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) - arrangeAttributeList(node.attributes) + arrangeAttributeList(node.attributes, separateByLineBreaks: config.lineBreakBeforeEachArgument) let hasArguments = !node.signature.parameterClause.parameters.isEmpty @@ -326,7 +326,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) - arrangeAttributeList(attributes) + arrangeAttributeList(attributes, separateByLineBreaks: config.lineBreakBetweenDeclarationAttributes) // Prioritize keeping " :" together (corresponding group close is // below at `lastTokenBeforeBrace`). @@ -458,7 +458,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.returnClause.lastToken(viewMode: .sourceAccurate), tokens: .close) } - arrangeAttributeList(node.attributes) + arrangeAttributeList(node.attributes, separateByLineBreaks: config.lineBreakBetweenDeclarationAttributes) if let genericWhereClause = node.genericWhereClause { before(genericWhereClause.firstToken(viewMode: .sourceAccurate), tokens: .break(.same), .open) @@ -513,7 +513,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) where BodyContents.Element: SyntaxProtocol { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) - arrangeAttributeList(attributes) + arrangeAttributeList(attributes, separateByLineBreaks: config.lineBreakBetweenDeclarationAttributes) arrangeBracesAndContents(of: body, contentsKeyPath: bodyContentsKeyPath) if let genericWhereClause = genericWhereClause { @@ -549,7 +549,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: AccessorDeclSyntax) -> SyntaxVisitorContinueKind { - arrangeAttributeList(node.attributes) + arrangeAttributeList(node.attributes, separateByLineBreaks: config.lineBreakBetweenDeclarationAttributes) arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements) return .visitChildren } @@ -1327,7 +1327,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: MacroExpansionDeclSyntax) -> SyntaxVisitorContinueKind { - arrangeAttributeList(node.attributes) + arrangeAttributeList(node.attributes, separateByLineBreaks: config.lineBreakBetweenDeclarationAttributes) before( node.trailingClosure?.leftBrace, @@ -1546,7 +1546,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: EnumCaseDeclSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) - arrangeAttributeList(node.attributes) + arrangeAttributeList(node.attributes, separateByLineBreaks: config.lineBreakBetweenDeclarationAttributes) after(node.caseKeyword, tokens: .break) after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) @@ -2179,7 +2179,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { - arrangeAttributeList(node.attributes) + arrangeAttributeList(node.attributes, separateByLineBreaks: config.lineBreakBetweenDeclarationAttributes) if node.bindings.count == 1 { // If there is only a single binding, don't allow a break between the `let/var` keyword and @@ -2285,7 +2285,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { - arrangeAttributeList(node.attributes) + arrangeAttributeList(node.attributes, separateByLineBreaks: config.lineBreakBetweenDeclarationAttributes) after(node.typealiasKeyword, tokens: .break) @@ -2499,7 +2499,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind { - arrangeAttributeList(node.attributes) + arrangeAttributeList(node.attributes, separateByLineBreaks: config.lineBreakBetweenDeclarationAttributes) after(node.associatedtypeKeyword, tokens: .break) @@ -2890,14 +2890,30 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// Applies formatting tokens around and between the attributes in an attribute list. private func arrangeAttributeList( _ attributes: AttributeListSyntax?, - suppressFinalBreak: Bool = false + suppressFinalBreak: Bool = false, + separateByLineBreaks: Bool = false ) { if let attributes = attributes { + let behavior: NewlineBehavior = separateByLineBreaks ? .hard : .elective before(attributes.firstToken(viewMode: .sourceAccurate), tokens: .open) - insertTokens(.break(.same), betweenElementsOf: attributes) + for element in attributes.dropLast() { + if let ifConfig = element.as(IfConfigDeclSyntax.self) { + for clause in ifConfig.clauses { + if let nestedAttributes = AttributeListSyntax(clause.elements) { + arrangeAttributeList( + nestedAttributes, + suppressFinalBreak: true, + separateByLineBreaks: separateByLineBreaks + ) + } + } + } else { + after(element.lastToken(viewMode: .sourceAccurate), tokens: .break(.same, newlines: behavior)) + } + } var afterAttributeTokens = [Token.close] if !suppressFinalBreak { - afterAttributeTokens.append(.break(.same)) + afterAttributeTokens.append(.break(.same, newlines: behavior)) } after(attributes.lastToken(viewMode: .sourceAccurate), tokens: afterAttributeTokens) } diff --git a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift index 3ff5db02d..c16950390 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift @@ -468,4 +468,112 @@ final class AttributeTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 100) } + + func testLineBreakBetweenDeclarationAttributes() { + let input = + """ + @_spi(Private) @_spi(InviteOnly) import SwiftFormat + + @available(iOS 14.0, *) @available(macOS 11.0, *) + public protocol P { + @available(iOS 16.0, *) @available(macOS 14.0, *) + #if DEBUG + @available(tvOS 17.0, *) @available(watchOS 10.0, *) + #endif + @available(visionOS 1.0, *) + associatedtype ID + } + + @available(iOS 14.0, *) @available(macOS 11.0, *) + public enum Dimension { + case x + case y + @available(iOS 17.0, *) @available(visionOS 1.0, *) + case z + } + + @available(iOS 16.0, *) @available(macOS 14.0, *) + @available(tvOS 16.0, *) @frozen + struct X { + @available(iOS 17.0, *) @available(macOS 15.0, *) + typealias ID = UUID + + @available(iOS 17.0, *) @available(macOS 15.0, *) + var callMe: @MainActor @Sendable () -> Void + + @available(iOS 17.0, *) @available(macOS 15.0, *) + @MainActor @discardableResult + func f(@_inheritActorContext body: @MainActor @Sendable () -> Void) {} + + @available(iOS 17.0, *) @available(macOS 15.0, *) @MainActor + var foo: Foo { + get { Foo() } + @available(iOS, obsoleted: 17.0) @available(macOS 15.0, obsoleted: 15.0) + set { fatalError() } + } + } + """ + + let expected = + """ + @_spi(Private) @_spi(InviteOnly) import SwiftFormat + + @available(iOS 14.0, *) + @available(macOS 11.0, *) + public protocol P { + @available(iOS 16.0, *) + @available(macOS 14.0, *) + #if DEBUG + @available(tvOS 17.0, *) + @available(watchOS 10.0, *) + #endif + @available(visionOS 1.0, *) + associatedtype ID + } + + @available(iOS 14.0, *) + @available(macOS 11.0, *) + public enum Dimension { + case x + case y + @available(iOS 17.0, *) + @available(visionOS 1.0, *) + case z + } + + @available(iOS 16.0, *) + @available(macOS 14.0, *) + @available(tvOS 16.0, *) + @frozen + struct X { + @available(iOS 17.0, *) + @available(macOS 15.0, *) + typealias ID = UUID + + @available(iOS 17.0, *) + @available(macOS 15.0, *) + var callMe: @MainActor @Sendable () -> Void + + @available(iOS 17.0, *) + @available(macOS 15.0, *) + @MainActor + @discardableResult + func f(@_inheritActorContext body: @MainActor @Sendable () -> Void) {} + + @available(iOS 17.0, *) + @available(macOS 15.0, *) + @MainActor + var foo: Foo { + get { Foo() } + @available(iOS, obsoleted: 17.0) + @available(macOS 15.0, obsoleted: 15.0) + set { fatalError() } + } + } + + """ + var configuration = Configuration.forTesting + configuration.lineBreakBetweenDeclarationAttributes = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: configuration) + } } From fc7042bbfc9f5d1ef5a08158f1ee9174425946bd Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Wed, 24 Jul 2024 15:35:52 -0400 Subject: [PATCH 234/332] Make rules work even inside of nested data structures. Fixes #778. --- .../Core/Pipelines+Generated.swift | 4 + ...lPublicDeclarationsHaveDocumentation.swift | 14 +- ...cumentationCommentWithOneLineSummary.swift | 6 +- .../Rules/UseSynthesizedInitializer.swift | 2 +- ...icDeclarationsHaveDocumentationTests.swift | 172 +++++++++++++++++- ...tationCommentWithOneLineSummaryTests.swift | 65 +++++++ .../UseSynthesizedInitializerTests.swift | 20 ++ 7 files changed, 269 insertions(+), 14 deletions(-) diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 286c1f5c8..4e524c56b 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -37,10 +37,12 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) return .visitChildren } override func visitPost(_ node: ActorDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) } @@ -179,6 +181,7 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) visitIfEnabled(FullyIndirectEnum.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) @@ -188,6 +191,7 @@ class LintPipeline: SyntaxVisitor { return .visitChildren } override func visitPost(_ node: EnumDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) onVisitPost(rule: FullyIndirectEnum.self, for: node) onVisitPost(rule: NoLeadingUnderscores.self, for: node) diff --git a/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift index 75d619539..feaa3a550 100644 --- a/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift +++ b/Sources/SwiftFormat/Rules/AllPublicDeclarationsHaveDocumentation.swift @@ -46,7 +46,7 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseMissingDocComment(DeclSyntax(node), name: node.name.text, modifiers: node.modifiers) - return .skipChildren + return .visitChildren } public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { @@ -57,7 +57,17 @@ public final class AllPublicDeclarationsHaveDocumentation: SyntaxLintRule { public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseMissingDocComment(DeclSyntax(node), name: node.name.text, modifiers: node.modifiers) - return .skipChildren + return .visitChildren + } + + public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { + diagnoseMissingDocComment(DeclSyntax(node), name: node.name.text, modifiers: node.modifiers) + return .visitChildren + } + + public override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { + diagnoseMissingDocComment(DeclSyntax(node), name: node.name.text, modifiers: node.modifiers) + return .visitChildren } public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { diff --git a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift index 2195bd796..92fefce8a 100644 --- a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift +++ b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift @@ -40,7 +40,7 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseDocComments(in: DeclSyntax(node)) - return .skipChildren + return .visitChildren } public override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { @@ -60,7 +60,7 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseDocComments(in: DeclSyntax(node)) - return .skipChildren + return .visitChildren } public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { @@ -70,7 +70,7 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseDocComments(in: DeclSyntax(node)) - return .skipChildren + return .visitChildren } public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { diff --git a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift index e3d4e2444..43ba2d67d 100644 --- a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift @@ -72,7 +72,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { extraneousInitializers.forEach { diagnose(.removeRedundantInitializer, on: $0) } } - return .skipChildren + return .visitChildren } /// Compares the actual access level of an initializer with the access level of a synthesized diff --git a/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift b/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift index b32fdb5ff..81965c38c 100644 --- a/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift +++ b/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift @@ -7,16 +7,40 @@ final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCas assertLint( AllPublicDeclarationsHaveDocumentation.self, """ - 1️⃣public func lightswitchRave() { - } + 1️⃣public func lightswitchRave() {} + /// Comment. + public func lightswitchRave() {} + func lightswitchRave() {} - 2️⃣public var isSblounskched: Int { - return 0 - } + 2️⃣public var isSblounskched: Int { return 0 } + /// Comment. + public var isSblounskched: Int { return 0 } + var isSblounskched: Int { return 0 } - /// Everybody to the limit. - public func fhqwhgads() { - } + 3️⃣public struct Foo {} + /// Comment. + public struct Foo {} + struct Foo {} + + 4️⃣public actor Bar {} + /// Comment. + public actor Bar {} + actor Bar {} + + 5️⃣public class Baz {} + /// Comment. + public class Baz {} + class Baz {} + + 6️⃣public enum Qux {} + /// Comment. + public enum Qux {} + enum Qux {} + + 7️⃣public typealias MyType = Int + /// Comment. + public typealias MyType = Int + typealias MyType = Int /** * Determines if an email was delorted. @@ -28,6 +52,138 @@ final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCas findings: [ FindingSpec("1️⃣", message: "add a documentation comment for 'lightswitchRave()'"), FindingSpec("2️⃣", message: "add a documentation comment for 'isSblounskched'"), + FindingSpec("3️⃣", message: "add a documentation comment for 'Foo'"), + FindingSpec("4️⃣", message: "add a documentation comment for 'Bar'"), + FindingSpec("5️⃣", message: "add a documentation comment for 'Baz'"), + FindingSpec("6️⃣", message: "add a documentation comment for 'Qux'"), + FindingSpec("7️⃣", message: "add a documentation comment for 'MyType'") + ] + ) + } + + func testNestedDecls() { + assertLint( + AllPublicDeclarationsHaveDocumentation.self, + """ + /// Comment. + public struct MyContainer { + 1️⃣public func lightswitchRave() {} + /// Comment. + public func lightswitchRave() {} + func lightswitchRave() {} + + 2️⃣public var isSblounskched: Int { return 0 } + /// Comment. + public var isSblounskched: Int { return 0 } + var isSblounskched: Int { return 0 } + + 3️⃣public struct Foo {} + /// Comment. + public struct Foo {} + struct Foo {} + + 4️⃣public actor Bar {} + /// Comment. + public actor Bar {} + actor Bar {} + + 5️⃣public class Baz {} + /// Comment. + public class Baz {} + class Baz {} + + 6️⃣public enum Qux {} + /// Comment. + public enum Qux {} + enum Qux {} + + 7️⃣public typealias MyType = Int + /// Comment. + public typealias MyType = Int + typealias MyType = Int + + } + """, + findings: [ + FindingSpec("1️⃣", message: "add a documentation comment for 'lightswitchRave()'"), + FindingSpec("2️⃣", message: "add a documentation comment for 'isSblounskched'"), + FindingSpec("3️⃣", message: "add a documentation comment for 'Foo'"), + FindingSpec("4️⃣", message: "add a documentation comment for 'Bar'"), + FindingSpec("5️⃣", message: "add a documentation comment for 'Baz'"), + FindingSpec("6️⃣", message: "add a documentation comment for 'Qux'"), + FindingSpec("7️⃣", message: "add a documentation comment for 'MyType'") + ] + ) + } + + func testNestedInStruct() { + assertLint( + AllPublicDeclarationsHaveDocumentation.self, + """ + /// Comment. + public struct MyContainer { + 1️⃣public typealias MyType = Int + /// Comment. + public typealias MyType = Int + typealias MyType = Int + } + """, + findings: [ + FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'"), + ] + ) + } + + func testNestedInClass() { + assertLint( + AllPublicDeclarationsHaveDocumentation.self, + """ + /// Comment. + public class MyContainer { + 1️⃣public typealias MyType = Int + /// Comment. + public typealias MyType = Int + typealias MyType = Int + } + """, + findings: [ + FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'"), + ] + ) + } + + func testNestedInEnum() { + assertLint( + AllPublicDeclarationsHaveDocumentation.self, + """ + /// Comment. + public enum MyContainer { + 1️⃣public typealias MyType = Int + /// Comment. + public typealias MyType = Int + typealias MyType = Int + } + """, + findings: [ + FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'"), + ] + ) + } + + func testNestedInActor() { + assertLint( + AllPublicDeclarationsHaveDocumentation.self, + """ + /// Comment. + public actor MyContainer { + 1️⃣public typealias MyType = Int + /// Comment. + public typealias MyType = Int + typealias MyType = Int + } + """, + findings: [ + FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'"), ] ) } diff --git a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift index cfeaa09dd..6c96f6d8a 100644 --- a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift +++ b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift @@ -169,4 +169,69 @@ final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTe """ ) } + + func testNestedInsideStruct() { + assertLint( + BeginDocumentationCommentWithOneLineSummary.self, + """ + struct MyContainer { + /// This docline should not succeed. + /// There are two sentences without a blank line between them. + 1️⃣struct Test {} + } + """, + findings: [ + FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#) + ] + ) + } + + func testNestedInsideEnum() { + assertLint( + BeginDocumentationCommentWithOneLineSummary.self, + """ + enum MyContainer { + /// This docline should not succeed. + /// There are two sentences without a blank line between them. + 1️⃣struct Test {} + } + """, + findings: [ + FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#) + ] + ) + } + + func testNestedInsideClass() { + assertLint( + BeginDocumentationCommentWithOneLineSummary.self, + """ + class MyContainer { + /// This docline should not succeed. + /// There are two sentences without a blank line between them. + 1️⃣struct Test {} + } + """, + findings: [ + FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#) + ] + ) + } + + func testNestedInsideActor() { + assertLint( + BeginDocumentationCommentWithOneLineSummary.self, + """ + actor MyContainer { + /// This docline should not succeed. + /// There are two sentences without a blank line between them. + 1️⃣struct Test {} + } + """, + findings: [ + FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#) + ] + ) + } + } diff --git a/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift b/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift index fa00858fe..a21c99ba8 100644 --- a/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift @@ -26,6 +26,26 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { ) } + func testNestedMemberwiseInitializerIsDiagnosed() { + assertLint( + UseSynthesizedInitializer.self, + """ + public struct MyContainer { + public struct Person { + public var name: String + + 1️⃣init(name: String) { + self.name = name + } + } + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + ] + ) + } + func testInternalMemberwiseInitializerIsDiagnosed() { assertLint( UseSynthesizedInitializer.self, From 01ac89455850a921ba9a0a9e957e5e86ce0f805c Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Thu, 25 Jul 2024 09:43:02 -0400 Subject: [PATCH 235/332] Change assertLint helper to run via the pipeline only. Standalone lint visitors don't check for swift-format-ignore markers, so any test cases that use those markers will return different results between standalone vs full pipeline. Since the app uses the pipeline only, there is no value in running the rule standalone. --- ...DontRepeatTypeInStaticPropertiesTests.swift | 18 ++++++++++++++++++ .../Rules/LintOrFormatRuleTestCase.swift | 12 +----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift index 3d961d2d6..801a89a8c 100644 --- a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift +++ b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift @@ -75,4 +75,22 @@ final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase { ] ) } + + + func testIgnoreSingleDecl() { + assertLint( + DontRepeatTypeInStaticProperties.self, + """ + struct Foo { + // swift-format-ignore: DontRepeatTypeInStaticProperties + static let defaultFoo: Int + static let 1️⃣alternateFoo: Int + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove the suffix 'Foo' from the name of the variable 'alternateFoo'"), + ] + ) + } + } diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index 814952e6d..953f1e254 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -42,16 +42,6 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { configuration: configuration, selection: .infinite, findingConsumer: { emittedFindings.append($0) }) - let linter = type.init(context: context) - linter.walk(sourceFileSyntax) - - assertFindings( - expected: findings, - markerLocations: markedText.markers, - emittedFindings: emittedFindings, - context: context, - file: file, - line: line) var emittedPipelineFindings = [Finding]() // Disable default rules, so only select rule runs in pipeline @@ -66,7 +56,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { operatorTable: OperatorTable.standardOperators, assumingFileURL: URL(string: file.description)!) - // Check that pipeline produces the same findings as the isolated linter rule + // Check that pipeline produces the expected findings assertFindings( expected: findings, markerLocations: markedText.markers, From a917604ebcf270ec04070feb79c2419cf728a199 Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Tue, 6 Aug 2024 18:13:18 +0200 Subject: [PATCH 236/332] Remove closure capture equal sign spacing. --- Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 7859ee1ff..1e4629916 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -1271,8 +1271,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: ClosureCaptureSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) after(node.specifier?.lastToken(viewMode: .sourceAccurate), tokens: .break) - before(node.equal, tokens: .break) - after(node.equal, tokens: .break) if let trailingComma = node.trailingComma { before(trailingComma, tokens: .close) after(trailingComma, tokens: .break(.same)) From 29c39ebdf6f2d04f7c1cfd93324bb8c4cc1097fa Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Thu, 8 Aug 2024 14:19:28 -0400 Subject: [PATCH 237/332] Add support for re-indenting block comments. Re-indent all lines of a block comment with the current indentation level. The comment must not have anything other than whitespace preceding it on the line, and all lines must have at least that level of whitespace (or be empty). --- Sources/SwiftFormat/PrettyPrint/Comment.swift | 24 +++++++++++++- .../PrettyPrint/TokenStreamCreator.swift | 33 ++++++++++++++----- .../PrettyPrint/CommentTests.swift | 12 ++++--- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/Comment.swift b/Sources/SwiftFormat/PrettyPrint/Comment.swift index 60d63af10..3dff9480f 100644 --- a/Sources/SwiftFormat/PrettyPrint/Comment.swift +++ b/Sources/SwiftFormat/PrettyPrint/Comment.swift @@ -58,9 +58,12 @@ struct Comment { let kind: Kind var text: [String] var length: Int + // what was the leading indentation, if any, that preceded this comment? + var leadingIndent: Indent? - init(kind: Kind, text: String) { + init(kind: Kind, leadingIndent: Indent?, text: String) { self.kind = kind + self.leadingIndent = leadingIndent switch kind { case .line, .docLine: @@ -93,6 +96,25 @@ struct Comment { return kind.prefix + trimmedLines.joined(separator: separator) case .block, .docBlock: let separator = "\n" + + // if all the lines after the first matching leadingIndent, replace that prefix with the + // current indentation level + if let leadingIndent { + let rest = self.text.dropFirst() + + let hasLeading = rest.allSatisfy { + let result = $0.hasPrefix(leadingIndent.text) || $0.isEmpty + return result + } + if hasLeading, let first = self.text.first, !rest.isEmpty { + let restStr = rest.map { + let stripped = $0.dropFirst(leadingIndent.text.count) + return indent.indentation() + stripped + }.joined(separator: separator) + return kind.prefix + first + separator + restStr + "*/" + } + } + return kind.prefix + self.text.joined(separator: separator) + "*/" } } diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 1e4629916..e88f1dccb 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -3194,7 +3194,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { true, [ .space(size: config.spacesBeforeEndOfLineComments, flexible: true), - .comment(Comment(kind: .line, text: text), wasEndOfLine: true), + .comment(Comment(kind: .line, leadingIndent: nil, text: text), wasEndOfLine: true), // There must be a break with a soft newline after the comment, but it's impossible to // know which kind of break must be used. Adding this newline is deferred until the // comment is added to the token stream. @@ -3205,7 +3205,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { false, [ .space(size: 1, flexible: true), - .comment(Comment(kind: .block, text: text), wasEndOfLine: false), + .comment(Comment(kind: .block, leadingIndent: nil, text: text), wasEndOfLine: false), // We place a size-0 break after the comment to allow a discretionary newline after // the comment if the user places one here but the comment is otherwise adjacent to a // text token. @@ -3294,24 +3294,29 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // example, even if discretionary newlines are discarded). This is the case when the preceding // trivia was a line comment or garbage text. var requiresNextNewline = false + // Tracking whether or not the last piece was leading indentation. A newline is considered + // a 0-space indentation; used for nesting/un-nesting block comments during formatting. + var leadingIndent: Indent? = nil for (index, piece) in trivia.enumerated() { if let cutoff = cutoffIndex, index == cutoff { break } + switch piece { case .lineComment(let text): if index > 0 || isStartOfFile { generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength) - appendToken(.comment(Comment(kind: .line, text: text), wasEndOfLine: false)) + appendToken(.comment(Comment(kind: .line, leadingIndent: leadingIndent, text: text), wasEndOfLine: false)) generateDisableFormattingIfNecessary(position + piece.sourceLength) appendNewlines(.soft) isStartOfFile = false } requiresNextNewline = true + leadingIndent = nil case .blockComment(let text): if index > 0 || isStartOfFile { generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength) - appendToken(.comment(Comment(kind: .block, text: text), wasEndOfLine: false)) + appendToken(.comment(Comment(kind: .block, leadingIndent: leadingIndent, text: text), wasEndOfLine: false)) generateDisableFormattingIfNecessary(position + piece.sourceLength) // There is always a break after the comment to allow a discretionary newline after it. var breakSize = 0 @@ -3325,24 +3330,28 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { isStartOfFile = false } requiresNextNewline = false + leadingIndent = nil case .docLineComment(let text): generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength) - appendToken(.comment(Comment(kind: .docLine, text: text), wasEndOfLine: false)) + appendToken(.comment(Comment(kind: .docLine, leadingIndent: leadingIndent, text: text), wasEndOfLine: false)) generateDisableFormattingIfNecessary(position + piece.sourceLength) appendNewlines(.soft) isStartOfFile = false requiresNextNewline = true + leadingIndent = nil case .docBlockComment(let text): generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength) - appendToken(.comment(Comment(kind: .docBlock, text: text), wasEndOfLine: false)) + appendToken(.comment(Comment(kind: .docBlock, leadingIndent: leadingIndent, text: text), wasEndOfLine: false)) generateDisableFormattingIfNecessary(position + piece.sourceLength) appendNewlines(.soft) isStartOfFile = false requiresNextNewline = false + leadingIndent = nil case .newlines(let count), .carriageReturns(let count), .carriageReturnLineFeeds(let count): + leadingIndent = .spaces(0) guard !isStartOfFile else { break } if requiresNextNewline || @@ -3372,9 +3381,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { let isBOM = text == "\u{feff}" requiresNextNewline = !isBOM isStartOfFile = isStartOfFile && isBOM + leadingIndent = nil - default: - break + case .backslashes, .formfeeds, .pounds, .verticalTabs: + leadingIndent = nil + + case .spaces(let n): + guard leadingIndent == .spaces(0) else { break } + leadingIndent = .spaces(n) + case .tabs(let n): + guard leadingIndent == .spaces(0) else { break } + leadingIndent = .tabs(n) } position += piece.sourceLength } diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift index 64f9aa039..52226bed5 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift @@ -583,14 +583,14 @@ final class CommentTests: PrettyPrintTestCase { func testBlockComments() { let input = """ - /* Line Comment1 */ + /* Line Comment1 */ /* Line Comment2 */ let a = 123 let b = "456" /* End of line comment */ let c = "More content" - /* Comment 3 - Comment 4 */ + /* Comment 3 + Comment 4 */ let reallyLongVariableName = 123 /* This comment should wrap */ @@ -603,7 +603,9 @@ final class CommentTests: PrettyPrintTestCase { } let d = 123 - /* Trailing Comment */ + /* Trailing Comment */ + /* Trailing + Block Comment */ """ let expected = @@ -633,6 +635,8 @@ final class CommentTests: PrettyPrintTestCase { let d = 123 /* Trailing Comment */ + /* Trailing + Block Comment */ """ From 16c2c28cbaf25181ab13d124868e3331f14d57ca Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Wed, 14 Aug 2024 13:51:32 -0400 Subject: [PATCH 238/332] Support assume-filename option to find the configuration file when reading from stdin. --- Sources/swift-format/Frontend/Frontend.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index 247d682d3..307335883 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -117,9 +117,11 @@ class Frontend { /// Processes source content from standard input. private func processStandardInput() { + let assumedUrl = lintFormatOptions.assumeFilename.map(URL.init(fileURLWithPath:)) + guard let configuration = configuration( fromPathOrString: lintFormatOptions.configuration, - orInferredFromSwiftFileAt: nil) + orInferredFromSwiftFileAt: assumedUrl) else { // Already diagnosed in the called method. return @@ -127,7 +129,7 @@ class Frontend { let fileToProcess = FileToProcess( fileHandle: FileHandle.standardInput, - url: URL(fileURLWithPath: lintFormatOptions.assumeFilename ?? ""), + url: assumedUrl ?? URL(fileURLWithPath: ""), configuration: configuration, selection: Selection(offsetRanges: lintFormatOptions.offsets)) processFile(fileToProcess) From 6e0e645e1aa7ec342af98a6403833bfbe2fb6db0 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 14 Aug 2024 18:05:33 -0700 Subject: [PATCH 239/332] Fix build warnings --- .../SwiftFormat/PrettyPrint/TokenStreamCreator.swift | 12 +++++++----- .../Rules/UseSynthesizedInitializer.swift | 2 +- .../Rules/ValidateDocumentationComments.swift | 2 +- Sources/generate-swift-format/FileGenerator.swift | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index e88f1dccb..ce0dbe8c8 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -527,9 +527,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// Arranges the `async` and `throws` effect specifiers of a function or accessor declaration. private func arrangeEffectSpecifiers(_ node: Node) { before(node.asyncSpecifier, tokens: .break) - before(node.throwsSpecifier, tokens: .break) + before(node.throwsClause?.throwsSpecifier, tokens: .break) // Keep them together if both `async` and `throws` are present. - if let asyncSpecifier = node.asyncSpecifier, let throwsSpecifier = node.throwsSpecifier { + if let asyncSpecifier = node.asyncSpecifier, let throwsSpecifier = node.throwsClause?.throwsSpecifier { before(asyncSpecifier, tokens: .open) after(throwsSpecifier, tokens: .close) } @@ -2302,9 +2302,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AttributedTypeSyntax) -> SyntaxVisitorContinueKind { arrangeAttributeList(node.attributes) - after( - node.specifier, - tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + for specifier in node.specifiers { + after( + specifier.firstToken(viewMode: .sourceAccurate), + tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + } return .visitChildren } diff --git a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift index 43ba2d67d..80614b64a 100644 --- a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift @@ -36,7 +36,7 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { // Collect any possible redundant initializers into a list } else if let initDecl = member.as(InitializerDeclSyntax.self) { guard initDecl.optionalMark == nil else { continue } - guard initDecl.signature.effectSpecifiers?.throwsSpecifier == nil else { continue } + guard initDecl.signature.effectSpecifiers?.throwsClause == nil else { continue } initializers.append(initDecl) } } diff --git a/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift index 95c6c1238..8bb5b117b 100644 --- a/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift @@ -62,7 +62,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { } validateThrows( - signature.effectSpecifiers?.throwsSpecifier, + signature.effectSpecifiers?.throwsClause?.throwsSpecifier, name: name, throwsDescription: docComment.throws, node: node) diff --git a/Sources/generate-swift-format/FileGenerator.swift b/Sources/generate-swift-format/FileGenerator.swift index bd4c8b32a..85a8861bc 100644 --- a/Sources/generate-swift-format/FileGenerator.swift +++ b/Sources/generate-swift-format/FileGenerator.swift @@ -35,7 +35,7 @@ extension FileGenerator { } } -extension FileHandle: TextOutputStream { +extension FileHandle { /// Writes the provided string as data to a file output stream. public func write(_ string: String) { guard let data = string.data(using: .utf8) else { return } From 705af094a979e9e360c095671663a9097bd085b1 Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Mon, 12 Aug 2024 16:40:11 -0700 Subject: [PATCH 240/332] Wrap multiline string literals to line length. Change how PrettyPrinter emits StringSegmentSyntax's so that they can be broken up over multiple lines within multiline string literals. This does not change the behavior of single line string literals, which cannot contain newlines. This also does not change interpolations which are still emitted as verbatims and are never line broken by the pretty printer. Line breaking is done exclusively through escaped newlines. A literal containing escaped newlines will remove all escaped newlines and then reinsert them based on line length. Hard newlines are respected by the formatter and will not be moved, even if it causes short lines. Escaped newlines will be reformatted by the pretty printer so that lines ending in an escaped newline are at line length. Wrapping is implemented by introducing a new newlinebreak behavior `.escaped`. `.escaped` acts very similarly to `.elective` but has slightly different length calculation logic and printing behavior. An escaped newline is printed including it's preceeding whitespace followed by "\\\n". So a break of `.break(_, 2, .escaped)` will print as " \\\n". Because an escaped line break takes up characters when broken (unlike other breaks), length calculation must be handled differently for `.escaped` breaks. --- .../API/Configuration+Default.swift | 1 + Sources/SwiftFormat/API/Configuration.swift | 71 +++++ .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 55 +++- .../PrettyPrint/PrettyPrintBuffer.swift | 2 + Sources/SwiftFormat/PrettyPrint/Token.swift | 4 + .../PrettyPrint/TokenStreamCreator.swift | 112 ++++++-- .../PrettyPrint/StringTests.swift | 244 +++++++++++++++++- 7 files changed, 452 insertions(+), 37 deletions(-) diff --git a/Sources/SwiftFormat/API/Configuration+Default.swift b/Sources/SwiftFormat/API/Configuration+Default.swift index ca57700bb..d18164f39 100644 --- a/Sources/SwiftFormat/API/Configuration+Default.swift +++ b/Sources/SwiftFormat/API/Configuration+Default.swift @@ -40,5 +40,6 @@ extension Configuration { self.spacesAroundRangeFormationOperators = false self.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() self.multiElementCollectionTrailingCommas = true + self.reflowMultilineStringLiterals = .never } } diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 19af3de57..c6836ab8a 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -45,6 +45,7 @@ public struct Configuration: Codable, Equatable { case spacesAroundRangeFormationOperators case noAssignmentInExpressions case multiElementCollectionTrailingCommas + case reflowMultilineStringLiterals } /// A dictionary containing the default enabled/disabled states of rules, keyed by the rules' @@ -194,6 +195,71 @@ public struct Configuration: Codable, Equatable { /// ``` public var multiElementCollectionTrailingCommas: Bool + /// Determines how multiline string literals should reflow when formatted. + public enum MultilineStringReflowBehavior: Codable { + /// Never reflow multiline string literals. + case never + /// Reflow lines in string literal that exceed the maximum line length. For example with a line length of 10: + /// ```swift + /// """ + /// an escape\ + /// line break + /// a hard line break + /// """ + /// ``` + /// will be formatted as: + /// ```swift + /// """ + /// an esacpe\ + /// line break + /// a hard \ + /// line break + /// """ + /// ``` + /// The existing `\` is left in place, but the line over line length is broken. + case onlyLinesOverLength + /// Always reflow multiline string literals, this will ignore existing escaped newlines in the literal and reflow each line. Hard linebreaks are still respected. + /// For example, with a line length of 10: + /// ```swift + /// """ + /// one \ + /// word \ + /// a line. + /// this is too long. + /// """ + /// ``` + /// will be formatted as: + /// ```swift + /// """ + /// one word \ + /// a line. + /// this is \ + /// too long. + /// """ + /// ``` + case always + + var isNever: Bool { + switch self { + case .never: + return true + default: + return false + } + } + + var isAlways: Bool { + switch self { + case .always: + return true + default: + return false + } + } + } + + public var reflowMultilineStringLiterals: MultilineStringReflowBehavior + /// Creates a new `Configuration` by loading it from a configuration file. public init(contentsOf url: URL) throws { let data = try Data(contentsOf: url) @@ -287,6 +353,10 @@ public struct Configuration: Codable, Equatable { Bool.self, forKey: .multiElementCollectionTrailingCommas) ?? defaults.multiElementCollectionTrailingCommas + self.reflowMultilineStringLiterals = + try container.decodeIfPresent(MultilineStringReflowBehavior.self, forKey: .reflowMultilineStringLiterals) + ?? defaults.reflowMultilineStringLiterals + // If the `rules` key is not present at all, default it to the built-in set // so that the behavior is the same as if the configuration had been // default-initialized. To get an empty rules dictionary, one can explicitly @@ -321,6 +391,7 @@ public struct Configuration: Codable, Equatable { try container.encode(indentSwitchCaseLabels, forKey: .indentSwitchCaseLabels) try container.encode(noAssignmentInExpressions, forKey: .noAssignmentInExpressions) try container.encode(multiElementCollectionTrailingCommas, forKey: .multiElementCollectionTrailingCommas) + try container.encode(reflowMultilineStringLiterals, forKey: .reflowMultilineStringLiterals) try container.encode(rules, forKey: .rules) } diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index ada574f1c..8ad1c04c9 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -214,7 +214,6 @@ public class PrettyPrinter { switch token { case .contextualBreakingStart: activeBreakingContexts.append(ActiveBreakingContext(lineNumber: outputBuffer.lineNumber)) - // Discard the last finished breaking context to keep it from effecting breaks inside of the // new context. The discarded context has already either had an impact on the contextual break // after it or there was no relevant contextual break, so it's safe to discard. @@ -414,7 +413,7 @@ public class PrettyPrinter { var overrideBreakingSuppressed = false switch newline { - case .elective: break + case .elective, .escaped: break case .soft(_, let discretionary): // A discretionary newline (i.e. from the source) should create a line break even if the // rules for breaking are disabled. @@ -429,6 +428,10 @@ public class PrettyPrinter { let suppressBreaking = isBreakingSuppressed && !overrideBreakingSuppressed if !suppressBreaking && (!canFit(length) || mustBreak) { currentLineIsContinuation = isContinuationIfBreakFires + if case .escaped = newline { + outputBuffer.enqueueSpaces(size) + outputBuffer.write("\\") + } outputBuffer.writeNewlines(newline) lastBreak = true } else { @@ -594,19 +597,51 @@ public class PrettyPrinter { // Break lengths are equal to its size plus the token or group following it. Calculate the // length of any prior break tokens. case .break(_, let size, let newline): - if let index = delimIndexStack.last, case .break = tokens[index] { - lengths[index] += total + if let index = delimIndexStack.last, case .break(_, _, let lastNewline) = tokens[index] { + /// If the last break and this break are both `.escaped` we add an extra 1 to the total for the last `.escaped` break. + /// This is to handle situations where adding the `\` for an escaped line break would put us over the line length. + /// For example, consider the token sequence: + /// `[.syntax("this fits"), .break(.escaped), .syntax("this fits in line length"), .break(.escaped)]` + /// The naive layout of these tokens will incorrectly print as: + /// """ + /// this fits this fits in line length \ + /// """ + /// which will be too long because of the '\' character. Instead we have to print it as: + /// """ + /// this fits \ + /// this fits in line length + /// """ + /// + /// While not prematurely inserting a line in situations where a hard line break is occurring, such as: + /// + /// `[.syntax("some text"), .break(.escaped), .syntax("this is exactly the right length"), .break(.hard)]` + /// + /// We want this to print as: + /// """ + /// some text this is exactly the right length + /// """ + /// and not: + /// """ + /// some text \ + /// this is exactly the right length + /// """ + if case .escaped = newline, case .escaped = lastNewline { + lengths[index] += total + 1 + } else { + lengths[index] += total + } delimIndexStack.removeLast() } lengths.append(-total) delimIndexStack.append(i) - if case .elective = newline { - total += size - } else { - // `size` is never used in this case, because the break always fires. Use `maxLineLength` - // to ensure enclosing groups are large enough to force preceding breaks to fire. - total += maxLineLength + switch newline { + case .elective, .escaped: + total += size + default: + // `size` is never used in this case, because the break always fires. Use `maxLineLength` + // to ensure enclosing groups are large enough to force preceding breaks to fire. + total += maxLineLength } // Space tokens have a length equal to its size. diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift index 02a2a7ac6..6c3402a00 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift @@ -81,6 +81,8 @@ struct PrettyPrintBuffer { numberToPrint = min(count, maximumBlankLines + 1) - consecutiveNewlineCount case .hard(let count): numberToPrint = count + case .escaped: + numberToPrint = 1 } guard numberToPrint > 0 else { return } diff --git a/Sources/SwiftFormat/PrettyPrint/Token.swift b/Sources/SwiftFormat/PrettyPrint/Token.swift index 277317c62..654f09c54 100644 --- a/Sources/SwiftFormat/PrettyPrint/Token.swift +++ b/Sources/SwiftFormat/PrettyPrint/Token.swift @@ -147,6 +147,10 @@ enum NewlineBehavior { /// newlines and the configured maximum number of blank lines. case hard(count: Int) + /// Break onto a new line is allowed if neccessary. If a line break is emitted, it will be escaped with a '\', and this breaks whitespace will be printed prior to the + /// escaped line break. This is useful in multiline strings where we don't want newlines printed in syntax to appear in the literal. + case escaped + /// An elective newline that respects discretionary newlines from the user-entered text. static let elective = NewlineBehavior.elective(ignoresDiscretionary: false) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index e88f1dccb..e7ae27201 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -15,7 +15,7 @@ import SwiftOperators import SwiftSyntax fileprivate extension AccessorBlockSyntax { - /// Assuming that the accessor only contains an implicit getter (i.e. no + /// Assuming that the accessor only contains an implicit getter (i.e. no /// `get` or `set`), return the code block items in that getter. var getterCodeBlockItems: CodeBlockItemListSyntax { guard case .getter(let codeBlockItemList) = self.accessors else { @@ -1437,9 +1437,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: ReturnClauseSyntax) -> SyntaxVisitorContinueKind { if node.parent?.is(FunctionTypeSyntax.self) ?? false { - // `FunctionTypeSyntax` used to not use `ReturnClauseSyntax` and had - // slightly different formatting behavior than the normal - // `ReturnClauseSyntax`. To maintain the previous formatting behavior, + // `FunctionTypeSyntax` used to not use `ReturnClauseSyntax` and had + // slightly different formatting behavior than the normal + // `ReturnClauseSyntax`. To maintain the previous formatting behavior, // add a special case. before(node.arrow, tokens: .break) before(node.type.firstToken(viewMode: .sourceAccurate), tokens: .break) @@ -1819,7 +1819,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: OriginallyDefinedInAttributeArgumentsSyntax) -> SyntaxVisitorContinueKind { after(node.colon.lastToken(viewMode: .sourceAccurate), tokens: .break(.same, size: 1)) after(node.comma.lastToken(viewMode: .sourceAccurate), tokens: .break(.same, size: 1)) - return .visitChildren + return .visitChildren } override func visit(_ node: DocumentationAttributeArgumentSyntax) -> SyntaxVisitorContinueKind { @@ -2446,6 +2446,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if !node.segments.isEmpty { before(node.closingQuote, tokens: .break(breakKind, newlines: .hard(count: 1))) } + if shouldFormatterIgnore(node: Syntax(node)) { + appendFormatterIgnored(node: Syntax(node)) + // Mirror the tokens we'd normally append on '"""' + appendTrailingTrivia(node.closingQuote) + appendAfterTokensAndTrailingComments(node.closingQuote) + return .skipChildren + } } return .visitChildren } @@ -2460,38 +2467,92 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: StringSegmentSyntax) -> SyntaxVisitorContinueKind { - // Looks up the correct break kind based on prior context. - func breakKind() -> BreakKind { - if let stringLiteralSegments = node.parent?.as(StringLiteralSegmentListSyntax.self), - let stringLiteralExpr = stringLiteralSegments.parent?.as(StringLiteralExprSyntax.self) - { - return pendingMultilineStringBreakKinds[stringLiteralExpr, default: .same] + // Insert an `.escaped` break token after each series of whitespace in a substring + private func emitMultilineSegmentTextTokens(breakKind: BreakKind, segment: Substring) { + var currentWord = [Unicode.Scalar]() + var currentBreak = [Unicode.Scalar]() + + func emitWord() { + if !currentWord.isEmpty { + var str = "" + str.unicodeScalars.append(contentsOf: currentWord) + appendToken(.syntax(str)) + currentWord = [] + } + } + func emitBreak() { + if !currentBreak.isEmpty { + // We append this as a syntax, instead of a `.space`, so that it is always included in the output. + var str = "" + str.unicodeScalars.append(contentsOf: currentBreak) + appendToken(.syntax(str)) + appendToken(.break(breakKind, size: 0, newlines: .escaped)) + currentBreak = [] + } + } + + for scalar in segment.unicodeScalars { + // We don't have to worry about newlines occurring in segments. + // Either a segment will end in a newline character or the newline will be in trivia. + if scalar.properties.isWhitespace { + emitWord() + currentBreak.append(scalar) } else { - return .same + emitBreak() + currentWord.append(scalar) } } + // Only one of these will actually do anything based on whether our last char was whitespace or not. + emitWord() + emitBreak() + } + + override func visit(_ node: StringSegmentSyntax) -> SyntaxVisitorContinueKind { + // Looks up the correct break kind based on prior context. + let stringLiteralParent = + node.parent? + .as(StringLiteralSegmentListSyntax.self)? + .parent? + .as(StringLiteralExprSyntax.self) + let breakKind = stringLiteralParent.map { + pendingMultilineStringBreakKinds[$0, default: .same] + } ?? .same + + let isMultiLineString = + stringLiteralParent?.openingQuote.tokenKind == .multilineStringQuote + // We don't reflow raw strings, so treat them as if they weren't multiline + && stringLiteralParent?.openingPounds == nil + + let emitSegmentTextTokens = + // If our configure reflow behavior is never, always use the single line emit segment text tokens. + isMultiLineString && !config.reflowMultilineStringLiterals.isNever + ? { (segment) in self.emitMultilineSegmentTextTokens(breakKind: breakKind, segment: segment) } + // For single line strings we don't allow line breaks, so emit the string as a single `.syntax` token + : { (segment) in self.appendToken(.syntax(String(segment))) } + let segmentText = node.content.text if segmentText.hasSuffix("\n") { // If this is a multiline string segment, it will end in a newline. Remove the newline and // append the rest of the string, followed by a break if it's not the last line before the // closing quotes. (The `StringLiteralExpr` above does the closing break.) let remainder = node.content.text.dropLast() + if !remainder.isEmpty { - appendToken(.syntax(String(remainder))) + // Replace each space in the segment text by an elective break of size 1 + emitSegmentTextTokens(remainder) } - appendToken(.break(breakKind(), newlines: .hard(count: 1))) + appendToken(.break(breakKind, newlines: .hard(count: 1))) } else { - appendToken(.syntax(segmentText)) + emitSegmentTextTokens(segmentText[...]) } - if node.trailingTrivia.containsBackslashes { + if node.trailingTrivia.containsBackslashes && !config.reflowMultilineStringLiterals.isAlways { // Segments with trailing backslashes won't end with a literal newline; the backslash is // considered trivia. To preserve the original text and wrapping, we need to manually render // the backslash and a break into the token stream. appendToken(.syntax("\\")) - appendToken(.break(breakKind(), newlines: .hard(count: 1))) + appendToken(.break(breakKind, newlines: .hard(count: 1))) } return .skipChildren } @@ -3198,7 +3259,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // There must be a break with a soft newline after the comment, but it's impossible to // know which kind of break must be used. Adding this newline is deferred until the // comment is added to the token stream. - ]) + ] + ) case .blockComment(let text): return ( @@ -3487,12 +3549,18 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let last = tokens.last { switch (last, token) { + case (.break(_, _, .escaped), _), (_, .break(_, _, .escaped)): + lastBreakIndex = tokens.endIndex + // Don't allow merging for .escaped breaks + canMergeNewlinesIntoLastBreak = false + tokens.append(token) + return case (.break(let breakKind, _, .soft(1, _)), .comment(let c2, _)) where breakAllowsCommentMerge(breakKind) && (c2.kind == .docLine || c2.kind == .line): // we are search for the pattern of [line comment] - [soft break 1] - [line comment] // where the comment type is the same; these can be merged into a single comment if let nextToLast = tokens.dropLast().last, - case let .comment(c1, false) = nextToLast, + case let .comment(c1, false) = nextToLast, c1.kind == c2.kind { var mergedComment = c1 @@ -4291,6 +4359,10 @@ extension NewlineBehavior { // `lhs` is either also elective or a required newline, which overwrites elective. return lhs + case (.escaped, _): + return rhs + case (_, .escaped): + return lhs case (.soft(let lhsCount, let lhsDiscretionary), .soft(let rhsCount, let rhsDiscretionary)): let mergedCount: Int if lhsDiscretionary && rhsDiscretionary { diff --git a/Tests/SwiftFormatTests/PrettyPrint/StringTests.swift b/Tests/SwiftFormatTests/PrettyPrint/StringTests.swift index a9cc75f96..3c9f937e7 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/StringTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/StringTests.swift @@ -1,3 +1,5 @@ +@_spi(Rules) @_spi(Testing) import SwiftFormat + final class StringTests: PrettyPrintTestCase { func testStrings() { let input = @@ -22,6 +24,173 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 35) } + func testLongMultilinestringIsWrapped() { + let input = + #""" + let someString = """ + this string's total lengths will be longer than the column limit even though its individual lines are as well, whoops. + """ + """# + + let expected = + #""" + let someString = """ + this string's total \ + lengths will be longer \ + than the column limit even \ + though its individual \ + lines are as well, whoops. + """ + + """# + + var config = Configuration() + config.reflowMultilineStringLiterals = .onlyLinesOverLength + assertPrettyPrintEqual( + input: input, + expected: expected, + linelength: 30, + configuration: config) + } + + func testMultilineStringIsNotReformattedWithIgnore() { + let input = + #""" + let someString = + // swift-format-ignore + """ + lines \ + are \ + short. + """ + """# + + let expected = + #""" + let someString = + // swift-format-ignore + """ + lines \ + are \ + short. + """ + + """# + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 30) + } + + func testMultilineStringIsNotReformattedWithReflowDisabled() { + let input = + #""" + let someString = + """ + lines \ + are \ + short. + """ + """# + + let expected = + #""" + let someString = + """ + lines \ + are \ + short. + """ + + """# + + var config = Configuration() + config.reflowMultilineStringLiterals = .onlyLinesOverLength + assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) + } + + func testMultilineStringWithInterpolations() { + let input = + #""" + if true { + guard let opt else { + functionCall(""" + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero \(2) \(testVariable) ids risus placerat imperdiet. Praesent fringilla vel nisi sed fermentum. In vitae purus feugiat, euismod nulla in, rhoncus leo. Suspendisse feugiat sapien lobortis facilisis malesuada. Aliquam feugiat suscipit accumsan. Praesent tempus fermentum est, vel blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt efficitur ante id fermentum. + """) + } + } + """# + + let expected = + #""" + if true { + guard let opt else { + functionCall( + """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero \(2) \ + \(testVariable) ids risus placerat imperdiet. Praesent fringilla vel nisi sed fermentum. In \ + vitae purus feugiat, euismod nulla in, rhoncus leo. Suspendisse feugiat sapien lobortis \ + facilisis malesuada. Aliquam feugiat suscipit accumsan. Praesent tempus fermentum est, vel \ + blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt efficitur ante id fermentum. + """) + } + } + + """# + + var config = Configuration() + config.reflowMultilineStringLiterals = .onlyLinesOverLength + assertPrettyPrintEqual(input: input, expected: expected, linelength: 100, configuration: config) + } + + func testMutlilineStringsRespectsHardLineBreaks() { + let input = + #""" + """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero ids risus placerat imperdiet. Praesent fringilla vel nisi sed fermentum. In vitae purus feugiat, euismod nulla in, rhoncus leo. + Suspendisse feugiat sapien lobortis facilisis malesuada. Aliquam feugiat suscipit accumsan. Praesent tempus fermentum est, vel blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt efficitur ante id fermentum. + """ + """# + + let expected = + #""" + """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero ids risus placerat \ + imperdiet. Praesent fringilla vel nisi sed fermentum. In vitae purus feugiat, euismod nulla in, \ + rhoncus leo. + Suspendisse feugiat sapien lobortis facilisis malesuada. Aliquam feugiat suscipit accumsan. \ + Praesent tempus fermentum est, vel blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt \ + efficitur ante id fermentum. + """ + + """# + + var config = Configuration() + config.reflowMultilineStringLiterals = .onlyLinesOverLength + assertPrettyPrintEqual(input: input, expected: expected, linelength: 100, configuration: config) + } + + func testMultilineStringsWrapAroundInterpolations() { + let input = + #""" + """ + An interpolation should be treated as a single "word" and can't be broken up \(aLongVariableName + anotherLongVariableName), so no line breaks should be available within the expr. + """ + """# + + let expected = + #""" + """ + An interpolation should be treated as a single "word" and can't be broken up \ + \(aLongVariableName + anotherLongVariableName), so no line breaks should be available within the \ + expr. + """ + + """# + + var config = Configuration() + config.reflowMultilineStringLiterals = .onlyLinesOverLength + assertPrettyPrintEqual(input: input, expected: expected, linelength: 100, configuration: config) + } + func testMultilineStringOpenQuotesDoNotWrapIfStringIsVeryLong() { let input = #""" @@ -102,6 +271,27 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 25) } + func testMultilineStringWithWordLongerThanLineLength() { + let input = + #""" + """ + there isn't an opportunity to break up this long url: https://www.cool-math-games.org/games/id?=01913310-b7c3-77d8-898e-300ccd451ea8 + """ + """# + let expected = + #""" + """ + there isn't an opportunity to break up this long url: \ + https://www.cool-math-games.org/games/id?=01913310-b7c3-77d8-898e-300ccd451ea8 + """ + + """# + + var config = Configuration() + config.reflowMultilineStringLiterals = .onlyLinesOverLength + assertPrettyPrintEqual(input: input, expected: expected, linelength: 70, configuration: config) + } + func testMultilineStringInterpolations() { let input = #""" @@ -218,7 +408,7 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 25) } - func testMultilineStringPreservesTrailingBackslashes() { + func testMultilineStringReflowsTrailingBackslashes() { let input = #""" let x = """ @@ -235,14 +425,54 @@ final class StringTests: PrettyPrintTestCase { let x = """ there should be \ backslashes at \ - the end of \ - every line \ - except this one + the end of every \ + line except this \ + one """ """# - assertPrettyPrintEqual(input: input, expected: expected, linelength: 20) + var config = Configuration.forTesting + config.reflowMultilineStringLiterals = .always + assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: config) + } + + func testRawMultilineStringIsNotFormatted() { + let input = + ##""" + #""" + this is a long line that is not broken. + """# + """## + let expected = + ##""" + #""" + this is a long line that is not broken. + """# + + """## + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 10) + } + + func testMultilineStringIsNotFormattedWithNeverReflowBehavior() { + let input = + #""" + """ + this is a long line that is not broken. + """ + """# + let expected = + #""" + """ + this is a long line that is not broken. + """ + + """# + + var config = Configuration.forTesting + config.reflowMultilineStringLiterals = .never + assertPrettyPrintEqual(input: input, expected: expected, linelength: 10, configuration: config) } func testMultilineStringInParenthesizedExpression() { @@ -437,7 +667,7 @@ final class StringTests: PrettyPrintTestCase { let x = """ blah blah - """.data(using: .utf8) { + """.data(using: .utf8) else { print(x) } """# @@ -449,7 +679,7 @@ final class StringTests: PrettyPrintTestCase { blah blah """.data(using: .utf8) - { + else { print(x) } From 0c71671aa7c192b8de16cae0966a487ce1382009 Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Thu, 22 Aug 2024 09:14:32 -0300 Subject: [PATCH 241/332] Add NoEmptyLinesOpeningClosingBraces rule > This rule removes empty lines after opening braces and before closing braces: > > ```swift > // original > > struct P { > > let x: Int > > let y: Int > > } > > // formatted > > struct P { > let x: Int > > let y: Int > } > ``` > > That's similar to [vertical_whitespace_opening_braces](https://realm.github.io/SwiftLint/vertical_whitespace_opening_braces.html) and [vertical_whitespace_closing_braces](https://realm.github.io/SwiftLint/vertical_whitespace_opening_braces.html). > > It's a style used in official swift projects such as **swift-syntax** and **sourcekit-lsp**. --- Documentation/RuleDocumentation.md | 11 ++ Sources/SwiftFormat/CMakeLists.txt | 1 + .../Core/Pipelines+Generated.swift | 19 +++ .../Core/RuleNameCache+Generated.swift | 1 + .../Core/RuleRegistry+Generated.swift | 1 + .../NoEmptyLineOpeningClosingBraces.swift | 131 ++++++++++++++++ ...oEmptyLinesOpeningClosingBracesTests.swift | 140 ++++++++++++++++++ 7 files changed, 304 insertions(+) create mode 100644 Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift create mode 100644 Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index 05cdeda22..ecb376ec1 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -29,6 +29,7 @@ Here's the list of available rules: - [NoAssignmentInExpressions](#NoAssignmentInExpressions) - [NoBlockComments](#NoBlockComments) - [NoCasesWithOnlyFallthrough](#NoCasesWithOnlyFallthrough) +- [NoEmptyLinesOpeningClosingBraces](#NoEmptyLinesOpeningClosingBraces) - [NoEmptyTrailingClosureParentheses](#NoEmptyTrailingClosureParentheses) - [NoLabelsInCasePatterns](#NoLabelsInCasePatterns) - [NoLeadingUnderscores](#NoLeadingUnderscores) @@ -271,6 +272,16 @@ Format: The fallthrough `case` is added as a prefix to the next case unless the `NoCasesWithOnlyFallthrough` rule can format your code automatically. +### NoEmptyLinesOpeningClosingBraces + +Empty lines are forbidden after opening braces and before closing braces. + +Lint: Empty lines after opening braces and before closing braces yield a lint error. + +Format: Empty lines after opening braces and before closing braces will be removed. + +`NoEmptyLinesOpeningClosingBraces` rule can format your code automatically. + ### NoEmptyTrailingClosureParentheses Function calls with no arguments and a trailing closure should not have empty parentheses. diff --git a/Sources/SwiftFormat/CMakeLists.txt b/Sources/SwiftFormat/CMakeLists.txt index 2564f90b8..471cfa719 100644 --- a/Sources/SwiftFormat/CMakeLists.txt +++ b/Sources/SwiftFormat/CMakeLists.txt @@ -73,6 +73,7 @@ add_library(SwiftFormat Rules/NoAssignmentInExpressions.swift Rules/NoBlockComments.swift Rules/NoCasesWithOnlyFallthrough.swift + Rules/NoEmptyLineOpeningClosingBraces.swift Rules/NoEmptyTrailingClosureParentheses.swift Rules/NoLabelsInCasePatterns.swift Rules/NoLeadingUnderscores.swift diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 4e524c56b..6f22e1384 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -36,6 +36,14 @@ class LintPipeline: SyntaxVisitor { super.init(viewMode: .sourceAccurate) } + override func visit(_ node: AccessorBlockSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoEmptyLinesOpeningClosingBraces.visit, for: node) + return .visitChildren + } + override func visitPost(_ node: AccessorBlockSyntax) { + onVisitPost(rule: NoEmptyLinesOpeningClosingBraces.self, for: node) + } + override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) @@ -93,10 +101,12 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoEmptyLinesOpeningClosingBraces.visit, for: node) visitIfEnabled(OmitExplicitReturns.visit, for: node) return .visitChildren } override func visitPost(_ node: ClosureExprSyntax) { + onVisitPost(rule: NoEmptyLinesOpeningClosingBraces.self, for: node) onVisitPost(rule: OmitExplicitReturns.self, for: node) } @@ -134,10 +144,12 @@ class LintPipeline: SyntaxVisitor { override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) + visitIfEnabled(NoEmptyLinesOpeningClosingBraces.visit, for: node) return .visitChildren } override func visitPost(_ node: CodeBlockSyntax) { onVisitPost(rule: AmbiguousTrailingClosureOverload.self, for: node) + onVisitPost(rule: NoEmptyLinesOpeningClosingBraces.self, for: node) } override func visit(_ node: ConditionElementSyntax) -> SyntaxVisitorContinueKind { @@ -384,10 +396,12 @@ class LintPipeline: SyntaxVisitor { override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) + visitIfEnabled(NoEmptyLinesOpeningClosingBraces.visit, for: node) return .visitChildren } override func visitPost(_ node: MemberBlockSyntax) { onVisitPost(rule: AmbiguousTrailingClosureOverload.self, for: node) + onVisitPost(rule: NoEmptyLinesOpeningClosingBraces.self, for: node) } override func visit(_ node: OptionalBindingConditionSyntax) -> SyntaxVisitorContinueKind { @@ -411,10 +425,12 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: PrecedenceGroupDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoEmptyLinesOpeningClosingBraces.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } override func visitPost(_ node: PrecedenceGroupDeclSyntax) { + onVisitPost(rule: NoEmptyLinesOpeningClosingBraces.self, for: node) onVisitPost(rule: NoLeadingUnderscores.self, for: node) } @@ -511,10 +527,12 @@ class LintPipeline: SyntaxVisitor { } override func visit(_ node: SwitchExprSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoEmptyLinesOpeningClosingBraces.visit, for: node) visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } override func visitPost(_ node: SwitchExprSyntax) { + onVisitPost(rule: NoEmptyLinesOpeningClosingBraces.self, for: node) onVisitPost(rule: NoParensAroundConditions.self, for: node) } @@ -597,6 +615,7 @@ extension FormatPipeline { node = NoAccessLevelOnExtensionDeclaration(context: context).rewrite(node) node = NoAssignmentInExpressions(context: context).rewrite(node) node = NoCasesWithOnlyFallthrough(context: context).rewrite(node) + node = NoEmptyLinesOpeningClosingBraces(context: context).rewrite(node) node = NoEmptyTrailingClosureParentheses(context: context).rewrite(node) node = NoLabelsInCasePatterns(context: context).rewrite(node) node = NoParensAroundConditions(context: context).rewrite(node) diff --git a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift index ac542c1d4..ed06b5577 100644 --- a/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleNameCache+Generated.swift @@ -34,6 +34,7 @@ public let ruleNameCache: [ObjectIdentifier: String] = [ ObjectIdentifier(NoAssignmentInExpressions.self): "NoAssignmentInExpressions", ObjectIdentifier(NoBlockComments.self): "NoBlockComments", ObjectIdentifier(NoCasesWithOnlyFallthrough.self): "NoCasesWithOnlyFallthrough", + ObjectIdentifier(NoEmptyLinesOpeningClosingBraces.self): "NoEmptyLinesOpeningClosingBraces", ObjectIdentifier(NoEmptyTrailingClosureParentheses.self): "NoEmptyTrailingClosureParentheses", ObjectIdentifier(NoLabelsInCasePatterns.self): "NoLabelsInCasePatterns", ObjectIdentifier(NoLeadingUnderscores.self): "NoLeadingUnderscores", diff --git a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift index a5aeeab1f..d5c9c9ba1 100644 --- a/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift +++ b/Sources/SwiftFormat/Core/RuleRegistry+Generated.swift @@ -33,6 +33,7 @@ "NoAssignmentInExpressions": true, "NoBlockComments": true, "NoCasesWithOnlyFallthrough": true, + "NoEmptyLinesOpeningClosingBraces": false, "NoEmptyTrailingClosureParentheses": true, "NoLabelsInCasePatterns": true, "NoLeadingUnderscores": false, diff --git a/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift b/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift new file mode 100644 index 000000000..5fc32a5f0 --- /dev/null +++ b/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// Empty lines are forbidden after opening braces and before closing braces. +/// +/// Lint: Empty lines after opening braces and before closing braces yield a lint error. +/// +/// Format: Empty lines after opening braces and before closing braces will be removed. +@_spi(Rules) +public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule { + public override class var isOptIn: Bool { return true } + + public override func visit(_ node: AccessorBlockSyntax) -> AccessorBlockSyntax { + var result = node + switch node.accessors { + case .accessors(let accessors): + result.accessors = .init(rewritten(accessors)) + case .getter(let getter): + result.accessors = .init(rewritten(getter)) + } + result.rightBrace = rewritten(node.rightBrace) + return result + } + + public override func visit(_ node: CodeBlockSyntax) -> CodeBlockSyntax { + var result = node + result.statements = rewritten(node.statements) + result.rightBrace = rewritten(node.rightBrace) + return result + } + + public override func visit(_ node: MemberBlockSyntax) -> MemberBlockSyntax { + var result = node + result.members = rewritten(node.members) + result.rightBrace = rewritten(node.rightBrace) + return result + } + + public override func visit(_ node: ClosureExprSyntax) -> ExprSyntax { + var result = node + result.statements = rewritten(node.statements) + result.rightBrace = rewritten(node.rightBrace) + return ExprSyntax(result) + } + + public override func visit(_ node: SwitchExprSyntax) -> ExprSyntax { + var result = node + result.cases = rewritten(node.cases) + result.rightBrace = rewritten(node.rightBrace) + return ExprSyntax(result) + } + + public override func visit(_ node: PrecedenceGroupDeclSyntax) -> DeclSyntax { + var result = node + result.attributes = rewritten(node.attributes) + result.rightBrace = rewritten(node.rightBrace) + return DeclSyntax(result) + } + + func rewritten(_ token: TokenSyntax) -> TokenSyntax { + let (trimmedLeadingTrivia, count) = token.leadingTrivia.trimmingSuperfluousNewlines() + if trimmedLeadingTrivia.sourceLength != token.leadingTriviaLength { + diagnose(.removeEmptyLinesBefore(count), on: token, anchor: .start) + return token.with(\.leadingTrivia, trimmedLeadingTrivia) + } else { + return token + } + } + + func rewritten(_ collection: C) -> C { + var result = collection + if let first = collection.first, first.leadingTrivia.containsNewlines, + let index = collection.index(of: first) + { + let (trimmedLeadingTrivia, count) = first.leadingTrivia.trimmingSuperfluousNewlines() + if trimmedLeadingTrivia.sourceLength != first.leadingTriviaLength { + diagnose(.removeEmptyLinesAfter(count), on: first, anchor: .leadingTrivia(0)) + result[index] = first.with(\.leadingTrivia, trimmedLeadingTrivia) + } + } + return rewrite(result).as(C.self)! + } +} + +extension Trivia { + func trimmingSuperfluousNewlines() -> (Trivia, Int) { + var trimmmed = 0 + let pieces = self.indices.reduce([TriviaPiece]()) { (partialResult, index) in + let piece = self[index] + // Collapse consecutive newlines into a single one + if case .newlines(let count) = piece { + if let last = partialResult.last, last.isNewline { + trimmmed += count + return partialResult + } else { + trimmmed += count - 1 + return partialResult + [.newlines(1)] + } + } + // Remove spaces/tabs surrounded by newlines + if piece.isSpaceOrTab, index > 0, index < self.count - 1, self[index - 1].isNewline, self[index + 1].isNewline { + return partialResult + } + // Retain other trivia pieces + return partialResult + [piece] + } + + return (Trivia(pieces: pieces), trimmmed) + } +} + +extension Finding.Message { + fileprivate static func removeEmptyLinesAfter(_ count: Int) -> Finding.Message { + "remove empty \(count > 1 ? "lines" : "line") after '{'" + } + + fileprivate static func removeEmptyLinesBefore(_ count: Int) -> Finding.Message { + "remove empty \(count > 1 ? "lines" : "line") before '}'" + } +} diff --git a/Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift b/Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift new file mode 100644 index 000000000..294a38210 --- /dev/null +++ b/Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift @@ -0,0 +1,140 @@ +import _SwiftFormatTestSupport + +@_spi(Rules) import SwiftFormat + +final class NoEmptyLinesOpeningClosingBracesTests: LintOrFormatRuleTestCase { + func testNoEmptyLinesOpeningClosingBracesInCodeBlock() { + assertFormatting( + NoEmptyLinesOpeningClosingBraces.self, + input: """ + func f() {1️⃣ + + // + return + + + 2️⃣} + """, + expected: """ + func f() { + // + return + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove empty line after '{'"), + FindingSpec("2️⃣", message: "remove empty lines before '}'"), + ] + ) + } + + func testNoEmptyLinesOpeningClosingBracesInMemberBlock() { + assertFormatting( + NoEmptyLinesOpeningClosingBraces.self, + input: """ + struct {1️⃣ + + let x: Int + + let y: Int + + 2️⃣} + """, + expected: """ + struct { + let x: Int + + let y: Int + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove empty line after '{'"), + FindingSpec("2️⃣", message: "remove empty line before '}'"), + ] + ) + } + + func testNoEmptyLinesOpeningClosingBracesInAccessorBlock() { + assertFormatting( + NoEmptyLinesOpeningClosingBraces.self, + input: """ + var x: Int {1️⃣ + + // + return _x + + 2️⃣} + + var y: Int {3️⃣ + + get 5️⃣{ + + // + return _y + + 6️⃣ } + + set 7️⃣{ + + // + _x = newValue + + 8️⃣ } + + 4️⃣} + """, + expected: """ + var x: Int { + // + return _x + } + + var y: Int { + get { + // + return _y + } + + set { + // + _x = newValue + } + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove empty line after '{'"), + FindingSpec("2️⃣", message: "remove empty line before '}'"), + FindingSpec("3️⃣", message: "remove empty line after '{'"), + FindingSpec("4️⃣", message: "remove empty line before '}'"), + FindingSpec("5️⃣", message: "remove empty line after '{'"), + FindingSpec("6️⃣", message: "remove empty line before '}'"), + FindingSpec("7️⃣", message: "remove empty line after '{'"), + FindingSpec("8️⃣", message: "remove empty line before '}'"), + ] + ) + } + + func testNoEmptyLinesOpeningClosingBracesInClosureExpr() { + assertFormatting( + NoEmptyLinesOpeningClosingBraces.self, + input: """ + let closure = {1️⃣ + + // + return + + 2️⃣} + """, + expected: """ + let closure = { + // + return + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove empty line after '{'"), + FindingSpec("2️⃣", message: "remove empty line before '}'"), + ] + ) + } +} From 3f5d15a9c7a966227c1f52172f55a0138addf8b7 Mon Sep 17 00:00:00 2001 From: TTOzzi Date: Fri, 23 Aug 2024 23:39:36 +0900 Subject: [PATCH 242/332] Fix extraction of trailing comments only when necessary during afterToken addition --- .../PrettyPrint/TokenStreamCreator.swift | 12 ++- .../PrettyPrint/CommaTests.swift | 96 +++++++++++++++++++ 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index dd3dd1929..6e090e402 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -2929,8 +2929,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { var shouldExtractTrailingComment = false if wasLineComment && !hasAppendedTrailingComment { switch afterToken { - case .break, .printerControl: shouldExtractTrailingComment = true - default: break + case let .break(kind, _, _): + if case let .close(mustBreak) = kind { + shouldExtractTrailingComment = mustBreak + } else { + shouldExtractTrailingComment = true + } + case .printerControl: + shouldExtractTrailingComment = true + default: + break } } if shouldExtractTrailingComment { diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift index 4264370c8..70c05dfa7 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift @@ -143,6 +143,102 @@ final class CommaTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } + func testArrayWithCommentCommasPresentEnabled() { + let input = + """ + let MyCollection = [ + 1, + 2 // some comment + ] + + """ + + let expected = + """ + let MyCollection = [ + 1, + 2, // some comment + ] + + """ + + var configuration = Configuration.forTesting + configuration.multiElementCollectionTrailingCommas = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) + } + + func testArrayWithCommentCommasPresentDisabled() { + let input = + """ + let MyCollection = [ + 1, + 2 // some comment + ] + + """ + + let expected = + """ + let MyCollection = [ + 1, + 2 // some comment + ] + + """ + + var configuration = Configuration.forTesting + configuration.multiElementCollectionTrailingCommas = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) + } + + func testArrayWithTernaryOperatorAndCommentCommasPresentEnabled() { + let input = + """ + let MyCollection = [ + 1, + true ? 1 : 2 // some comment + ] + + """ + + let expected = + """ + let MyCollection = [ + 1, + true ? 1 : 2, // some comment + ] + + """ + + var configuration = Configuration.forTesting + configuration.multiElementCollectionTrailingCommas = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) + } + + func testArrayWithTernaryOperatorAndCommentCommasPresentDisabled() { + let input = + """ + let MyCollection = [ + 1, + true ? 1 : 2 // some comment + ] + + """ + + let expected = + """ + let MyCollection = [ + 1, + true ? 1 : 2 // some comment + ] + + """ + + var configuration = Configuration.forTesting + configuration.multiElementCollectionTrailingCommas = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) + } + func testDictionaryCommasAbsentEnabled() { let input = """ From 8411c075a8ce8ab35a749d8932b5036326736af1 Mon Sep 17 00:00:00 2001 From: TTOzzi Date: Sun, 25 Aug 2024 01:08:14 +0900 Subject: [PATCH 243/332] Fix to arrange attributeList for attributes starting with ifConfig --- .../PrettyPrint/TokenStreamCreator.swift | 25 +++++++++++-------- .../PrettyPrint/AttributeTests.swift | 23 +++++++++++++++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index dd3dd1929..e6eb43042 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -2957,19 +2957,24 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let attributes = attributes { let behavior: NewlineBehavior = separateByLineBreaks ? .hard : .elective before(attributes.firstToken(viewMode: .sourceAccurate), tokens: .open) - for element in attributes.dropLast() { - if let ifConfig = element.as(IfConfigDeclSyntax.self) { + if attributes.dropLast().isEmpty, + let ifConfig = attributes.first?.as(IfConfigDeclSyntax.self) { + for clause in ifConfig.clauses { + if let nestedAttributes = AttributeListSyntax(clause.elements) { + arrangeAttributeList(nestedAttributes, suppressFinalBreak: true, separateByLineBreaks: separateByLineBreaks) + } + } + } else { + for element in attributes.dropLast() { + if let ifConfig = element.as(IfConfigDeclSyntax.self) { for clause in ifConfig.clauses { - if let nestedAttributes = AttributeListSyntax(clause.elements) { - arrangeAttributeList( - nestedAttributes, - suppressFinalBreak: true, - separateByLineBreaks: separateByLineBreaks - ) - } + if let nestedAttributes = AttributeListSyntax(clause.elements) { + arrangeAttributeList(nestedAttributes, suppressFinalBreak: true, separateByLineBreaks: separateByLineBreaks) + } } - } else { + } else { after(element.lastToken(viewMode: .sourceAccurate), tokens: .break(.same, newlines: behavior)) + } } } var afterAttributeTokens = [Token.close] diff --git a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift index c16950390..668df4de2 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift @@ -576,4 +576,27 @@ final class AttributeTests: PrettyPrintTestCase { configuration.lineBreakBetweenDeclarationAttributes = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: configuration) } + + func testAttributesStartWithPoundIf() { + let input = + """ + #if os(macOS) + @available(macOS, unavailable) + @_spi(Foo) + #endif + public let myVar = "Test" + + """ + let expected = + """ + #if os(macOS) + @available(macOS, unavailable) + @_spi(Foo) + #endif + public let myVar = "Test" + + """ + + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) + } } From a1ee216dcbfbe2dd06f7f3b0ea99e91106a6af5f Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Fri, 6 Sep 2024 07:49:48 -0400 Subject: [PATCH 244/332] Fix infinite loop on Windows caused by checking path against "/". (#802) --- Sources/SwiftFormat/API/Configuration.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index c6836ab8a..531461a57 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -410,12 +410,17 @@ public struct Configuration: Codable, Equatable { candidateDirectory.appendPathComponent("placeholder") } repeat { + let previousDirectory = candidateDirectory candidateDirectory.deleteLastPathComponent() + // if deleting a path component resulted in no change, terminate the loop + if candidateDirectory == previousDirectory { + break + } let candidateFile = candidateDirectory.appendingPathComponent(".swift-format") if FileManager.default.isReadableFile(atPath: candidateFile.path) { return candidateFile } - } while candidateDirectory.path != "/" + } while true return nil } From 08cc71fab507673150ba0fc20311ab05fde24143 Mon Sep 17 00:00:00 2001 From: Mattia Valzelli Date: Tue, 10 Sep 2024 18:08:53 +0200 Subject: [PATCH 245/332] Fix link to configuration in rule documentation file --- Documentation/RuleDocumentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index ecb376ec1..f0d7e6b2a 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -4,7 +4,7 @@ Use the rules below in the `rules` block of your `.swift-format` configuration file, as described in -[Configuration](Documentation/Configuration.md). All of these rules can be +[Configuration](Configuration.md). All of these rules can be applied in the linter, but only some of them can format your source code automatically. From 34d1bbf3b0b0d0a16ca2951dd55f61099c2e1bc6 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 27 Sep 2024 21:27:25 -0700 Subject: [PATCH 246/332] [CI] GitHub Actions support for Linux platform --- .github/workflows/pull_request.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/pull_request.yml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 000000000..1de636c5f --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,10 @@ +name: pull_request + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + tests: + name: tests + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main From ab48e0f4e89c0b791315a54ebf66356faffc8665 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 27 Sep 2024 21:32:21 -0700 Subject: [PATCH 247/332] Add CODEOWNERS file --- CODEOWNERS | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..fdeb9b7ac --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,14 @@ +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2024 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See https://swift.org/LICENSE.txt for license information +# See https://swift.org/CONTRIBUTORS.txt for Swift project authors +# + +* @ahoppen @allevato @bnbarham @shawnhyam + +.github/ @shahmishal +.swiftci/ @shahmishal From 1f01ae4da9daa377ddaf67b302207f6b275686c7 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 29 Sep 2024 08:18:32 -0700 Subject: [PATCH 248/332] Use human-readable names in pull_request action and consistently use 2 spaces for indentation --- .github/workflows/pull_request.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 1de636c5f..5ca456bdf 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,10 +1,10 @@ -name: pull_request +name: Pull request on: - pull_request: - types: [opened, reopened, synchronize] + pull_request: + types: [opened, reopened, synchronize] jobs: - tests: - name: tests - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + tests: + name: Test + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main From a29f7e61640f3b1f8f6f5873f31e5c9491fecdb0 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sat, 28 Sep 2024 22:08:57 -0700 Subject: [PATCH 249/332] Create a GitHub action to create a release This should replace the script that I have locally to test swift-format for a release and at the same time expand the configurations that are being tested. --- .github/workflows/create-release-commits.sh | 28 +++++ .github/workflows/publish_release.yml | 108 ++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100755 .github/workflows/create-release-commits.sh create mode 100644 .github/workflows/publish_release.yml diff --git a/.github/workflows/create-release-commits.sh b/.github/workflows/create-release-commits.sh new file mode 100755 index 000000000..ebde30ed8 --- /dev/null +++ b/.github/workflows/create-release-commits.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +set -ex + +SWIFT_SYNTAX_TAG="$1" +SWIFT_FORMAT_VERSION="$2" + +if [[ -z "$SWIFT_SYNTAX_TAG" || -z "$SWIFT_FORMAT_VERSION" ]]; then + echo "Update the Package manifest to reference a specific version of swift-syntax and embed the given version in the swift-format --version command" + echo "Usage create-release-commits.sh " + echo " SWIFT_SYNTAX_TAG: The tag of swift-syntax to depend on" + echo " SWIFT_FORMAT_VERSION: The version of swift-format that is about to be released" + exit 1 +fi + +# Without this, we can't perform git operations in GitHub actions. +git config --global --add safe.directory $(realpath .) + +git config --local user.name 'swift-ci' +git config --local user.email 'swift-ci@users.noreply.github.com' + +sed -E -i "s#branch: \"(main|release/[0-9]+\.[0-9]+)\"#from: \"$SWIFT_SYNTAX_TAG\"#" Package.swift +git add Package.swift +git commit -m "Change swift-syntax dependency to $SWIFT_SYNTAX_TAG" + +sed -E -i "s#print\(\".*\"\)#print\(\"$SWIFT_FORMAT_VERSION\"\)#" Sources/swift-format/PrintVersion.swift +git add Sources/swift-format/PrintVersion.swift +git commit -m "Change version to $SWIFT_FORMAT_VERSION" diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml new file mode 100644 index 000000000..505a79329 --- /dev/null +++ b/.github/workflows/publish_release.yml @@ -0,0 +1,108 @@ +name: Publish Release + +on: + workflow_dispatch: + inputs: + prerelease: + type: boolean + description: "Prerelease" + # Whether to create a prerelease or proper release + default: true + required: true + swift_format_version: + type: string + default: 601.0.0 + description: "swift-format version" + # The version of swift-format to tag. If this is a prerelease, `-prerelease-` is added to this version. + required: true + swift_syntax_tag: + type: string + default: 601.0.0 + description: "swift-syntax version" + # The swift-syntax version to depend on. If this is a prerelease, the latest swift-syntax prerelease tag for this version is used. + required: true + +jobs: + check_triggering_actor: + name: Check user is allowed to create release + # Only a single user should be allowed to create releases to avoid two people triggering the creation of a release + # at the same time. If the release manager changes between users, update this condition. + runs-on: ubuntu-latest + steps: + - run: | + if [[ "${{ github.triggering_actor }}" != "ahoppen" ]]; then + echo "${{ github.triggering_actor }} is not allowed to create a release" + exit 1 + fi + define_tags: + name: Determine dependent swift-syntax version and prerelease date + runs-on: ubuntu-latest + outputs: + swift_syntax_tag: ${{ steps.swift_syntax_tag.outputs.swift_syntax_tag }} + swift_format_version: ${{ steps.swift_format_version.outputs.swift_format_version }} + steps: + - name: Determine swift-syntax tag to depend on + id: swift_syntax_tag + shell: bash + run: | + if [[ "${{ github.event.inputs.prerelease }}" == "false" ]]; then + SWIFT_SYNTAX_TAG="${{ github.event.inputs.swift_syntax_tag }}" + else + git clone https://github.com/swiftlang/swift-syntax.git + cd swift-syntax + SWIFT_SYNTAX_TAG="$(git tag | grep ${{ github.event.inputs.swift_syntax_tag }}-prerelease | sort -r | head -1)" + fi + + echo "Using swift-syntax tag: $SWIFT_SYNTAX_TAG" + echo "swift_syntax_tag=$SWIFT_SYNTAX_TAG" >> "$GITHUB_OUTPUT" + - name: Determine swift-format prerelease version + id: swift_format_version + run: | + if [[ "${{ github.event.inputs.prerelease }}" == "false" ]]; then + SWIFT_FORMAT_VERSION="${{ github.event.inputs.swift_format_version }}" + else + SWIFT_FORMAT_VERSION="${{ github.event.inputs.swift_format_version }}-prerelease-$(date +'%Y-%m-%d')" + fi + echo "Using swift-format version: $SWIFT_FORMAT_VERSION" + echo "swift_format_version=$SWIFT_FORMAT_VERSION" >> "$GITHUB_OUTPUT" + test_debug: + name: Test in Debug configuration + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + needs: define_tags + with: + pre_build_command: bash .github/workflows/create-release-commits.sh '${{ needs.define_tags.outputs.swift_syntax_tag }}' '${{ needs.define_tags.outputs.swift_format_version }}' + # We require that releases of swift-format build without warnings + build_command: swift test -Xswiftc -warnings-as-errors + test_release: + name: Test in Release configuration + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + needs: define_tags + with: + pre_build_command: bash .github/workflows/create-release-commits.sh '${{ needs.define_tags.outputs.swift_syntax_tag }}' '${{ needs.define_tags.outputs.swift_format_version }}' + # We require that releases of swift-format build without warnings + build_command: swift test -c release -Xswiftc -warnings-as-errors + create_tag: + name: Create Tag + runs-on: ubuntu-latest + needs: [check_triggering_actor, test_debug, test_release, define_tags] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create release commits + run: bash .github/workflows/create-release-commits.sh '${{ needs.define_tags.outputs.swift_syntax_tag }}' '${{ needs.define_tags.outputs.swift_format_version }}' + - name: Tag release + run: | + git tag "${{ needs.define_tags.outputs.swift_format_version }}" + git push origin "${{ needs.define_tags.outputs.swift_format_version }}" + - name: Create release + env: + GH_TOKEN: ${{ github.token }} + run: | + if [[ "${{ github.event.inputs.prerelease }}" != "true" ]]; then + # Only create a release automatically for prereleases. For real releases, release notes should be crafted by hand. + exit + fi + gh release create "${{ needs.define_tags.outputs.swift_format_version }}" \ + --title "${{ needs.define_tags.outputs.swift_format_version }}" \ + --prerelease + From d2b01fc52c665fbc4ca039e99a94b7348c344c48 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 29 Sep 2024 08:12:39 -0700 Subject: [PATCH 250/332] Fix build warning: `eachKeyword` was renamed to `specifier` --- Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index c3709c60c..a8298eed5 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -2369,7 +2369,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.specifier, tokens: .break) after(node.colon, tokens: .break) if let trailingComma = node.trailingComma { after(trailingComma, tokens: .close, .break(.same)) From acc3c6c933b6564c4d4a640ccd02a911976b7513 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 29 Sep 2024 08:55:40 -0700 Subject: [PATCH 251/332] =?UTF-8?q?Don=E2=80=99t=20discard=20the=20return?= =?UTF-8?q?=20value=20of=20`FileManager.createFile`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/generate-swift-format/FileGenerator.swift | 8 +++++++- Tests/swift-formatTests/Utilities/FileIteratorTests.swift | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Sources/generate-swift-format/FileGenerator.swift b/Sources/generate-swift-format/FileGenerator.swift index 85a8861bc..c4ba1f553 100644 --- a/Sources/generate-swift-format/FileGenerator.swift +++ b/Sources/generate-swift-format/FileGenerator.swift @@ -19,6 +19,10 @@ protocol FileGenerator { func write(into handle: FileHandle) throws } +private struct FailedToCreateFileError: Error { + let url: URL +} + extension FileGenerator { /// Generates a file at the given URL, overwriting it if it already exists. func generateFile(at url: URL) throws { @@ -27,7 +31,9 @@ extension FileGenerator { try fm.removeItem(at: url) } - fm.createFile(atPath: url.path, contents: nil, attributes: nil) + if !fm.createFile(atPath: url.path, contents: nil, attributes: nil) { + throw FailedToCreateFileError(url: url) + } let handle = try FileHandle(forWritingTo: url) defer { handle.closeFile() } diff --git a/Tests/swift-formatTests/Utilities/FileIteratorTests.swift b/Tests/swift-formatTests/Utilities/FileIteratorTests.swift index 5bfe5bb18..868758bbe 100644 --- a/Tests/swift-formatTests/Utilities/FileIteratorTests.swift +++ b/Tests/swift-formatTests/Utilities/FileIteratorTests.swift @@ -70,7 +70,12 @@ extension FileIteratorTests { let fileURL = tmpURL(path) try FileManager.default.createDirectory( at: fileURL.deletingLastPathComponent(), withIntermediateDirectories: true) - FileManager.default.createFile(atPath: fileURL.path, contents: Data()) + struct FailedToCreateFileError: Error { + let url: URL + } + if !FileManager.default.createFile(atPath: fileURL.path, contents: Data()) { + throw FailedToCreateFileError(url: fileURL) + } } /// Create a symlink between files or directories in the test's temporary space. From 226601a08b844c8e136c783ef59c0c8ff7e54f6e Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 30 Sep 2024 12:12:02 -0700 Subject: [PATCH 252/332] Replace `Sanity` by `Basic` --- .../PrettyPrint/RespectsExistingLineBreaksTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift b/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift index 86c5e3cde..65226cf5e 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift @@ -1,6 +1,6 @@ import SwiftFormat -/// Sanity checks and regression tests for the `respectsExistingLineBreaks` configuration setting +/// Basic checks and regression tests for the `respectsExistingLineBreaks` configuration setting /// in both true and false states. final class RespectsExistingLineBreaksTests: PrettyPrintTestCase { func testExpressions() { From 77edc435527a7be49070c48b5a3457c20aae5635 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 30 Sep 2024 12:12:12 -0700 Subject: [PATCH 253/332] Quote `$(realpath .)` --- .github/workflows/create-release-commits.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create-release-commits.sh b/.github/workflows/create-release-commits.sh index ebde30ed8..81fa8eba9 100755 --- a/.github/workflows/create-release-commits.sh +++ b/.github/workflows/create-release-commits.sh @@ -14,7 +14,7 @@ if [[ -z "$SWIFT_SYNTAX_TAG" || -z "$SWIFT_FORMAT_VERSION" ]]; then fi # Without this, we can't perform git operations in GitHub actions. -git config --global --add safe.directory $(realpath .) +git config --global --add safe.directory "$(realpath .)" git config --local user.name 'swift-ci' git config --local user.email 'swift-ci@users.noreply.github.com' From abff1814246de8ec865b65dd49209dd057be4b67 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 30 Sep 2024 16:20:14 -0700 Subject: [PATCH 254/332] Move tests in `swift-formatTests` to `SwiftFormatTests` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows doesn’t support test targets that depend on executables. Move `FileIterator` into `SwiftFormat` so we can test it as part of `SwiftFormatTests` and can remove `swift-formatTests`. --- Package.swift | 6 +----- Sources/SwiftFormat/CMakeLists.txt | 3 ++- .../Utilities/FileIterator.swift | 2 +- Sources/swift-format/CMakeLists.txt | 1 - .../Utilities/FileIteratorTests.swift | 2 +- 5 files changed, 5 insertions(+), 9 deletions(-) rename Sources/{swift-format => SwiftFormat}/Utilities/FileIterator.swift (99%) rename Tests/{swift-formatTests => SwiftFormatTests}/Utilities/FileIteratorTests.swift (99%) diff --git a/Package.swift b/Package.swift index 18c697e77..4ee03be04 100644 --- a/Package.swift +++ b/Package.swift @@ -131,11 +131,7 @@ let package = Package( .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ] - ), - .testTarget( - name: "swift-formatTests", - dependencies: ["swift-format"] - ), + ) ] ) diff --git a/Sources/SwiftFormat/CMakeLists.txt b/Sources/SwiftFormat/CMakeLists.txt index 471cfa719..62c2d4df8 100644 --- a/Sources/SwiftFormat/CMakeLists.txt +++ b/Sources/SwiftFormat/CMakeLists.txt @@ -96,7 +96,8 @@ add_library(SwiftFormat Rules/UseSynthesizedInitializer.swift Rules/UseTripleSlashForDocumentationComments.swift Rules/UseWhereClausesInForLoops.swift - Rules/ValidateDocumentationComments.swift) + Rules/ValidateDocumentationComments.swift + Utilities/FileIterator.swift) target_link_libraries(SwiftFormat PUBLIC SwiftMarkdown::Markdown SwiftSyntax::SwiftSyntax diff --git a/Sources/swift-format/Utilities/FileIterator.swift b/Sources/SwiftFormat/Utilities/FileIterator.swift similarity index 99% rename from Sources/swift-format/Utilities/FileIterator.swift rename to Sources/SwiftFormat/Utilities/FileIterator.swift index a3b2a6984..86f099cfc 100644 --- a/Sources/swift-format/Utilities/FileIterator.swift +++ b/Sources/SwiftFormat/Utilities/FileIterator.swift @@ -14,7 +14,7 @@ import Foundation /// Iterator for looping over lists of files and directories. Directories are automatically /// traversed recursively, and we check for files with a ".swift" extension. -@_spi(Testing) +@_spi(Internal) public struct FileIterator: Sequence, IteratorProtocol { /// List of file and directory URLs to iterate over. diff --git a/Sources/swift-format/CMakeLists.txt b/Sources/swift-format/CMakeLists.txt index 982e93d71..9ae9603e1 100644 --- a/Sources/swift-format/CMakeLists.txt +++ b/Sources/swift-format/CMakeLists.txt @@ -23,7 +23,6 @@ add_executable(swift-format Utilities/Diagnostic.swift Utilities/DiagnosticsEngine.swift Utilities/FileHandleTextOutputStream.swift - Utilities/FileIterator.swift Utilities/FormatError.swift Utilities/StderrDiagnosticPrinter.swift Utilities/TTY.swift) diff --git a/Tests/swift-formatTests/Utilities/FileIteratorTests.swift b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift similarity index 99% rename from Tests/swift-formatTests/Utilities/FileIteratorTests.swift rename to Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift index 868758bbe..2f556e629 100644 --- a/Tests/swift-formatTests/Utilities/FileIteratorTests.swift +++ b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift @@ -1,6 +1,6 @@ import XCTest -@_spi(Testing) import swift_format +@_spi(Internal) import SwiftFormat final class FileIteratorTests: XCTestCase { private var tmpdir: URL! From 1e0fd86df6a06a020d35e68a01b079bddd217266 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Mon, 30 Sep 2024 16:21:10 -0700 Subject: [PATCH 255/332] [CI] Add soundness check (#824) --- .github/workflows/pull_request.yml | 7 +++++++ Documentation/PrettyPrinter.md | 2 +- Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 5ca456bdf..b95dcd4fa 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -8,3 +8,10 @@ jobs: tests: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + soundness: + name: Soundness + uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main + with: + format_check_enabled: false + license_header_check_enabled: false + license_header_check_project_name: "Swift.org" diff --git a/Documentation/PrettyPrinter.md b/Documentation/PrettyPrinter.md index 2883d65a5..7a62bd835 100644 --- a/Documentation/PrettyPrinter.md +++ b/Documentation/PrettyPrinter.md @@ -490,6 +490,6 @@ sense to place this label on the containing group. Oppen's algorithm prints the indentation whitespace when `break` tokens are encountered. If we have extra blank lines in between source code, this can -result in hanging or trailing whitespace. Waiting to print the indentation +result in hanging or trailing whitespace. Waiting to print the indentation whitespace until encountering a `syntax`, `comment, or `verbatim` tokens prevents this. diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index a8298eed5..7691b60be 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -1104,7 +1104,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// - rightDelimiter: The right parenthesis or bracket surrounding the arguments, if any. /// - forcesBreakBeforeRightDelimiter: True if a line break should be forced before the right /// right delimiter if a line break occurred after the left delimiter, or false if the right - /// delimiter is allowed to hang on the same line as the final argument. + /// delimiter is allowed to hang on the same line as the final argument. # ignore-unacceptable-language private func arrangeFunctionCallArgumentList( _ arguments: LabeledExprListSyntax, leftDelimiter: TokenSyntax?, From c1f06b8b00e5f0387f92d9cc48afa26cb1e5c9f3 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 30 Sep 2024 16:58:34 -0700 Subject: [PATCH 256/332] Allow the `Publish Release` action to create a tag and release --- .github/workflows/publish_release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 505a79329..481093b36 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -85,6 +85,8 @@ jobs: name: Create Tag runs-on: ubuntu-latest needs: [check_triggering_actor, test_debug, test_release, define_tags] + permissions: + contents: write steps: - name: Checkout repository uses: actions/checkout@v4 From f94d502da61a6851b0e28fea66d9b1b6f11c501d Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 30 Sep 2024 22:20:21 -0700 Subject: [PATCH 257/332] Skip files and directories starting with `.` on Windows Windows does not consider files and directories starting with `.` as hidden but we don't want to traverse into eg. `.build`. Manually skip any items starting with `.`. --- Sources/SwiftFormat/Utilities/FileIterator.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/SwiftFormat/Utilities/FileIterator.swift b/Sources/SwiftFormat/Utilities/FileIterator.swift index 86f099cfc..d274d71c3 100644 --- a/Sources/SwiftFormat/Utilities/FileIterator.swift +++ b/Sources/SwiftFormat/Utilities/FileIterator.swift @@ -113,6 +113,14 @@ public struct FileIterator: Sequence, IteratorProtocol { guard let item = dirIterator?.nextObject() as? URL else { break } + #if os(Windows) + // Windows does not consider files and directories starting with `.` as hidden but we don't want to traverse + // into eg. `.build`. Manually skip any items starting with `.`. + if item.lastPathComponent.hasPrefix(".") { + dirIterator?.skipDescendants() + continue + } + #endif guard item.lastPathComponent.hasSuffix(fileSuffix), let fileType = fileType(at: item) else { continue From 6452eff916eca4f89d2216fc13a771b49896f62e Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 1 Oct 2024 08:16:03 -0400 Subject: [PATCH 258/332] Bring over swift-syntax's formatter config into swift-format. --- .swift-format | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .swift-format diff --git a/.swift-format b/.swift-format new file mode 100644 index 000000000..41a022f26 --- /dev/null +++ b/.swift-format @@ -0,0 +1,18 @@ +{ + "version": 1, + "lineLength": 120, + "indentation": { + "spaces": 2 + }, + "lineBreakBeforeEachArgument": true, + "indentConditionalCompilationBlocks": false, + "prioritizeKeepingFunctionOutputTogether": true, + "rules": { + "AlwaysUseLowerCamelCase": false, + "AmbiguousTrailingClosureOverload": false, + "NoBlockComments": false, + "OrderedImports": true, + "UseLetInEveryBoundCaseVariable": false, + "UseSynthesizedInitializer": false + } +} From f0eabf80a43832045bd1b5692ca7cadbf38759e9 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 1 Oct 2024 08:27:46 -0400 Subject: [PATCH 259/332] Run swift-format on swift-format. --- Package.swift | 9 +- Plugins/FormatPlugin/plugin.swift | 34 +- Plugins/LintPlugin/plugin.swift | 30 +- Sources/SwiftFormat/API/Configuration.swift | 39 +- Sources/SwiftFormat/API/Indent.swift | 8 +- Sources/SwiftFormat/API/Selection.swift | 3 +- Sources/SwiftFormat/API/SwiftFormatter.swift | 38 +- Sources/SwiftFormat/API/SwiftLinter.swift | 25 +- Sources/SwiftFormat/Core/Context.swift | 5 +- .../Core/DocumentationComment.swift | 5 +- .../Core/DocumentationCommentText.swift | 25 +- Sources/SwiftFormat/Core/FindingEmitter.swift | 4 +- Sources/SwiftFormat/Core/LintPipeline.swift | 9 +- .../Core/ModifierListSyntax+Convenience.swift | 2 +- Sources/SwiftFormat/Core/Rule.swift | 11 +- Sources/SwiftFormat/Core/RuleMask.swift | 12 +- .../Core/SyntaxProtocol+Convenience.swift | 3 +- .../Core/WithSemicolonSyntax.swift | 1 - .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 47 ++- .../PrettyPrint/TokenStreamCreator.swift | 346 +++++++++++------- .../PrettyPrint/WhitespaceLinter.swift | 52 ++- ...waysUseLiteralForEmptyCollectionInit.swift | 56 ++- .../Rules/AlwaysUseLowerCamelCase.swift | 53 ++- .../AmbiguousTrailingClosureOverload.swift | 6 +- ...cumentationCommentWithOneLineSummary.swift | 93 ++--- .../Rules/FileScopedDeclarationPrivacy.swift | 6 +- .../SwiftFormat/Rules/FullyIndirectEnum.swift | 30 +- .../Rules/IdentifiersMustBeASCII.swift | 3 +- ...NeverUseImplicitlyUnwrappedOptionals.swift | 4 +- .../NoAccessLevelOnExtensionDeclaration.swift | 32 +- .../Rules/NoAssignmentInExpressions.swift | 7 +- .../NoEmptyLineOpeningClosingBraces.swift | 30 +- .../NoVoidReturnOnFunctionSignature.swift | 6 +- .../Rules/OmitExplicitReturns.swift | 48 ++- .../Rules/OneVariableDeclarationPerLine.swift | 4 +- .../SwiftFormat/Rules/OrderedImports.swift | 13 +- .../Rules/ReplaceForEachWithForLoop.swift | 5 +- .../Rules/ReturnVoidInsteadOfEmptyTuple.swift | 15 +- .../Rules/TypeNamesShouldBeCapitalized.swift | 5 +- Sources/SwiftFormat/Rules/UseEarlyExits.swift | 6 +- .../UseExplicitNilCheckInConditions.swift | 6 +- .../Rules/UseShorthandTypeNames.swift | 98 +++-- .../Rules/UseSynthesizedInitializer.swift | 23 +- .../Rules/UseWhereClausesInForLoops.swift | 5 +- .../Rules/ValidateDocumentationComments.swift | 23 +- .../SwiftFormat/Utilities/FileIterator.swift | 5 +- .../DiagnosingTestCase.swift | 30 +- .../_SwiftFormatTestSupport/MarkedText.swift | 4 +- .../PipelineGenerator.swift | 42 ++- .../generate-swift-format/RuleCollector.swift | 8 +- .../RuleDocumentationGenerator.swift | 25 +- .../RuleNameCacheGenerator.swift | 1 - Sources/generate-swift-format/main.swift | 15 +- .../Frontend/FormatFrontend.swift | 12 +- Sources/swift-format/Frontend/Frontend.swift | 34 +- .../swift-format/Frontend/LintFrontend.swift | 23 +- .../Subcommands/DumpConfiguration.swift | 6 +- Sources/swift-format/Subcommands/Format.swift | 6 +- Sources/swift-format/Subcommands/Lint.swift | 7 +- .../Subcommands/LintFormatOptions.swift | 41 ++- .../Utilities/DiagnosticsEngine.swift | 22 +- .../swift-format/Utilities/FormatError.swift | 3 +- Sources/swift-format/Utilities/TTY.swift | 14 +- .../WhitespaceLinterPerformanceTests.swift | 12 +- .../Core/DocumentationCommentTests.swift | 25 +- .../Core/DocumentationCommentTextTests.swift | 35 +- .../SwiftFormatTests/Core/RuleMaskTests.swift | 5 +- .../PrettyPrint/ArrayDeclTests.swift | 32 +- .../PrettyPrint/AssignmentExprTests.swift | 176 +++++---- .../PrettyPrint/AttributeTests.swift | 102 +++--- .../PrettyPrint/BinaryOperatorExprTests.swift | 12 +- .../PrettyPrint/ClassDeclTests.swift | 12 +- .../PrettyPrint/ClosureExprTests.swift | 10 +- .../PrettyPrint/CommaTests.swift | 148 ++++---- .../PrettyPrint/CommentTests.swift | 250 ++++++------- .../PrettyPrint/ConsumeExprTests.swift | 3 +- .../PrettyPrint/CopyExprSyntax.swift | 3 +- .../PrettyPrint/DeclNameArgumentTests.swift | 1 - .../PrettyPrint/DeinitializerDeclTests.swift | 2 +- .../PrettyPrint/DictionaryDeclTests.swift | 36 +- .../PrettyPrint/DiscardStmtTests.swift | 3 +- .../PrettyPrint/DoStmtTests.swift | 35 +- .../PrettyPrint/EnumDeclTests.swift | 12 +- .../PrettyPrint/ExtensionDeclTests.swift | 12 +- .../PrettyPrint/FunctionDeclTests.swift | 215 ++++++----- .../PrettyPrint/IfConfigTests.swift | 2 +- .../PrettyPrint/IgnoreNodeTests.swift | 2 +- .../PrettyPrint/InitializerDeclTests.swift | 192 +++++----- .../PrettyPrint/MacroCallTests.swift | 2 +- .../PrettyPrint/MemberAccessExprTests.swift | 69 ++-- .../PrettyPrint/ParameterPackTests.swift | 9 +- .../PrettyPrint/PrettyPrintTestCase.swift | 33 +- .../PrettyPrint/ProtocolDeclTests.swift | 9 +- .../RespectsExistingLineBreaksTests.swift | 56 ++- .../PrettyPrint/SemicolonTests.swift | 20 +- .../PrettyPrint/StringTests.swift | 99 ++--- .../PrettyPrint/StructDeclTests.swift | 8 +- .../PrettyPrint/SubscriptDeclTests.swift | 242 ++++++------ .../PrettyPrint/SubscriptExprTests.swift | 4 +- .../PrettyPrint/SwitchStmtTests.swift | 7 +- .../PrettyPrint/WhitespaceLintTests.swift | 2 +- .../PrettyPrint/WhitespaceTestCase.swift | 16 +- ...icDeclarationsHaveDocumentationTests.swift | 23 +- ...seLiteralForEmptyCollectionInitTests.swift | 3 +- .../Rules/AlwaysUseLowerCamelCaseTests.swift | 10 +- ...mbiguousTrailingClosureOverloadTests.swift | 19 +- .../AvoidRetroactiveConformancesTests.swift | 5 +- ...tationCommentWithOneLineSummaryTests.swift | 119 +++--- .../Rules/DoNotUseSemicolonsTests.swift | 15 +- ...ontRepeatTypeInStaticPropertiesTests.swift | 22 +- .../FileScopedDeclarationPrivacyTests.swift | 12 +- .../Rules/FullyIndirectEnumTests.swift | 7 +- .../Rules/GroupNumericLiteralsTests.swift | 3 +- .../Rules/IdentifiersMustBeASCIITests.swift | 3 +- .../Rules/ImportsXCTestVisitorTests.swift | 48 ++- .../Rules/LintOrFormatRuleTestCase.swift | 47 ++- .../Rules/NeverForceUnwrapTests.swift | 3 +- .../Rules/NeverUseForceTryTests.swift | 5 +- ...UseImplicitlyUnwrappedOptionalsTests.swift | 3 +- ...cessLevelOnExtensionDeclarationTests.swift | 24 +- .../NoAssignmentInExpressionsTests.swift | 19 +- .../Rules/NoBlockCommentsTests.swift | 3 +- .../NoCasesWithOnlyFallthroughTests.swift | 7 +- ...oEmptyLinesOpeningClosingBracesTests.swift | 157 ++++---- ...EmptyTrailingClosureParenthesesTests.swift | 3 +- .../Rules/NoLabelsInCasePatternsTests.swift | 3 +- .../Rules/NoLeadingUnderscoresTests.swift | 7 +- .../Rules/NoParensAroundConditionsTests.swift | 3 +- .../Rules/NoPlaygroundLiteralsTests.swift | 18 +- ...NoVoidReturnOnFunctionSignatureTests.swift | 3 +- .../Rules/OmitReturnsTests.swift | 135 +++---- .../Rules/OneCasePerLineTests.swift | 5 +- .../OneVariableDeclarationPerLineTests.swift | 9 +- .../OnlyOneTrailingClosureArgumentTests.swift | 8 +- .../Rules/OrderedImportsTests.swift | 25 +- .../ReplaceForEachWithForLoopTests.swift | 6 +- .../ReturnVoidInsteadOfEmptyTupleTests.swift | 3 +- .../TypeNamesShouldBeCapitalizedTests.swift | 8 +- .../Rules/UseEarlyExitsTests.swift | 7 +- ...UseExplicitNilCheckInConditionsTests.swift | 5 +- .../UseLetInEveryBoundCaseVariableTests.swift | 128 +++++-- .../Rules/UseShorthandTypeNamesTests.swift | 5 +- .../UseSingleLinePropertyGetterTests.swift | 8 +- .../UseSynthesizedInitializerTests.swift | 48 ++- ...leSlashForDocumentationCommentsTests.swift | 5 +- .../UseWhereClausesInForLoopsTests.swift | 3 +- .../ValidateDocumentationCommentsTests.swift | 19 +- .../Utilities/FileIteratorTests.swift | 17 +- 148 files changed, 2593 insertions(+), 1941 deletions(-) diff --git a/Package.swift b/Package.swift index 4ee03be04..d8c46cb4f 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,7 @@ let package = Package( name: "swift-format", platforms: [ .macOS("12.0"), - .iOS("13.0") + .iOS("13.0"), ], products: [ .executable( @@ -131,7 +131,7 @@ let package = Package( .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ] - ) + ), ] ) @@ -167,15 +167,12 @@ var dependencies: [Package.Dependency] { } } - - // MARK: - Compute custom build settings -var swiftformatLinkSettings: [LinkerSetting] { +var swiftformatLinkSettings: [LinkerSetting] { if installAction { return [.unsafeFlags(["-no-toolchain-stdlib-rpath"], .when(platforms: [.linux, .android]))] } else { return [] } } - diff --git a/Plugins/FormatPlugin/plugin.swift b/Plugins/FormatPlugin/plugin.swift index 96c77f4d2..66bf3b74e 100644 --- a/Plugins/FormatPlugin/plugin.swift +++ b/Plugins/FormatPlugin/plugin.swift @@ -1,28 +1,27 @@ -import PackagePlugin import Foundation +import PackagePlugin @main struct FormatPlugin { func format(tool: PluginContext.Tool, targetDirectories: [String], configurationFilePath: String?) throws { let swiftFormatExec = URL(fileURLWithPath: tool.path.string) - + var arguments: [String] = ["format"] - + arguments.append(contentsOf: targetDirectories) - + arguments.append(contentsOf: ["--recursive", "--parallel", "--in-place"]) - + if let configurationFilePath = configurationFilePath { arguments.append(contentsOf: ["--configuration", configurationFilePath]) } - + let process = try Process.run(swiftFormatExec, arguments: arguments) process.waitUntilExit() - + if process.terminationReason == .exit && process.terminationStatus == 0 { print("Formatted the source code.") - } - else { + } else { let problem = "\(process.terminationReason):\(process.terminationStatus)" Diagnostics.error("swift-format invocation failed: \(problem)") } @@ -35,15 +34,16 @@ extension FormatPlugin: CommandPlugin { arguments: [String] ) async throws { let swiftFormatTool = try context.tool(named: "swift-format") - + var argExtractor = ArgumentExtractor(arguments) let targetNames = argExtractor.extractOption(named: "target") - let targetsToFormat = targetNames.isEmpty ? context.package.targets : try context.package.targets(named: targetNames) - + let targetsToFormat = + targetNames.isEmpty ? context.package.targets : try context.package.targets(named: targetNames) + let configurationFilePath = argExtractor.extractOption(named: "swift-format-configuration").first - - let sourceCodeTargets = targetsToFormat.compactMap{ $0 as? SourceModuleTarget } - + + let sourceCodeTargets = targetsToFormat.compactMap { $0 as? SourceModuleTarget } + try format( tool: swiftFormatTool, targetDirectories: sourceCodeTargets.map(\.directory.string), @@ -58,10 +58,10 @@ import XcodeProjectPlugin extension FormatPlugin: XcodeCommandPlugin { func performCommand(context: XcodeProjectPlugin.XcodePluginContext, arguments: [String]) throws { let swiftFormatTool = try context.tool(named: "swift-format") - + var argExtractor = ArgumentExtractor(arguments) let configurationFilePath = argExtractor.extractOption(named: "swift-format-configuration").first - + try format( tool: swiftFormatTool, targetDirectories: [context.xcodeProject.directory.string], diff --git a/Plugins/LintPlugin/plugin.swift b/Plugins/LintPlugin/plugin.swift index 34644f6db..bb59c34a1 100644 --- a/Plugins/LintPlugin/plugin.swift +++ b/Plugins/LintPlugin/plugin.swift @@ -1,28 +1,27 @@ -import PackagePlugin import Foundation +import PackagePlugin @main struct LintPlugin { func lint(tool: PluginContext.Tool, targetDirectories: [String], configurationFilePath: String?) throws { let swiftFormatExec = URL(fileURLWithPath: tool.path.string) - + var arguments: [String] = ["lint"] - + arguments.append(contentsOf: targetDirectories) - + arguments.append(contentsOf: ["--recursive", "--parallel", "--strict"]) - + if let configurationFilePath = configurationFilePath { arguments.append(contentsOf: ["--configuration", configurationFilePath]) } - + let process = try Process.run(swiftFormatExec, arguments: arguments) process.waitUntilExit() - + if process.terminationReason == .exit && process.terminationStatus == 0 { print("Linted the source code.") - } - else { + } else { let problem = "\(process.terminationReason):\(process.terminationStatus)" Diagnostics.error("swift-format invocation failed: \(problem)") } @@ -35,16 +34,17 @@ extension LintPlugin: CommandPlugin { arguments: [String] ) async throws { let swiftFormatTool = try context.tool(named: "swift-format") - + // Extract the arguments that specify what targets to format. var argExtractor = ArgumentExtractor(arguments) let targetNames = argExtractor.extractOption(named: "target") - - let targetsToFormat = targetNames.isEmpty ? context.package.targets : try context.package.targets(named: targetNames) + + let targetsToFormat = + targetNames.isEmpty ? context.package.targets : try context.package.targets(named: targetNames) let configurationFilePath = argExtractor.extractOption(named: "swift-format-configuration").first - + let sourceCodeTargets = targetsToFormat.compactMap { $0 as? SourceModuleTarget } - + try lint( tool: swiftFormatTool, targetDirectories: sourceCodeTargets.map(\.directory.string), @@ -61,7 +61,7 @@ extension LintPlugin: XcodeCommandPlugin { let swiftFormatTool = try context.tool(named: "swift-format") var argExtractor = ArgumentExtractor(arguments) let configurationFilePath = argExtractor.extractOption(named: "swift-format-configuration").first - + try lint( tool: swiftFormatTool, targetDirectories: [context.xcodeProject.directory.string], diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 531461a57..e1584a5c5 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -278,9 +278,11 @@ public struct Configuration: Codable, Equatable { self.version = try container.decodeIfPresent(Int.self, forKey: .version) ?? 1 guard version <= highestSupportedConfigurationVersion else { throw DecodingError.dataCorruptedError( - forKey: .version, in: container, + forKey: .version, + in: container, debugDescription: - "This version of the formatter does not support configuration version \(version).") + "This version of the formatter does not support configuration version \(version)." + ) } // If we ever introduce a new version, this is where we should switch on the decoded version @@ -328,30 +330,40 @@ public struct Configuration: Codable, Equatable { ?? defaults.prioritizeKeepingFunctionOutputTogether self.indentConditionalCompilationBlocks = try container.decodeIfPresent(Bool.self, forKey: .indentConditionalCompilationBlocks) - ?? defaults.indentConditionalCompilationBlocks + ?? defaults.indentConditionalCompilationBlocks self.lineBreakAroundMultilineExpressionChainComponents = try container.decodeIfPresent( - Bool.self, forKey: .lineBreakAroundMultilineExpressionChainComponents) + Bool.self, + forKey: .lineBreakAroundMultilineExpressionChainComponents + ) ?? defaults.lineBreakAroundMultilineExpressionChainComponents self.spacesAroundRangeFormationOperators = try container.decodeIfPresent( - Bool.self, forKey: .spacesAroundRangeFormationOperators) + Bool.self, + forKey: .spacesAroundRangeFormationOperators + ) ?? defaults.spacesAroundRangeFormationOperators self.fileScopedDeclarationPrivacy = try container.decodeIfPresent( - FileScopedDeclarationPrivacyConfiguration.self, forKey: .fileScopedDeclarationPrivacy) + FileScopedDeclarationPrivacyConfiguration.self, + forKey: .fileScopedDeclarationPrivacy + ) ?? defaults.fileScopedDeclarationPrivacy self.indentSwitchCaseLabels = try container.decodeIfPresent(Bool.self, forKey: .indentSwitchCaseLabels) - ?? defaults.indentSwitchCaseLabels + ?? defaults.indentSwitchCaseLabels self.noAssignmentInExpressions = try container.decodeIfPresent( - NoAssignmentInExpressionsConfiguration.self, forKey: .noAssignmentInExpressions) + NoAssignmentInExpressionsConfiguration.self, + forKey: .noAssignmentInExpressions + ) ?? defaults.noAssignmentInExpressions self.multiElementCollectionTrailingCommas = try container.decodeIfPresent( - Bool.self, forKey: .multiElementCollectionTrailingCommas) - ?? defaults.multiElementCollectionTrailingCommas + Bool.self, + forKey: .multiElementCollectionTrailingCommas + ) + ?? defaults.multiElementCollectionTrailingCommas self.reflowMultilineStringLiterals = try container.decodeIfPresent(MultilineStringReflowBehavior.self, forKey: .reflowMultilineStringLiterals) @@ -384,9 +396,12 @@ public struct Configuration: Codable, Equatable { try container.encode(lineBreakBetweenDeclarationAttributes, forKey: .lineBreakBetweenDeclarationAttributes) try container.encode( lineBreakAroundMultilineExpressionChainComponents, - forKey: .lineBreakAroundMultilineExpressionChainComponents) + forKey: .lineBreakAroundMultilineExpressionChainComponents + ) try container.encode( - spacesAroundRangeFormationOperators, forKey: .spacesAroundRangeFormationOperators) + spacesAroundRangeFormationOperators, + forKey: .spacesAroundRangeFormationOperators + ) try container.encode(fileScopedDeclarationPrivacy, forKey: .fileScopedDeclarationPrivacy) try container.encode(indentSwitchCaseLabels, forKey: .indentSwitchCaseLabels) try container.encode(noAssignmentInExpressions, forKey: .noAssignmentInExpressions) diff --git a/Sources/SwiftFormat/API/Indent.swift b/Sources/SwiftFormat/API/Indent.swift index c8ca49c5c..5039da135 100644 --- a/Sources/SwiftFormat/API/Indent.swift +++ b/Sources/SwiftFormat/API/Indent.swift @@ -36,7 +36,9 @@ public enum Indent: Hashable, Codable { throw DecodingError.dataCorrupted( DecodingError.Context( codingPath: decoder.codingPath, - debugDescription: "Only one of \"tabs\" or \"spaces\" may be specified")) + debugDescription: "Only one of \"tabs\" or \"spaces\" may be specified" + ) + ) } if let spacesCount = spacesCount { self = .spaces(spacesCount) @@ -50,7 +52,9 @@ public enum Indent: Hashable, Codable { throw DecodingError.dataCorrupted( DecodingError.Context( codingPath: decoder.codingPath, - debugDescription: "One of \"tabs\" or \"spaces\" must be specified")) + debugDescription: "One of \"tabs\" or \"spaces\" must be specified" + ) + ) } public func encode(to encoder: Encoder) throws { diff --git a/Sources/SwiftFormat/API/Selection.swift b/Sources/SwiftFormat/API/Selection.swift index 9ea599db3..2e7d00109 100644 --- a/Sources/SwiftFormat/API/Selection.swift +++ b/Sources/SwiftFormat/API/Selection.swift @@ -24,7 +24,7 @@ public enum Selection { self = .infinite } else { let ranges = offsetRanges.map { - AbsolutePosition(utf8Offset: $0.lowerBound) ..< AbsolutePosition(utf8Offset: $0.upperBound) + AbsolutePosition(utf8Offset: $0.lowerBound).. Bool { diff --git a/Sources/SwiftFormat/API/SwiftFormatter.swift b/Sources/SwiftFormat/API/SwiftFormatter.swift index e91030b3c..0daeb22c6 100644 --- a/Sources/SwiftFormat/API/SwiftFormatter.swift +++ b/Sources/SwiftFormat/API/SwiftFormatter.swift @@ -72,7 +72,8 @@ public final class SwiftFormatter { assumingFileURL: url, selection: .infinite, to: &outputStream, - parsingDiagnosticHandler: parsingDiagnosticHandler) + parsingDiagnosticHandler: parsingDiagnosticHandler + ) } /// Formats the given Swift source code and writes the result to an output stream. @@ -109,10 +110,16 @@ public final class SwiftFormatter { source: source, operatorTable: .standardOperators, assumingFileURL: url, - parsingDiagnosticHandler: parsingDiagnosticHandler) + parsingDiagnosticHandler: parsingDiagnosticHandler + ) try format( - syntax: sourceFile, source: source, operatorTable: .standardOperators, assumingFileURL: url, - selection: selection, to: &outputStream) + syntax: sourceFile, + source: source, + operatorTable: .standardOperators, + assumingFileURL: url, + selection: selection, + to: &outputStream + ) } /// Formats the given Swift syntax tree and writes the result to an output stream. @@ -137,14 +144,24 @@ public final class SwiftFormatter { /// be written. /// - Throws: If an unrecoverable error occurs when formatting the code. public func format( - syntax: SourceFileSyntax, source: String, operatorTable: OperatorTable, - assumingFileURL url: URL?, selection: Selection, to outputStream: inout Output + syntax: SourceFileSyntax, + source: String, + operatorTable: OperatorTable, + assumingFileURL url: URL?, + selection: Selection, + to outputStream: inout Output ) throws { let assumedURL = url ?? URL(fileURLWithPath: "source") let context = Context( - configuration: configuration, operatorTable: operatorTable, findingConsumer: findingConsumer, - fileURL: assumedURL, selection: selection, sourceFileSyntax: syntax, source: source, - ruleNameCache: ruleNameCache) + configuration: configuration, + operatorTable: operatorTable, + findingConsumer: findingConsumer, + fileURL: assumedURL, + selection: selection, + sourceFileSyntax: syntax, + source: source, + ruleNameCache: ruleNameCache + ) let pipeline = FormatPipeline(context: context) let transformedSyntax = pipeline.rewrite(Syntax(syntax)) @@ -158,7 +175,8 @@ public final class SwiftFormatter { source: source, node: transformedSyntax, printTokenStream: debugOptions.contains(.dumpTokenStream), - whitespaceOnly: false) + whitespaceOnly: false + ) outputStream.write(printer.prettyPrint()) } } diff --git a/Sources/SwiftFormat/API/SwiftLinter.swift b/Sources/SwiftFormat/API/SwiftLinter.swift index 79568e2cb..25cfa2af1 100644 --- a/Sources/SwiftFormat/API/SwiftLinter.swift +++ b/Sources/SwiftFormat/API/SwiftLinter.swift @@ -67,7 +67,8 @@ public final class SwiftLinter { try lint( source: String(contentsOf: url, encoding: .utf8), assumingFileURL: url, - parsingDiagnosticHandler: parsingDiagnosticHandler) + parsingDiagnosticHandler: parsingDiagnosticHandler + ) } /// Lints the given Swift source code. @@ -97,9 +98,14 @@ public final class SwiftLinter { source: source, operatorTable: .standardOperators, assumingFileURL: url, - parsingDiagnosticHandler: parsingDiagnosticHandler) + parsingDiagnosticHandler: parsingDiagnosticHandler + ) try lint( - syntax: sourceFile, operatorTable: .standardOperators, assumingFileURL: url, source: source) + syntax: sourceFile, + operatorTable: .standardOperators, + assumingFileURL: url, + source: source + ) } /// Lints the given Swift syntax tree. @@ -133,8 +139,14 @@ public final class SwiftLinter { source: String ) throws { let context = Context( - configuration: configuration, operatorTable: operatorTable, findingConsumer: findingConsumer, - fileURL: url, sourceFileSyntax: syntax, source: source, ruleNameCache: ruleNameCache) + configuration: configuration, + operatorTable: operatorTable, + findingConsumer: findingConsumer, + fileURL: url, + sourceFileSyntax: syntax, + source: source, + ruleNameCache: ruleNameCache + ) let pipeline = LintPipeline(context: context) pipeline.walk(Syntax(syntax)) @@ -149,7 +161,8 @@ public final class SwiftLinter { source: source, node: Syntax(syntax), printTokenStream: debugOptions.contains(.dumpTokenStream), - whitespaceOnly: true) + whitespaceOnly: true + ) let formatted = printer.prettyPrint() let ws = WhitespaceLinter(user: syntax.description, formatted: formatted, context: context) ws.lint() diff --git a/Sources/SwiftFormat/Core/Context.swift b/Sources/SwiftFormat/Core/Context.swift index 2bbb900ac..e00e38b20 100644 --- a/Sources/SwiftFormat/Core/Context.swift +++ b/Sources/SwiftFormat/Core/Context.swift @@ -12,8 +12,8 @@ import Foundation import SwiftOperators -import SwiftSyntax import SwiftParser +import SwiftSyntax /// Context contains the bits that each formatter and linter will need access to. /// @@ -102,7 +102,8 @@ public final class Context { """ Missing cached rule name for '\(rule)'! \ Ensure `generate-swift-format` has been run and `ruleNameCache` was injected. - """) + """ + ) let ruleName = ruleNameCache[ObjectIdentifier(rule)] ?? R.ruleName switch ruleMask.ruleState(ruleName, at: loc) { diff --git a/Sources/SwiftFormat/Core/DocumentationComment.swift b/Sources/SwiftFormat/Core/DocumentationComment.swift index 4aa0d601b..d89b4c1c6 100644 --- a/Sources/SwiftFormat/Core/DocumentationComment.swift +++ b/Sources/SwiftFormat/Core/DocumentationComment.swift @@ -216,7 +216,8 @@ public struct DocumentationComment { ) -> Parameter? { var rewriter = ParameterOutlineMarkupRewriter( origin: listItem, - expectParameterLabel: expectParameterLabel) + expectParameterLabel: expectParameterLabel + ) guard let newListItem = listItem.accept(&rewriter) as? ListItem, let name = rewriter.parameterName @@ -275,7 +276,7 @@ private struct ParameterOutlineMarkupRewriter: MarkupRewriter { guard listItem.isIdentical(to: origin) else { return listItem } return defaultVisit(listItem) } - + mutating func visitParagraph(_ paragraph: Paragraph) -> Markup? { // Only recurse into the first paragraph in the list item. guard paragraph.indexInParent == 0 else { return paragraph } diff --git a/Sources/SwiftFormat/Core/DocumentationCommentText.swift b/Sources/SwiftFormat/Core/DocumentationCommentText.swift index 32dd82a18..7ee21b7ba 100644 --- a/Sources/SwiftFormat/Core/DocumentationCommentText.swift +++ b/Sources/SwiftFormat/Core/DocumentationCommentText.swift @@ -157,7 +157,8 @@ public struct DocumentationCommentText { private func indentationDistance(of text: Substring) -> Int { return text.distance( from: text.startIndex, - to: text.firstIndex { !$0.isWhitespace } ?? text.endIndex) + to: text.firstIndex { !$0.isWhitespace } ?? text.endIndex + ) } /// Returns the number of contiguous whitespace characters (spaces and tabs only) that precede the @@ -215,18 +216,16 @@ private func findCommentStartIndex(_ triviaArray: Array) -> Array( - _ visitor: (Rule) -> (Node) -> SyntaxVisitorContinueKind, for node: Node + _ visitor: (Rule) -> (Node) -> SyntaxVisitorContinueKind, + for node: Node ) { guard context.shouldFormat(Rule.self, node: Syntax(node)) else { return } let ruleId = ObjectIdentifier(Rule.self) @@ -48,7 +49,8 @@ extension LintPipeline { /// - node: The syntax node on which the rule will be applied. This lets us check whether the /// rule is enabled for the particular source range where the node occurs. func visitIfEnabled( - _ visitor: (Rule) -> (Node) -> Any, for node: Node + _ visitor: (Rule) -> (Node) -> Any, + for node: Node ) { // Note that visitor function type is expressed as `Any` because we ignore the return value, but // more importantly because the `visit` methods return protocol refinements of `Syntax` that @@ -66,7 +68,8 @@ extension LintPipeline { /// - rule: The type of the syntax rule we're cleaning up. /// - node: The syntax node htat our traversal has left. func onVisitPost( - rule: R.Type, for node: Node + rule: R.Type, + for node: Node ) { let rule = ObjectIdentifier(rule) if case .some(let skipNode) = self.shouldSkipChildren[rule] { diff --git a/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift b/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift index b1e58cc12..043f3b893 100644 --- a/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift +++ b/Sources/SwiftFormat/Core/ModifierListSyntax+Convenience.swift @@ -18,7 +18,7 @@ extension DeclModifierListSyntax { for modifier in self { switch modifier.name.tokenKind { case .keyword(.public), .keyword(.private), .keyword(.fileprivate), .keyword(.internal), - .keyword(.package): + .keyword(.package): return modifier default: continue diff --git a/Sources/SwiftFormat/Core/Rule.swift b/Sources/SwiftFormat/Core/Rule.swift index 5994ea76e..368c7087e 100644 --- a/Sources/SwiftFormat/Core/Rule.swift +++ b/Sources/SwiftFormat/Core/Rule.swift @@ -73,10 +73,14 @@ extension Rule { syntaxLocation = node.startLocation(converter: context.sourceLocationConverter) case .leadingTrivia(let index): syntaxLocation = node.startLocation( - ofLeadingTriviaAt: index, converter: context.sourceLocationConverter) + ofLeadingTriviaAt: index, + converter: context.sourceLocationConverter + ) case .trailingTrivia(let index): syntaxLocation = node.startLocation( - ofTrailingTriviaAt: index, converter: context.sourceLocationConverter) + ofTrailingTriviaAt: index, + converter: context.sourceLocationConverter + ) } } else { syntaxLocation = nil @@ -87,6 +91,7 @@ extension Rule { message, category: category, location: syntaxLocation.flatMap(Finding.Location.init), - notes: notes) + notes: notes + ) } } diff --git a/Sources/SwiftFormat/Core/RuleMask.swift b/Sources/SwiftFormat/Core/RuleMask.swift index b08a6942b..9edf2449c 100644 --- a/Sources/SwiftFormat/Core/RuleMask.swift +++ b/Sources/SwiftFormat/Core/RuleMask.swift @@ -154,7 +154,10 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor { } let sourceRange = node.sourceRange( - converter: sourceLocationConverter, afterLeadingTrivia: false, afterTrailingTrivia: true) + converter: sourceLocationConverter, + afterLeadingTrivia: false, + afterTrailingTrivia: true + ) allRulesIgnoredRanges.append(sourceRange) return .skipChildren } @@ -181,9 +184,10 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor { /// - Parameters: /// - token: A token that may have comments that modify the status of rules. /// - node: The node to which the token belongs. - private func appendRuleStatusDirectives(from token: TokenSyntax, of node: Syntax) - -> SyntaxVisitorContinueKind - { + private func appendRuleStatusDirectives( + from token: TokenSyntax, + of node: Syntax + ) -> SyntaxVisitorContinueKind { let isFirstInFile = token.previousToken(viewMode: .sourceAccurate) == nil let matches = loneLineComments(in: token.leadingTrivia, isFirstToken: isFirstInFile) .compactMap(ruleStatusDirectiveMatch) diff --git a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift index 94147ef05..d359a1b23 100644 --- a/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift +++ b/Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift @@ -155,7 +155,8 @@ extension SyntaxProtocol { var parent = self.parent while let existingParent = parent { if let functionDecl = existingParent.as(FunctionDeclSyntax.self), - functionDecl.hasAttribute("Test", inModule: "Testing") { + functionDecl.hasAttribute("Test", inModule: "Testing") + { return true } parent = existingParent.parent diff --git a/Sources/SwiftFormat/Core/WithSemicolonSyntax.swift b/Sources/SwiftFormat/Core/WithSemicolonSyntax.swift index 1f9a3d2d3..42ac7ce42 100644 --- a/Sources/SwiftFormat/Core/WithSemicolonSyntax.swift +++ b/Sources/SwiftFormat/Core/WithSemicolonSyntax.swift @@ -29,4 +29,3 @@ extension SyntaxProtocol { return self.asProtocol(WithSemicolonSyntax.self) != nil } } - diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 8ad1c04c9..f9e9246e5 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -10,8 +10,8 @@ // //===----------------------------------------------------------------------===// -import SwiftSyntax import Foundation +import SwiftSyntax /// PrettyPrinter takes a Syntax node and outputs a well-formatted, re-indented reproduction of the /// code as a String. @@ -151,7 +151,8 @@ public class PrettyPrinter { private var currentIndentation: [Indent] { let indentation = configuration.indentation var totalIndentation: [Indent] = activeOpenBreaks.flatMap { (open) -> [Indent] in - let count = (open.contributesBlockIndent ? 1 : 0) + let count = + (open.contributesBlockIndent ? 1 : 0) + (open.contributesContinuationIndent ? 1 : 0) return Array(repeating: indentation, count: count) } @@ -187,11 +188,15 @@ public class PrettyPrinter { self.tokens = node.makeTokenStream( configuration: configuration, selection: context.selection, - operatorTable: context.operatorTable) + operatorTable: context.operatorTable + ) self.maxLineLength = configuration.lineLength self.printTokenStream = printTokenStream self.whitespaceOnly = whitespaceOnly - self.outputBuffer = PrettyPrintBuffer(maximumBlankLines: configuration.maximumBlankLines, tabWidth: configuration.tabWidth) + self.outputBuffer = PrettyPrintBuffer( + maximumBlankLines: configuration.maximumBlankLines, + tabWidth: configuration.tabWidth + ) } /// Print out the provided token, and apply line-wrapping and indentation as needed. @@ -274,7 +279,8 @@ public class PrettyPrinter { // lines within it (unless they are themselves continuations within that particular // scope), so we need the continuation indentation to persist across all the lines in that // scope. Additionally, continuation open breaks must indent when the break fires. - let continuationBreakWillFire = openKind == .continuation + let continuationBreakWillFire = + openKind == .continuation && (outputBuffer.isAtStartOfLine || !canFit(length) || mustBreak) let contributesContinuationIndent = currentLineIsContinuation || continuationBreakWillFire @@ -284,7 +290,9 @@ public class PrettyPrinter { kind: openKind, lineNumber: currentLineNumber, contributesContinuationIndent: contributesContinuationIndent, - contributesBlockIndent: openKind == .block)) + contributesBlockIndent: openKind == .block + ) + ) continuationStack.append(currentLineIsContinuation) @@ -298,8 +306,7 @@ public class PrettyPrinter { fatalError("Unmatched closing break") } - let openedOnDifferentLine - = openCloseBreakCompensatingLineNumber != matchingOpenBreak.lineNumber + let openedOnDifferentLine = openCloseBreakCompensatingLineNumber != matchingOpenBreak.lineNumber if matchingOpenBreak.contributesBlockIndent { // The actual line number is used, instead of the compensating line number. When the close @@ -350,12 +357,14 @@ public class PrettyPrinter { // // Likewise, we need to do this if we popped an old continuation state off the stack, // even if the break *doesn't* fire. - let matchingOpenBreakIndented = matchingOpenBreak.contributesContinuationIndent + let matchingOpenBreakIndented = + matchingOpenBreak.contributesContinuationIndent || matchingOpenBreak.contributesBlockIndent currentLineIsContinuation = matchingOpenBreakIndented && openedOnDifferentLine } - let wasContinuationWhenOpened = (continuationStack.popLast() ?? false) + let wasContinuationWhenOpened = + (continuationStack.popLast() ?? false) || matchingOpenBreak.contributesContinuationIndent // This ensures a continuation indent is propagated to following scope when an initial // scope would've indented if the leading break wasn't at the start of a line. @@ -499,7 +508,8 @@ public class PrettyPrinter { // We never want to add a trailing comma in an initializer so we disable trailing commas on // single element collections. let shouldHaveTrailingComma = - startLineNumber != openCloseBreakCompensatingLineNumber && !isSingleElement && configuration.multiElementCollectionTrailingCommas + startLineNumber != openCloseBreakCompensatingLineNumber && !isSingleElement + && configuration.multiElementCollectionTrailingCommas if shouldHaveTrailingComma && !hasTrailingComma { diagnose(.addTrailingComma, category: .trailingComma) } else if !shouldHaveTrailingComma && hasTrailingComma { @@ -526,7 +536,9 @@ public class PrettyPrinter { var text = String(source[start.. SyntaxVisitorContinueKind { - let catchPrecedingBreak = config.lineBreakBeforeControlFlowKeywords + let catchPrecedingBreak = + config.lineBreakBeforeControlFlowKeywords ? Token.break(.same, newlines: .soft) : Token.space before(node.catchKeyword, tokens: catchPrecedingBreak) @@ -960,7 +975,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { let endToken = Token.commaDelimitedRegionEnd( hasTrailingComma: lastElement.trailingComma != nil, - isSingleElement: node.first == lastElement) + isSingleElement: node.first == lastElement + ) after(lastElement.expression.lastToken(viewMode: .sourceAccurate), tokens: [endToken]) } return .visitChildren @@ -1003,7 +1019,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { let endToken = Token.commaDelimitedRegionEnd( hasTrailingComma: lastElement.trailingComma != nil, - isSingleElement: node.first == node.last) + isSingleElement: node.first == node.last + ) after(lastElement.lastToken(viewMode: .sourceAccurate), tokens: endToken) } return .visitChildren @@ -1054,8 +1071,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // When this function call is wrapped by a try-expr or await-expr, the group applied when // visiting that wrapping expression is sufficient. Adding another group here in that case // can result in unnecessarily breaking after the try/await keyword. - if !(base.firstToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .all)?.parent?.is(TryExprSyntax.self) ?? false - || base.firstToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .all)?.parent?.is(AwaitExprSyntax.self) ?? false) { + if !(base.firstToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .all)?.parent?.is(TryExprSyntax.self) + ?? false + || base.firstToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .all)?.parent?.is(AwaitExprSyntax.self) + ?? false) + { before(base.firstToken(viewMode: .sourceAccurate), tokens: .open) after(calledMemberAccessExpr.declName.baseName.lastToken(viewMode: .sourceAccurate), tokens: .close) } @@ -1072,13 +1092,15 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before( node.trailingClosure?.leftBrace, - tokens: .break(.same, newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.same, newlines: .elective(ignoresDiscretionary: true)) + ) arrangeFunctionCallArgumentList( arguments, leftDelimiter: node.leftParen, rightDelimiter: node.rightParen, - forcesBreakBeforeRightDelimiter: breakBeforeRightParen) + forcesBreakBeforeRightDelimiter: breakBeforeRightParen + ) return .visitChildren } @@ -1087,9 +1109,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { clearContextualBreakState(node) } - override func visit(_ node: MultipleTrailingClosureElementSyntax) - -> SyntaxVisitorContinueKind - { + override func visit( + _ node: MultipleTrailingClosureElementSyntax + ) -> SyntaxVisitorContinueKind { before(node.label, tokens: .space) after(node.colon, tokens: .space) return .visitChildren @@ -1114,7 +1136,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if !arguments.isEmpty { var afterLeftDelimiter: [Token] = [.break(.open, size: 0)] var beforeRightDelimiter: [Token] = [ - .break(.close(mustBreak: forcesBreakBeforeRightDelimiter), size: 0), + .break(.close(mustBreak: forcesBreakBeforeRightDelimiter), size: 0) ] if shouldGroupAroundArgumentList(arguments) { @@ -1203,7 +1225,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { of: node, contentsKeyPath: \.statements, shouldResetBeforeLeftBrace: false, - openBraceNewlineBehavior: newlineBehavior) + openBraceNewlineBehavior: newlineBehavior + ) } return .visitChildren } @@ -1217,7 +1240,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) arrangeAttributeList( - node.attributes, suppressFinalBreak: node.parameterClause == nil && node.capture == nil) + node.attributes, + suppressFinalBreak: node.parameterClause == nil && node.capture == nil + ) if let parameterClause = node.parameterClause { // We unconditionally put a break before the `in` keyword below, so we should only put a break @@ -1240,7 +1265,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Since the output clause is optional but the in-token is required, placing the .close // before `inTok` ensures the close gets into the token stream. before(node.inKeyword, tokens: .close) - } else { + } else { // Group outside of the parens, so that the argument list together, preferring to break // between the argument list and the output. before(parameterClause.firstToken(viewMode: .sourceAccurate), tokens: .open) @@ -1300,13 +1325,15 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before( node.trailingClosure?.leftBrace, - tokens: .break(.same, newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.same, newlines: .elective(ignoresDiscretionary: true)) + ) arrangeFunctionCallArgumentList( arguments, leftDelimiter: node.leftSquare, rightDelimiter: node.rightSquare, - forcesBreakBeforeRightDelimiter: breakBeforeRightBracket) + forcesBreakBeforeRightDelimiter: breakBeforeRightBracket + ) return .visitChildren } @@ -1329,13 +1356,15 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before( node.trailingClosure?.leftBrace, - tokens: .break(.same, newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.same, newlines: .elective(ignoresDiscretionary: true)) + ) arrangeFunctionCallArgumentList( node.arguments, leftDelimiter: node.leftParen, rightDelimiter: node.rightParen, - forcesBreakBeforeRightDelimiter: false) + forcesBreakBeforeRightDelimiter: false + ) return .visitChildren } @@ -1350,13 +1379,15 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before( node.trailingClosure?.leftBrace, - tokens: .break(.same, newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.same, newlines: .elective(ignoresDiscretionary: true)) + ) arrangeFunctionCallArgumentList( arguments, leftDelimiter: node.leftParen, rightDelimiter: node.rightParen, - forcesBreakBeforeRightDelimiter: breakBeforeRightParen) + forcesBreakBeforeRightDelimiter: breakBeforeRightParen + ) return .visitChildren } @@ -1393,7 +1424,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { arrangeAttributeList(node.attributes) before( node.secondName, - tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true)) + ) after(node.colon, tokens: .break) if let trailingComma = node.trailingComma { @@ -1408,7 +1440,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) before( node.secondName, - tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true)) + ) after(node.colon, tokens: .break) if let trailingComma = node.trailingComma { @@ -1424,7 +1457,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { arrangeAttributeList(node.attributes) before( node.secondName, - tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true)) + ) after(node.colon, tokens: .break) if let trailingComma = node.trailingComma { @@ -1497,10 +1531,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if !isNestedInPostfixIfConfig(node: Syntax(node)), let condition = node.condition { before( condition.firstToken(viewMode: .sourceAccurate), - tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: true))) + tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: true)) + ) after( condition.lastToken(viewMode: .sourceAccurate), - tokens: .printerControl(kind: .enableBreaking), .break(.reset, size: 0)) + tokens: .printerControl(kind: .enableBreaking), + .break(.reset, size: 0) + ) } return .visitChildren @@ -1518,7 +1555,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { let newlines: NewlineBehavior = item != node.last && shouldInsertNewline(basedOn: item.semicolon) ? .soft : .elective let resetSize = item.semicolon != nil ? 1 : 0 - after(item.lastToken(viewMode: .sourceAccurate), tokens: .close, .break(.reset, size: resetSize, newlines: newlines)) + after( + item.lastToken(viewMode: .sourceAccurate), + tokens: .close, + .break(.reset, size: resetSize, newlines: newlines) + ) } return .visitChildren } @@ -1560,7 +1601,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: OperatorPrecedenceAndTypesSyntax) -> SyntaxVisitorContinueKind { before(node.colon, tokens: .space) after(node.colon, tokens: .break(.open), .open) - after(node.designatedTypes.lastToken(viewMode: .sourceAccurate) ?? node.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0), .close) + after( + node.designatedTypes.lastToken(viewMode: .sourceAccurate) ?? node.lastToken(viewMode: .sourceAccurate), + tokens: .break(.close, size: 0), + .close + ) return .visitChildren } @@ -1647,7 +1692,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { let newlines: NewlineBehavior = item != node.last && shouldInsertNewline(basedOn: item.semicolon) ? .soft : .elective let resetSize = item.semicolon != nil ? 1 : 0 - after(item.lastToken(viewMode: .sourceAccurate), tokens: .close, .break(.reset, size: resetSize, newlines: newlines)) + after( + item.lastToken(viewMode: .sourceAccurate), + tokens: .close, + .break(.reset, size: resetSize, newlines: newlines) + ) } return .visitChildren } @@ -1661,7 +1710,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // This group applies to a top-level if-stmt so that all of the bodies will have the same // breaking behavior. if let exprStmt = node.item.as(ExpressionStmtSyntax.self), - let ifStmt = exprStmt.expression.as(IfExprSyntax.self) { + let ifStmt = exprStmt.expression.as(IfExprSyntax.self) + { before(ifStmt.conditions.firstToken(viewMode: .sourceAccurate), tokens: .open(.consistent)) after(ifStmt.lastToken(viewMode: .sourceAccurate), tokens: .close) } @@ -1694,7 +1744,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) before( node.secondName, - tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true)) + ) after(node.colon, tokens: .break) if let trailingComma = node.trailingComma { @@ -1726,7 +1777,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: TryExprSyntax) -> SyntaxVisitorContinueKind { before( node.expression.firstToken(viewMode: .sourceAccurate), - tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true)) + ) // Check for an anchor token inside of the expression to group with the try keyword. if let anchorToken = findTryAwaitExprConnectingToken(inExpr: node.expression) { @@ -1740,7 +1792,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AwaitExprSyntax) -> SyntaxVisitorContinueKind { before( node.expression.firstToken(viewMode: .sourceAccurate), - tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true)) + ) // Check for an anchor token inside of the expression to group with the await keyword. if !(node.parent?.is(TryExprSyntax.self) ?? false), @@ -1766,8 +1819,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let callingExpr = expr.asProtocol(CallingExprSyntaxProtocol.self) { return findTryAwaitExprConnectingToken(inExpr: callingExpr.calledExpression) } - if let memberAccessExpr = expr.as(MemberAccessExprSyntax.self), let base = memberAccessExpr.base - { + if let memberAccessExpr = expr.as(MemberAccessExprSyntax.self), let base = memberAccessExpr.base { // When there's a simple base (i.e. identifier), group the entire `try/await .` // sequence. This check has to happen here so that the `MemberAccessExprSyntax.name` is // available. @@ -1795,7 +1847,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { argumentList, leftDelimiter: leftParen, rightDelimiter: rightParen, - forcesBreakBeforeRightDelimiter: false) + forcesBreakBeforeRightDelimiter: false + ) } case .some: // Wrap the attribute's arguments in their own group, so arguments stay together with a higher @@ -1854,9 +1907,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: PlatformVersionItemListSyntax) - -> SyntaxVisitorContinueKind - { + override func visit( + _ node: PlatformVersionItemListSyntax + ) -> SyntaxVisitorContinueKind { insertTokens(.break(.same, size: 1), betweenElementsOf: node) return .visitChildren } @@ -1871,9 +1924,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: BackDeployedAttributeArgumentsSyntax) -> SyntaxVisitorContinueKind { before( node.platforms.firstToken(viewMode: .sourceAccurate), - tokens: .break(.open, size: 1), .open(argumentListConsistency())) + tokens: .break(.open, size: 1), + .open(argumentListConsistency()) + ) after( - node.platforms.lastToken(viewMode: .sourceAccurate), tokens: .break(.close, size: 0), .close) + node.platforms.lastToken(viewMode: .sourceAccurate), + tokens: .break(.close, size: 0), + .close + ) return .visitChildren } @@ -1894,7 +1952,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind { // Import declarations should never be wrapped. - before(node.firstToken(viewMode: .sourceAccurate), tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: false))) + before( + node.firstToken(viewMode: .sourceAccurate), + tokens: .printerControl(kind: .disableBreaking(allowDiscretionary: false)) + ) arrangeAttributeList(node.attributes) after(node.importKeyword, tokens: .space) @@ -1936,7 +1997,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { node.arguments, leftDelimiter: node.leftSquare, rightDelimiter: node.rightSquare, - forcesBreakBeforeRightDelimiter: breakBeforeRightParen) + forcesBreakBeforeRightDelimiter: breakBeforeRightParen + ) return .visitChildren } @@ -1961,7 +2023,10 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { after(node.questionMark, tokens: .space) before( node.colon, - tokens: .break(.close(mustBreak: false), size: 0), .break(.open(kind: .continuation)), .open) + tokens: .break(.close(mustBreak: false), size: 0), + .break(.open(kind: .continuation)), + .open + ) after(node.colon, tokens: .space) // When the ternary is wrapped in parens, absorb the closing paren into the ternary's group so @@ -2067,7 +2132,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } after( unindentingNode.lastToken(viewMode: .sourceAccurate), - tokens: afterTokens) + tokens: afterTokens + ) } else { beforeTokens = [.break(.continue)] } @@ -2153,7 +2219,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { """ SequenceExpr should have already been folded; found at byte offsets \ \(node.position.utf8Offset)..<\(node.endPosition.utf8Offset) - """) + """ + ) } override func visit(_ node: AssignmentExprSyntax) -> SyntaxVisitorContinueKind { @@ -2214,7 +2281,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let typeAnnotation = node.typeAnnotation, !typeAnnotation.type.is(MissingTypeSyntax.self) { after( typeAnnotation.colon, - tokens: .break(.open(kind: .continuation), newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.open(kind: .continuation), newlines: .elective(ignoresDiscretionary: true)) + ) closesNeeded += 1 closeAfterToken = typeAnnotation.lastToken(viewMode: .sourceAccurate) } @@ -2305,7 +2373,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { for specifier in node.specifiers { after( specifier.firstToken(viewMode: .sourceAccurate), - tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true)) + ) } return .visitChildren } @@ -2514,12 +2583,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // Looks up the correct break kind based on prior context. let stringLiteralParent = node.parent? - .as(StringLiteralSegmentListSyntax.self)? - .parent? - .as(StringLiteralExprSyntax.self) - let breakKind = stringLiteralParent.map { - pendingMultilineStringBreakKinds[$0, default: .same] - } ?? .same + .as(StringLiteralSegmentListSyntax.self)? + .parent? + .as(StringLiteralExprSyntax.self) + let breakKind = + stringLiteralParent.map { + pendingMultilineStringBreakKinds[$0, default: .same] + } ?? .same let isMultiLineString = stringLiteralParent?.openingQuote.tokenKind == .multilineStringQuote @@ -2529,9 +2599,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { let emitSegmentTextTokens = // If our configure reflow behavior is never, always use the single line emit segment text tokens. isMultiLineString && !config.reflowMultilineStringLiterals.isNever - ? { (segment) in self.emitMultilineSegmentTextTokens(breakKind: breakKind, segment: segment) } - // For single line strings we don't allow line breaks, so emit the string as a single `.syntax` token - : { (segment) in self.appendToken(.syntax(String(segment))) } + ? { (segment) in self.emitMultilineSegmentTextTokens(breakKind: breakKind, segment: segment) } + // For single line strings we don't allow line breaks, so emit the string as a single `.syntax` token + : { (segment) in self.appendToken(.syntax(String(segment))) } let segmentText = node.content.text if segmentText.hasSuffix("\n") { @@ -2704,7 +2774,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let typeAnnotation = node.typeAnnotation { after( typeAnnotation.colon, - tokens: .break(.open(kind: .continuation), newlines: .elective(ignoresDiscretionary: true))) + tokens: .break(.open(kind: .continuation), newlines: .elective(ignoresDiscretionary: true)) + ) after(typeAnnotation.lastToken(viewMode: .sourceAccurate), tokens: .break(.close(mustBreak: false), size: 0)) } @@ -2768,9 +2839,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: DerivativeAttributeArgumentsSyntax) - -> SyntaxVisitorContinueKind - { + override func visit( + _ node: DerivativeAttributeArgumentsSyntax + ) -> SyntaxVisitorContinueKind { // This node encapsulates the entire list of arguments in a `@derivative(...)` or // `@transpose(...)` attribute. before(node.ofLabel, tokens: .open) @@ -2813,7 +2884,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { closeScopeTokens.forEach(appendToken) generateEnableFormattingIfNecessary( - token.positionAfterSkippingLeadingTrivia ..< token.endPositionBeforeTrailingTrivia + token.positionAfterSkippingLeadingTrivia.. (isLineComment: Bool, tokens: [Token]) - { + private func afterTokensForTrailingComment( + _ token: TokenSyntax + ) -> (isLineComment: Bool, tokens: [Token]) { let (_, trailingComments) = partitionTrailingTrivia(token.trailingTrivia) let trivia = Trivia(pieces: trailingComments) @@ -3299,9 +3392,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// closing-scope collection. The opening-scope collection contains `.open` and `.break` tokens /// that start a "scope" before the token. The closing-scope collection contains `.close` and /// `.break` tokens that end a "scope" after the token. - private func splitScopingBeforeTokens(of token: TokenSyntax) -> ( - openingScope: [Token], closingScope: [Token] - ) { + private func splitScopingBeforeTokens( + of token: TokenSyntax + ) -> (openingScope: [Token], closingScope: [Token]) { guard let beforeTokens = beforeMap[token] else { return ([], []) } @@ -3381,7 +3474,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { switch piece { case .lineComment(let text): if index > 0 || isStartOfFile { - generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength) + generateEnableFormattingIfNecessary(position.. 0 || isStartOfFile { - generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength) + generateEnableFormattingIfNecessary(position..( - of expr: T, argumentListPath: KeyPath + of expr: T, + argumentListPath: KeyPath ) -> Bool { guard let parent = expr.parent, @@ -3699,7 +3791,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// the expression. This is a hand-crafted list of expressions that generally look better when the /// break(s) before the expression fire before breaks inside of the expression. private func maybeGroupAroundSubexpression( - _ expr: ExprSyntax, combiningOperator operatorExpr: ExprSyntax? = nil + _ expr: ExprSyntax, + combiningOperator operatorExpr: ExprSyntax? = nil ) { switch Syntax(expr).kind { case .memberAccessExpr, .subscriptCallExpr: @@ -3839,7 +3932,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// alongside the last token of the given node. Any tokens between `node.lastToken` and the /// returned node's `lastToken` are delimiter tokens that shouldn't be preceded by a break. private func outermostEnclosingNode(from node: Syntax) -> Syntax? { - guard let afterToken = node.lastToken(viewMode: .sourceAccurate)?.nextToken(viewMode: .all), closingDelimiterTokens.contains(afterToken) + guard let afterToken = node.lastToken(viewMode: .sourceAccurate)?.nextToken(viewMode: .all), + closingDelimiterTokens.contains(afterToken) else { return nil } @@ -3957,9 +4051,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) } - if let leftmostExpr = leftmostExpr(of: rhs, ifMatching: { - $0.is(IfExprSyntax.self) || $0.is(SwitchExprSyntax.self) - }) { + if let leftmostExpr = leftmostExpr( + of: rhs, + ifMatching: { + $0.is(IfExprSyntax.self) || $0.is(SwitchExprSyntax.self) + } + ) { return ( unindentingNode: Syntax(leftmostExpr), shouldReset: false, @@ -3986,8 +4083,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { if let binaryOperator = operatorExpr.as(BinaryOperatorExprSyntax.self) { let token = binaryOperator.operator if !config.spacesAroundRangeFormationOperators, - let binOp = operatorTable.infixOperator(named: token.text), - let precedenceGroup = binOp.precedenceGroup, precedenceGroup == "RangeFormationPrecedence" + let binOp = operatorTable.infixOperator(named: token.text), + let precedenceGroup = binOp.precedenceGroup, precedenceGroup == "RangeFormationPrecedence" { // We want to omit whitespace around range formation operators if possible. We can't do this // if the token is either preceded by a postfix operator, followed by a prefix operator, or @@ -4028,7 +4125,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // The leading trivia of the next token, after the ignored node, may contain content that // belongs with the ignored node. The trivia extraction that is performed for `lastToken` later // excludes that content so it needs to be extracted and added to the token stream here. - if let next = node.lastToken(viewMode: .sourceAccurate)?.nextToken(viewMode: .all), let trivia = next.leadingTrivia.first { + if let next = node.lastToken(viewMode: .sourceAccurate)?.nextToken(viewMode: .all), + let trivia = next.leadingTrivia.first + { switch trivia { case .lineComment, .blockComment: trivia.write(to: &nodeText) @@ -4070,16 +4169,16 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { /// tokens. When visiting an expression node, `preVisitInsertingContextualBreaks(_:)` should be /// called instead of this helper. @discardableResult - private func insertContextualBreaks(_ expr: ExprSyntax, isTopLevel: Bool) -> ( - hasCompoundExpression: Bool, hasMemberAccess: Bool - ) { + private func insertContextualBreaks( + _ expr: ExprSyntax, + isTopLevel: Bool + ) -> (hasCompoundExpression: Bool, hasMemberAccess: Bool) { preVisitedExprs.insert(expr.id) if let memberAccessExpr = expr.as(MemberAccessExprSyntax.self) { // When the member access is part of a calling expression, the break before the dot is // inserted when visiting the parent node instead so that the break is inserted before any // scoping tokens (e.g. `contextualBreakingStart`, `open`). - if memberAccessExpr.base != nil && - expr.parent?.isProtocol(CallingExprSyntaxProtocol.self) != true { + if memberAccessExpr.base != nil && expr.parent?.isProtocol(CallingExprSyntaxProtocol.self) != true { before(memberAccessExpr.period, tokens: .break(.contextual, size: 0)) } var hasCompoundExpression = false @@ -4164,8 +4263,7 @@ private func isNestedInPostfixIfConfig(node: Syntax) -> Bool { return false } - if this?.is(IfConfigDeclSyntax.self) == true && - this?.parent?.is(PostfixIfConfigExprSyntax.self) == true { + if this?.is(IfConfigDeclSyntax.self) == true && this?.parent?.is(PostfixIfConfigExprSyntax.self) == true { return true } @@ -4264,9 +4362,9 @@ class CommentMovingRewriter: SyntaxRewriter { /// 2 trivia collections: the trivia that wasn't extracted and should remain in `token`'s leading /// trivia and the trivia that meets the criteria for extraction. /// - Parameter token: A token whose leading trivia should be split to extract line comments. - private func extractLineCommentTrivia(from token: TokenSyntax) -> ( - remainingTrivia: Trivia, extractedTrivia: Trivia - ) { + private func extractLineCommentTrivia( + from token: TokenSyntax + ) -> (remainingTrivia: Trivia, extractedTrivia: Trivia) { var pendingPieces = [TriviaPiece]() var keepWithTokenPieces = [TriviaPiece]() var extractingPieces = [TriviaPiece]() @@ -4320,8 +4418,8 @@ fileprivate func isFormatterIgnorePresent(inTrivia trivia: Trivia, isWholeFile: func isFormatterIgnore(in commentText: String, prefix: String, suffix: String) -> Bool { let trimmed = commentText.dropFirst(prefix.count) - .dropLast(suffix.count) - .trimmingCharacters(in: .whitespaces) + .dropLast(suffix.count) + .trimmingCharacters(in: .whitespaces) let pattern = isWholeFile ? "swift-format-ignore-file" : "swift-format-ignore" return trimmed == pattern } @@ -4365,7 +4463,7 @@ fileprivate func shouldFormatterIgnore(file: SourceFileSyntax) -> Bool { } extension NewlineBehavior { - static func +(lhs: NewlineBehavior, rhs: NewlineBehavior) -> NewlineBehavior { + static func + (lhs: NewlineBehavior, rhs: NewlineBehavior) -> NewlineBehavior { switch (lhs, rhs) { case (.elective, _): // `rhs` is either also elective or a required newline, which overwrites elective. @@ -4406,8 +4504,8 @@ protocol CallingExprSyntaxProtocol: ExprSyntaxProtocol { var calledExpression: ExprSyntax { get } } -extension FunctionCallExprSyntax: CallingExprSyntaxProtocol { } -extension SubscriptCallExprSyntax: CallingExprSyntaxProtocol { } +extension FunctionCallExprSyntax: CallingExprSyntaxProtocol {} +extension SubscriptCallExprSyntax: CallingExprSyntaxProtocol {} extension Syntax { func asProtocol(_: CallingExprSyntaxProtocol.Protocol) -> CallingExprSyntaxProtocol? { diff --git a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift index c88c88c5b..c5c5e2ae8 100644 --- a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift +++ b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift @@ -59,7 +59,8 @@ public class WhitespaceLinter { assert( safeCodeUnit(at: userWhitespace.endIndex, in: userText) == safeCodeUnit(at: formattedWhitespace.endIndex, in: formattedText), - "Non-whitespace characters do not match") + "Non-whitespace characters do not match" + ) compareWhitespace(userWhitespace: userWhitespace, formattedWhitespace: formattedWhitespace) @@ -82,7 +83,8 @@ public class WhitespaceLinter { /// - formattedWhitespace: A slice of formatted text representing the current span of contiguous /// whitespace that will be compared to the user whitespace. private func compareWhitespace( - userWhitespace: ArraySlice, formattedWhitespace: ArraySlice + userWhitespace: ArraySlice, + formattedWhitespace: ArraySlice ) { // We use a custom-crafted lazy-splitting iterator here instead of the standard // `Collection.split` function because Time Profiler indicated that a very large proportion of @@ -97,7 +99,8 @@ public class WhitespaceLinter { userIndex: userWhitespace.startIndex, formattedIndex: formattedWhitespace.startIndex, userRuns: userRuns, - formattedRuns: formattedRuns) + formattedRuns: formattedRuns + ) // No need to perform any further checks if the whitespace is identical. guard userWhitespace != formattedWhitespace else { return } @@ -135,7 +138,10 @@ public class WhitespaceLinter { // If this isn't the last whitespace run, then it must precede a newline, so we check // for trailing whitespace violations. checkForTrailingWhitespaceErrors( - userIndex: userIndex, userRun: userRun, formattedRun: formattedRun) + userIndex: userIndex, + userRun: userRun, + formattedRun: formattedRun + ) } userIndex += userRun.count + 1 } @@ -152,7 +158,8 @@ public class WhitespaceLinter { checkForIndentationErrors( userIndex: userIndex, userRun: userRunsIterator.latestElement!, - formattedRun: lastFormattedRun) + formattedRun: lastFormattedRun + ) } } @@ -163,7 +170,8 @@ public class WhitespaceLinter { diagnose( .addLinesError(excessFormattedLines), category: .addLines, - utf8Offset: userWhitespace.startIndex) + utf8Offset: userWhitespace.startIndex + ) } } @@ -257,7 +265,9 @@ public class WhitespaceLinter { /// - userRun: A run of whitespace from the user text. /// - formattedRun: A run of whitespace from the formatted text. private func checkForIndentationErrors( - userIndex: Int, userRun: ArraySlice, formattedRun: ArraySlice + userIndex: Int, + userRun: ArraySlice, + formattedRun: ArraySlice ) { guard userRun != formattedRun else { return } @@ -266,7 +276,8 @@ public class WhitespaceLinter { diagnose( .indentationError(expected: expected, actual: actual), category: .indentation, - utf8Offset: userIndex) + utf8Offset: userIndex + ) } /// Compare user and formatted whitespace buffers, and check for trailing whitespace. @@ -276,7 +287,9 @@ public class WhitespaceLinter { /// - userRun: The tokenized user whitespace buffer. /// - formattedRun: The tokenized formatted whitespace buffer. private func checkForTrailingWhitespaceErrors( - userIndex: Int, userRun: ArraySlice, formattedRun: ArraySlice + userIndex: Int, + userRun: ArraySlice, + formattedRun: ArraySlice ) { if userRun != formattedRun { diagnose(.trailingWhitespaceError, category: .trailingWhitespace, utf8Offset: userIndex) @@ -294,7 +307,9 @@ public class WhitespaceLinter { /// - userRun: The tokenized user whitespace buffer. /// - formattedRun: The tokenized formatted whitespace buffer. private func checkForSpacingErrors( - userIndex: Int, userRun: ArraySlice, formattedRun: ArraySlice + userIndex: Int, + userRun: ArraySlice, + formattedRun: ArraySlice ) { guard userRun != formattedRun else { return } @@ -320,11 +335,13 @@ public class WhitespaceLinter { /// - data: The input string. /// - Returns: A slice of `data` that covers the contiguous whitespace starting at the given /// index. - private func contiguousWhitespace(startingAt offset: Int, in data: [UTF8.CodeUnit]) - -> ArraySlice - { - guard let whitespaceEnd = - data[offset...].firstIndex(where: { !UnicodeScalar($0).properties.isWhitespace }) + private func contiguousWhitespace( + startingAt offset: Int, + in data: [UTF8.CodeUnit] + ) -> ArraySlice { + guard + let whitespaceEnd = + data[offset...].firstIndex(where: { !UnicodeScalar($0).properties.isWhitespace }) else { return data[offset..]()` syntax. In call sites that should be replaced with `[]`, /// for initializations use explicit type combined with empty array literal `let _: [] = []` @@ -21,12 +21,13 @@ import SwiftParser /// Lint: Non-literal empty array initialization will yield a lint error. /// Format: All invalid use sites would be related with empty literal (with or without explicit type annotation). @_spi(Rules) -public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { +public final class AlwaysUseLiteralForEmptyCollectionInit: SyntaxFormatRule { public override class var isOptIn: Bool { return true } public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax { guard let initializer = node.initializer, - let type = isRewritable(initializer) else { + let type = isRewritable(initializer) + else { return node } @@ -43,7 +44,8 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { public override func visit(_ param: FunctionParameterSyntax) -> FunctionParameterSyntax { guard let initializer = param.defaultValue, - let type = isRewritable(initializer) else { + let type = isRewritable(initializer) + else { return param } @@ -62,7 +64,8 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { /// Return a type of the collection. public func isRewritable(_ initializer: InitializerClauseSyntax) -> TypeSyntax? { guard let initCall = initializer.value.as(FunctionCallExprSyntax.self), - initCall.arguments.isEmpty else { + initCall.arguments.isEmpty + else { return nil } @@ -77,8 +80,10 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { return nil } - private func rewrite(_ node: PatternBindingSyntax, - type: ArrayTypeSyntax) -> PatternBindingSyntax { + private func rewrite( + _ node: PatternBindingSyntax, + type: ArrayTypeSyntax + ) -> PatternBindingSyntax { var replacement = node diagnose(node, type: type) @@ -87,8 +92,10 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { // Drop trailing trivia after pattern because ':' has to appear connected to it. replacement.pattern = node.pattern.with(\.trailingTrivia, []) // Add explicit type annotation: ': []` - replacement.typeAnnotation = .init(type: type.with(\.leadingTrivia, .space) - .with(\.trailingTrivia, .space)) + replacement.typeAnnotation = .init( + type: type.with(\.leadingTrivia, .space) + .with(\.trailingTrivia, .space) + ) } let initializer = node.initializer! @@ -100,8 +107,10 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { return replacement } - private func rewrite(_ node: PatternBindingSyntax, - type: DictionaryTypeSyntax) -> PatternBindingSyntax { + private func rewrite( + _ node: PatternBindingSyntax, + type: DictionaryTypeSyntax + ) -> PatternBindingSyntax { var replacement = node diagnose(node, type: type) @@ -110,8 +119,10 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { // Drop trailing trivia after pattern because ':' has to appear connected to it. replacement.pattern = node.pattern.with(\.trailingTrivia, []) // Add explicit type annotation: ': []` - replacement.typeAnnotation = .init(type: type.with(\.leadingTrivia, .space) - .with(\.trailingTrivia, .space)) + replacement.typeAnnotation = .init( + type: type.with(\.leadingTrivia, .space) + .with(\.trailingTrivia, .space) + ) } let initializer = node.initializer! @@ -121,8 +132,10 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { return replacement } - private func rewrite(_ param: FunctionParameterSyntax, - type: ArrayTypeSyntax) -> FunctionParameterSyntax { + private func rewrite( + _ param: FunctionParameterSyntax, + type: ArrayTypeSyntax + ) -> FunctionParameterSyntax { guard let initializer = param.defaultValue else { return param } @@ -131,8 +144,10 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { return param.with(\.defaultValue, initializer.with(\.value, getEmptyArrayLiteral())) } - private func rewrite(_ param: FunctionParameterSyntax, - type: DictionaryTypeSyntax) -> FunctionParameterSyntax { + private func rewrite( + _ param: FunctionParameterSyntax, + type: DictionaryTypeSyntax + ) -> FunctionParameterSyntax { guard let initializer = param.defaultValue else { return param } @@ -201,9 +216,10 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule { } extension Finding.Message { - fileprivate static func refactorIntoEmptyLiteral(replace: String, with: String) - -> Finding.Message - { + fileprivate static func refactorIntoEmptyLiteral( + replace: String, + with: String + ) -> Finding.Message { "replace '\(replace)' with '\(with)'" } } diff --git a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift index aac9deadb..214daa253 100644 --- a/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift +++ b/Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift @@ -56,7 +56,10 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { continue } diagnoseLowerCamelCaseViolations( - pat.identifier, allowUnderscores: false, description: identifierDescription(for: node)) + pat.identifier, + allowUnderscores: false, + description: identifierDescription(for: node) + ) } return .visitChildren } @@ -66,7 +69,10 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { return .visitChildren } diagnoseLowerCamelCaseViolations( - pattern.identifier, allowUnderscores: false, description: identifierDescription(for: node)) + pattern.identifier, + allowUnderscores: false, + description: identifierDescription(for: node) + ) return .visitChildren } @@ -75,15 +81,24 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { if let closureParamList = input.as(ClosureShorthandParameterListSyntax.self) { for param in closureParamList { diagnoseLowerCamelCaseViolations( - param.name, allowUnderscores: false, description: identifierDescription(for: node)) + param.name, + allowUnderscores: false, + description: identifierDescription(for: node) + ) } } else if let parameterClause = input.as(ClosureParameterClauseSyntax.self) { for param in parameterClause.parameters { diagnoseLowerCamelCaseViolations( - param.firstName, allowUnderscores: false, description: identifierDescription(for: node)) + param.firstName, + allowUnderscores: false, + description: identifierDescription(for: node) + ) if let secondName = param.secondName { diagnoseLowerCamelCaseViolations( - secondName, allowUnderscores: false, description: identifierDescription(for: node)) + secondName, + allowUnderscores: false, + description: identifierDescription(for: node) + ) } } } @@ -104,16 +119,24 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { let allowUnderscores = testCaseFuncs.contains(node) || node.hasAttribute("Test", inModule: "Testing") diagnoseLowerCamelCaseViolations( - node.name, allowUnderscores: allowUnderscores, - description: identifierDescription(for: node)) + node.name, + allowUnderscores: allowUnderscores, + description: identifierDescription(for: node) + ) for param in node.signature.parameterClause.parameters { // These identifiers aren't described using `identifierDescription(for:)` because no single // node can disambiguate the argument label from the parameter name. diagnoseLowerCamelCaseViolations( - param.firstName, allowUnderscores: false, description: "argument label") + param.firstName, + allowUnderscores: false, + description: "argument label" + ) if let paramName = param.secondName { diagnoseLowerCamelCaseViolations( - paramName, allowUnderscores: false, description: "function parameter") + paramName, + allowUnderscores: false, + description: "function parameter" + ) } } return .visitChildren @@ -121,7 +144,10 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { public override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind { diagnoseLowerCamelCaseViolations( - node.name, allowUnderscores: false, description: identifierDescription(for: node)) + node.name, + allowUnderscores: false, + description: identifierDescription(for: node) + ) return .skipChildren } @@ -153,7 +179,9 @@ public final class AlwaysUseLowerCamelCase: SyntaxLintRule { } private func diagnoseLowerCamelCaseViolations( - _ identifier: TokenSyntax, allowUnderscores: Bool, description: String + _ identifier: TokenSyntax, + allowUnderscores: Bool, + description: String ) { guard case .identifier(let text) = identifier.tokenKind else { return } if text.isEmpty { return } @@ -197,7 +225,8 @@ extension ReturnClauseSyntax { extension Finding.Message { fileprivate static func nameMustBeLowerCamelCase( - _ name: String, description: String + _ name: String, + description: String ) -> Finding.Message { "rename the \(description) '\(name)' using lowerCamelCase" } diff --git a/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift b/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift index ecd2ff257..72eb28cc3 100644 --- a/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift +++ b/Sources/SwiftFormat/Rules/AmbiguousTrailingClosureOverload.swift @@ -29,9 +29,11 @@ public final class AmbiguousTrailingClosureOverload: SyntaxLintRule { Finding.Note( message: .otherAmbiguousOverloadHere(decl.fullDeclName), location: Finding.Location( - decl.name.startLocation(converter: self.context.sourceLocationConverter)) + decl.name.startLocation(converter: self.context.sourceLocationConverter) + ) ) - }) + } + ) } } diff --git a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift index 92fefce8a..6caf28ddc 100644 --- a/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift +++ b/Sources/SwiftFormat/Rules/BeginDocumentationCommentWithOneLineSummary.swift @@ -11,10 +11,11 @@ //===----------------------------------------------------------------------===// import Foundation +import SwiftSyntax + #if os(macOS) import NaturalLanguage #endif -import SwiftSyntax /// All documentation comments must begin with a one-line summary of the declaration. /// @@ -123,50 +124,50 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { /// actual text). private func sentences(in text: String) -> (sentences: [String], trailingText: Substring) { #if os(macOS) - if BeginDocumentationCommentWithOneLineSummary._forcesFallbackModeForTesting { - return nonLinguisticSentenceApproximations(in: text) - } + if BeginDocumentationCommentWithOneLineSummary._forcesFallbackModeForTesting { + return nonLinguisticSentenceApproximations(in: text) + } - var sentences = [String]() - var tags = [NLTag]() - var tokenRanges = [Range]() - - let tagger = NLTagger(tagSchemes: [.lexicalClass]) - tagger.string = text - tagger.enumerateTags( - in: text.startIndex..]() + + let tagger = NLTagger(tagSchemes: [.lexicalClass]) + tagger.string = text + tagger.enumerateTags( + in: text.startIndex.. ( + private func nonLinguisticSentenceApproximations( + in text: String + ) -> ( sentences: [String], trailingText: Substring ) { // If we find a period followed by a space, then there is definitely one (approximate) sentence; @@ -216,15 +219,15 @@ public final class BeginDocumentationCommentWithOneLineSummary: SyntaxLintRule { } extension Finding.Message { - fileprivate static func terminateSentenceWithPeriod(_ text: Sentence) - -> Finding.Message - { + fileprivate static func terminateSentenceWithPeriod( + _ text: Sentence + ) -> Finding.Message { "terminate this sentence with a period: \"\(text)\"" } - fileprivate static func addBlankLineAfterFirstSentence(_ text: Sentence) - -> Finding.Message - { + fileprivate static func addBlankLineAfterFirstSentence( + _ text: Sentence + ) -> Finding.Message { "add a blank comment line after this sentence: \"\(text)\"" } } diff --git a/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift b/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift index bc1a8f2f3..e0f9af4c5 100644 --- a/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift +++ b/Sources/SwiftFormat/Rules/FileScopedDeclarationPrivacy.swift @@ -35,9 +35,9 @@ public final class FileScopedDeclarationPrivacy: SyntaxFormatRule { /// /// - Parameter codeBlockItems: The list of code block items to rewrite. /// - Returns: A new `CodeBlockItemListSyntax` that has possibly been rewritten. - private func rewrittenCodeBlockItems(_ codeBlockItems: CodeBlockItemListSyntax) - -> CodeBlockItemListSyntax - { + private func rewrittenCodeBlockItems( + _ codeBlockItems: CodeBlockItemListSyntax + ) -> CodeBlockItemListSyntax { let newCodeBlockItems = codeBlockItems.map { codeBlockItem -> CodeBlockItemSyntax in switch codeBlockItem.item { case .decl(let decl): diff --git a/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift index 98466fe2f..53273f676 100644 --- a/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift +++ b/Sources/SwiftFormat/Rules/FullyIndirectEnum.swift @@ -35,10 +35,15 @@ public final class FullyIndirectEnum: SyntaxFormatRule { Finding.Note( message: .removeIndirect, location: Finding.Location( - modifier.startLocation(converter: self.context.sourceLocationConverter))) + modifier.startLocation(converter: self.context.sourceLocationConverter) + ) + ) } diagnose( - .moveIndirectKeywordToEnumDecl(name: node.name.text), on: node.enumKeyword, notes: notes) + .moveIndirectKeywordToEnumDecl(name: node.name.text), + on: node.enumKeyword, + notes: notes + ) // Removes 'indirect' keyword from cases, reformats let newMembers = enumMembers.map { @@ -74,7 +79,12 @@ public final class FullyIndirectEnum: SyntaxFormatRule { let newModifier = DeclModifierSyntax( name: TokenSyntax.identifier( - "indirect", leadingTrivia: leadingTrivia, trailingTrivia: .spaces(1)), detail: nil) + "indirect", + leadingTrivia: leadingTrivia, + trailingTrivia: .spaces(1) + ), + detail: nil + ) newEnumDecl.modifiers = newEnumDecl.modifiers + [newModifier] newEnumDecl.memberBlock.members = MemberBlockItemListSyntax(newMembers) @@ -84,15 +94,17 @@ public final class FullyIndirectEnum: SyntaxFormatRule { /// Returns a value indicating whether all enum cases in the given list are indirect. /// /// Note that if the enum has no cases, this returns false. - private func indirectModifiersIfAllCasesIndirect(in members: MemberBlockItemListSyntax) - -> [DeclModifierSyntax] - { + private func indirectModifiersIfAllCasesIndirect( + in members: MemberBlockItemListSyntax + ) -> [DeclModifierSyntax] { var indirectModifiers = [DeclModifierSyntax]() for member in members { if let caseMember = member.decl.as(EnumCaseDeclSyntax.self) { - guard let indirectModifier = caseMember.modifiers.first( - where: { $0.name.text == "indirect" } - ) else { + guard + let indirectModifier = caseMember.modifiers.first( + where: { $0.name.text == "indirect" } + ) + else { return [] } indirectModifiers.append(indirectModifier) diff --git a/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift b/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift index 2da9d1290..913dc2a06 100644 --- a/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift +++ b/Sources/SwiftFormat/Rules/IdentifiersMustBeASCII.swift @@ -32,7 +32,8 @@ public final class IdentifiersMustBeASCII: SyntaxLintRule { extension Finding.Message { fileprivate static func nonASCIICharsNotAllowed( - _ invalidCharacters: [String], _ identifierName: String + _ invalidCharacters: [String], + _ identifierName: String ) -> Finding.Message { """ remove non-ASCII characters from '\(identifierName)': \ diff --git a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift index 7fc8ad341..009969ddc 100644 --- a/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift +++ b/Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift @@ -59,7 +59,9 @@ public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule { private func diagnoseImplicitWrapViolation(_ type: TypeSyntax) { guard let violation = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) else { return } diagnose( - .doNotUseImplicitUnwrapping(identifier: violation.wrappedType.trimmedDescription), on: type) + .doNotUseImplicitUnwrapping(identifier: violation.wrappedType.trimmedDescription), + on: type + ) } } diff --git a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift index aa3447c41..ea71fc510 100644 --- a/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift +++ b/Sources/SwiftFormat/Rules/NoAccessLevelOnExtensionDeclaration.swift @@ -89,11 +89,13 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule { // Create a note associated with each declaration that needs to have an access level modifier // added to it. - notes.append(Finding.Note( - message: .addModifierToExtensionMember(keyword: modifier.name.text), - location: - Finding.Location(decl.startLocation(converter: context.sourceLocationConverter)) - )) + notes.append( + Finding.Note( + message: .addModifierToExtensionMember(keyword: modifier.name.text), + location: + Finding.Location(decl.startLocation(converter: context.sourceLocationConverter)) + ) + ) var newItem = memberItem newItem.decl = applyingAccessModifierIfNone(modifier, to: decl) @@ -142,16 +144,28 @@ private func applyingAccessModifierIfNone( return applyingAccessModifierIfNone(modifier, to: funcDecl, declKeywordKeyPath: \.funcKeyword) case .structDecl(let structDecl): return applyingAccessModifierIfNone( - modifier, to: structDecl, declKeywordKeyPath: \.structKeyword) + modifier, + to: structDecl, + declKeywordKeyPath: \.structKeyword + ) case .subscriptDecl(let subscriptDecl): return applyingAccessModifierIfNone( - modifier, to: subscriptDecl, declKeywordKeyPath: \.subscriptKeyword) + modifier, + to: subscriptDecl, + declKeywordKeyPath: \.subscriptKeyword + ) case .typeAliasDecl(let typeAliasDecl): return applyingAccessModifierIfNone( - modifier, to: typeAliasDecl, declKeywordKeyPath: \.typealiasKeyword) + modifier, + to: typeAliasDecl, + declKeywordKeyPath: \.typealiasKeyword + ) case .variableDecl(let varDecl): return applyingAccessModifierIfNone( - modifier, to: varDecl, declKeywordKeyPath: \.bindingSpecifier) + modifier, + to: varDecl, + declKeywordKeyPath: \.bindingSpecifier + ) default: return decl } diff --git a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift index b51a950dd..62db9130d 100644 --- a/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift +++ b/Sources/SwiftFormat/Rules/NoAssignmentInExpressions.swift @@ -60,8 +60,8 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { var assignmentItem = CodeBlockItemSyntax(item: .expr(ExprSyntax(assignmentExpr))) assignmentItem.leadingTrivia = returnStmt.leadingTrivia - + returnStmt.returnKeyword.trailingTrivia.withoutLeadingSpaces() - + assignmentExpr.leadingTrivia + + returnStmt.returnKeyword.trailingTrivia.withoutLeadingSpaces() + + assignmentExpr.leadingTrivia assignmentItem.trailingTrivia = [] let trailingTrivia = returnStmt.trailingTrivia @@ -120,8 +120,7 @@ public final class NoAssignmentInExpressions: SyntaxFormatRule { /// `CodeBlockItem` parent. private func isStandaloneAssignmentStatement(_ node: InfixOperatorExprSyntax) -> Bool { var node = Syntax(node) - while - let parent = node.parent, + while let parent = node.parent, parent.is(TryExprSyntax.self) || parent.is(AwaitExprSyntax.self) { node = parent diff --git a/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift b/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift index 5fc32a5f0..4e5da2dcb 100644 --- a/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift +++ b/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift @@ -20,54 +20,54 @@ import SwiftSyntax @_spi(Rules) public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule { public override class var isOptIn: Bool { return true } - + public override func visit(_ node: AccessorBlockSyntax) -> AccessorBlockSyntax { var result = node switch node.accessors { - case .accessors(let accessors): - result.accessors = .init(rewritten(accessors)) - case .getter(let getter): - result.accessors = .init(rewritten(getter)) + case .accessors(let accessors): + result.accessors = .init(rewritten(accessors)) + case .getter(let getter): + result.accessors = .init(rewritten(getter)) } result.rightBrace = rewritten(node.rightBrace) return result } - + public override func visit(_ node: CodeBlockSyntax) -> CodeBlockSyntax { var result = node result.statements = rewritten(node.statements) result.rightBrace = rewritten(node.rightBrace) return result } - + public override func visit(_ node: MemberBlockSyntax) -> MemberBlockSyntax { var result = node result.members = rewritten(node.members) result.rightBrace = rewritten(node.rightBrace) return result } - + public override func visit(_ node: ClosureExprSyntax) -> ExprSyntax { var result = node result.statements = rewritten(node.statements) result.rightBrace = rewritten(node.rightBrace) return ExprSyntax(result) } - + public override func visit(_ node: SwitchExprSyntax) -> ExprSyntax { var result = node result.cases = rewritten(node.cases) result.rightBrace = rewritten(node.rightBrace) return ExprSyntax(result) } - + public override func visit(_ node: PrecedenceGroupDeclSyntax) -> DeclSyntax { var result = node result.attributes = rewritten(node.attributes) result.rightBrace = rewritten(node.rightBrace) return DeclSyntax(result) } - + func rewritten(_ token: TokenSyntax) -> TokenSyntax { let (trimmedLeadingTrivia, count) = token.leadingTrivia.trimmingSuperfluousNewlines() if trimmedLeadingTrivia.sourceLength != token.leadingTriviaLength { @@ -77,11 +77,11 @@ public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule { return token } } - + func rewritten(_ collection: C) -> C { var result = collection if let first = collection.first, first.leadingTrivia.containsNewlines, - let index = collection.index(of: first) + let index = collection.index(of: first) { let (trimmedLeadingTrivia, count) = first.leadingTrivia.trimmingSuperfluousNewlines() if trimmedLeadingTrivia.sourceLength != first.leadingTriviaLength { @@ -115,7 +115,7 @@ extension Trivia { // Retain other trivia pieces return partialResult + [piece] } - + return (Trivia(pieces: pieces), trimmmed) } } @@ -124,7 +124,7 @@ extension Finding.Message { fileprivate static func removeEmptyLinesAfter(_ count: Int) -> Finding.Message { "remove empty \(count > 1 ? "lines" : "line") after '{'" } - + fileprivate static func removeEmptyLinesBefore(_ count: Int) -> Finding.Message { "remove empty \(count > 1 ? "lines" : "line") before '}'" } diff --git a/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift b/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift index 47b948e26..3cacd29e0 100644 --- a/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift +++ b/Sources/SwiftFormat/Rules/NoVoidReturnOnFunctionSignature.swift @@ -42,9 +42,9 @@ public final class NoVoidReturnOnFunctionSignature: SyntaxFormatRule { } /// Returns a copy of the given function signature with the return clause removed. - private func removingReturnClause(from signature: FunctionSignatureSyntax) - -> FunctionSignatureSyntax - { + private func removingReturnClause( + from signature: FunctionSignatureSyntax + ) -> FunctionSignatureSyntax { var result = signature result.returnClause = nil return result diff --git a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift index 1456a58c4..63c65fc2a 100644 --- a/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift +++ b/Sources/SwiftFormat/Rules/OmitExplicitReturns.swift @@ -27,8 +27,9 @@ public final class OmitExplicitReturns: SyntaxFormatRule { // func () -> { return ... } guard var funcDecl = decl.as(FunctionDeclSyntax.self), - let body = funcDecl.body, - let returnStmt = containsSingleReturn(body.statements) else { + let body = funcDecl.body, + let returnStmt = containsSingleReturn(body.statements) + else { return decl } @@ -41,10 +42,11 @@ public final class OmitExplicitReturns: SyntaxFormatRule { let decl = super.visit(node) guard var subscriptDecl = decl.as(SubscriptDeclSyntax.self), - let accessorBlock = subscriptDecl.accessorBlock, - // We are assuming valid Swift code here where only - // one `get { ... }` is allowed. - let transformed = transformAccessorBlock(accessorBlock) else { + let accessorBlock = subscriptDecl.accessorBlock, + // We are assuming valid Swift code here where only + // one `get { ... }` is allowed. + let transformed = transformAccessorBlock(accessorBlock) + else { return decl } @@ -56,7 +58,8 @@ public final class OmitExplicitReturns: SyntaxFormatRule { var binding = node guard let accessorBlock = binding.accessorBlock, - let transformed = transformAccessorBlock(accessorBlock) else { + let transformed = transformAccessorBlock(accessorBlock) + else { return node } @@ -69,8 +72,9 @@ public final class OmitExplicitReturns: SyntaxFormatRule { // test { return ... } guard var closureExpr = expr.as(ClosureExprSyntax.self), - let returnStmt = containsSingleReturn(closureExpr.statements) else { - return expr + let returnStmt = containsSingleReturn(closureExpr.statements) + else { + return expr } closureExpr.statements = rewrapReturnedExpression(returnStmt) @@ -83,20 +87,25 @@ public final class OmitExplicitReturns: SyntaxFormatRule { // one `get { ... }` is allowed. switch accessorBlock.accessors { case .accessors(var accessors): - guard var getter = accessors.filter({ - $0.accessorSpecifier.tokenKind == .keyword(.get) - }).first else { + guard + var getter = accessors.filter({ + $0.accessorSpecifier.tokenKind == .keyword(.get) + }).first + else { return nil } guard let body = getter.body, - let returnStmt = containsSingleReturn(body.statements) else { + let returnStmt = containsSingleReturn(body.statements) + else { return nil } - guard let getterAt = accessors.firstIndex(where: { - $0.accessorSpecifier.tokenKind == .keyword(.get) - }) else { + guard + let getterAt = accessors.firstIndex(where: { + $0.accessorSpecifier.tokenKind == .keyword(.get) + }) + else { return nil } @@ -124,8 +133,8 @@ public final class OmitExplicitReturns: SyntaxFormatRule { private func containsSingleReturn(_ body: CodeBlockItemListSyntax) -> ReturnStmtSyntax? { guard let element = body.firstAndOnly, - let returnStmt = element.item.as(ReturnStmtSyntax.self) else - { + let returnStmt = element.item.as(ReturnStmtSyntax.self) + else { return nil } @@ -138,7 +147,8 @@ public final class OmitExplicitReturns: SyntaxFormatRule { leadingTrivia: returnStmt.leadingTrivia, item: .expr(returnStmt.expression!), semicolon: nil, - trailingTrivia: returnStmt.trailingTrivia) + trailingTrivia: returnStmt.trailingTrivia + ) ]) } } diff --git a/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift index 321e5948e..0387942fa 100644 --- a/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift +++ b/Sources/SwiftFormat/Rules/OneVariableDeclarationPerLine.swift @@ -51,7 +51,8 @@ public final class OneVariableDeclarationPerLine: SyntaxFormatRule { var splitter = VariableDeclSplitter { CodeBlockItemSyntax( item: .decl(DeclSyntax($0)), - semicolon: nil) + semicolon: nil + ) } newItems.append(contentsOf: splitter.nodes(bySplitting: visitedDecl)) } @@ -214,4 +215,3 @@ private struct VariableDeclSplitter { bindingQueue = [] } } - diff --git a/Sources/SwiftFormat/Rules/OrderedImports.swift b/Sources/SwiftFormat/Rules/OrderedImports.swift index 6d2475a1b..75fcb4572 100644 --- a/Sources/SwiftFormat/Rules/OrderedImports.swift +++ b/Sources/SwiftFormat/Rules/OrderedImports.swift @@ -168,7 +168,8 @@ public final class OrderedImports: SyntaxFormatRule { switch lineType { case .regularImport, .declImport: diagnose( - .groupImports(before: lineType, after: LineType.testableImport), on: line.firstToken + .groupImports(before: lineType, after: LineType.testableImport), + on: line.firstToken ) default: () } @@ -178,7 +179,8 @@ public final class OrderedImports: SyntaxFormatRule { switch lineType { case .regularImport: diagnose( - .groupImports(before: lineType, after: LineType.declImport), on: line.firstToken + .groupImports(before: lineType, after: LineType.declImport), + on: line.firstToken ) default: () } @@ -281,9 +283,10 @@ fileprivate func joinLines(_ inputLineLists: [Line]...) -> [Line] { /// This function transforms the statements in a CodeBlockItemListSyntax object into a list of Line /// objects. Blank lines and standalone comments are represented by their own Line object. Code with /// a trailing comment are represented together in the same Line. -fileprivate func generateLines(codeBlockItemList: CodeBlockItemListSyntax, context: Context) - -> [Line] -{ +fileprivate func generateLines( + codeBlockItemList: CodeBlockItemListSyntax, + context: Context +) -> [Line] { var lines: [Line] = [] var currentLine = Line() diff --git a/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift b/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift index 120b69967..14e6dcbff 100644 --- a/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift +++ b/Sources/SwiftFormat/Rules/ReplaceForEachWithForLoop.swift @@ -16,12 +16,11 @@ import SwiftSyntax /// /// Lint: invalid use of `forEach` yield will yield a lint error. @_spi(Rules) -public final class ReplaceForEachWithForLoop : SyntaxLintRule { +public final class ReplaceForEachWithForLoop: SyntaxLintRule { public override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { // We are only interested in calls with a single trailing closure // argument. - if !node.arguments.isEmpty || node.trailingClosure == nil || - !node.additionalTrailingClosures.isEmpty { + if !node.arguments.isEmpty || node.trailingClosure == nil || !node.additionalTrailingClosures.isEmpty { return .visitChildren } diff --git a/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift b/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift index e98d67cdd..977c9bfe0 100644 --- a/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift +++ b/Sources/SwiftFormat/Rules/ReturnVoidInsteadOfEmptyTuple.swift @@ -35,7 +35,8 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { // still diagnose it as a lint error but we don't replace it because it's not obvious where the // comment should go. if hasNonWhitespaceTrivia(returnType.leftParen, at: .trailing) - || hasNonWhitespaceTrivia(returnType.rightParen, at: .leading) { + || hasNonWhitespaceTrivia(returnType.rightParen, at: .leading) + { return super.visit(node) } @@ -64,7 +65,8 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { // still diagnose it as a lint error but we don't replace it because it's not obvious where the // comment should go. if hasNonWhitespaceTrivia(returnType.leftParen, at: .trailing) - || hasNonWhitespaceTrivia(returnType.rightParen, at: .leading) { + || hasNonWhitespaceTrivia(returnType.rightParen, at: .leading) + { return super.visit(node) } @@ -107,14 +109,15 @@ public final class ReturnVoidInsteadOfEmptyTuple: SyntaxFormatRule { /// Returns a type syntax node with the identifier `Void` whose leading and trailing trivia have /// been copied from the tuple type syntax node it is replacing. - private func makeVoidIdentifierType(toReplace node: TupleTypeSyntax) -> IdentifierTypeSyntax - { + private func makeVoidIdentifierType(toReplace node: TupleTypeSyntax) -> IdentifierTypeSyntax { return IdentifierTypeSyntax( name: TokenSyntax.identifier( "Void", leadingTrivia: node.firstToken(viewMode: .sourceAccurate)?.leadingTrivia ?? [], - trailingTrivia: node.lastToken(viewMode: .sourceAccurate)?.trailingTrivia ?? []), - genericArgumentClause: nil) + trailingTrivia: node.lastToken(viewMode: .sourceAccurate)?.trailingTrivia ?? [] + ), + genericArgumentClause: nil + ) } } diff --git a/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift b/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift index 3fdb53828..44912bad7 100644 --- a/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift +++ b/Sources/SwiftFormat/Rules/TypeNamesShouldBeCapitalized.swift @@ -16,7 +16,7 @@ import SwiftSyntax /// /// Lint: Types with un-capitalized names will yield a lint error. @_spi(Rules) -public final class TypeNamesShouldBeCapitalized : SyntaxLintRule { +public final class TypeNamesShouldBeCapitalized: SyntaxLintRule { public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { diagnoseNameConventionMismatch(node, name: node.name, kind: "struct") return .visitChildren @@ -59,7 +59,8 @@ public final class TypeNamesShouldBeCapitalized : SyntaxLintRule { ) { let leadingUnderscores = name.text.prefix { $0 == "_" } if let firstChar = name.text[leadingUnderscores.endIndex...].first, - firstChar.uppercased() != String(firstChar) { + firstChar.uppercased() != String(firstChar) + { diagnose(.capitalizeTypeName(name: name.text, kind: kind), on: name, severity: .convention) } } diff --git a/Sources/SwiftFormat/Rules/UseEarlyExits.swift b/Sources/SwiftFormat/Rules/UseEarlyExits.swift index 15f935b35..3f1e53912 100644 --- a/Sources/SwiftFormat/Rules/UseEarlyExits.swift +++ b/Sources/SwiftFormat/Rules/UseEarlyExits.swift @@ -69,12 +69,14 @@ public final class UseEarlyExits: SyntaxFormatRule { let guardKeyword = TokenSyntax.keyword( .guard, leadingTrivia: ifStatement.ifKeyword.leadingTrivia, - trailingTrivia: .spaces(1)) + trailingTrivia: .spaces(1) + ) let guardStatement = GuardStmtSyntax( guardKeyword: guardKeyword, conditions: ifStatement.conditions, elseKeyword: TokenSyntax.keyword(.else, trailingTrivia: .spaces(1)), - body: visit(elseBody)) + body: visit(elseBody) + ) newItems.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(guardStatement)))) diff --git a/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift index 2ff1ceee8..6e7e3e6c0 100644 --- a/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift +++ b/Sources/SwiftFormat/Rules/UseExplicitNilCheckInConditions.swift @@ -49,7 +49,8 @@ public final class UseExplicitNilCheckInConditions: SyntaxFormatRule { var inequalExpr = InfixOperatorExprSyntax( leftOperand: addingParenthesesIfNecessary(to: value), operator: operatorExpr, - rightOperand: NilLiteralExprSyntax()) + rightOperand: NilLiteralExprSyntax() + ) inequalExpr.leadingTrivia = node.leadingTrivia inequalExpr.trailingTrivia = trailingTrivia @@ -103,8 +104,7 @@ public final class UseExplicitNilCheckInConditions: SyntaxFormatRule { // Note that we could also cover the `tryExpr` and `ternaryExpr` cases above with this, but // this reparsing trick is going to be slower so we should avoid it whenever we can. let reparsedExpr = "\(expr) != nil" as ExprSyntax - if - let infixExpr = reparsedExpr.as(InfixOperatorExprSyntax.self), + if let infixExpr = reparsedExpr.as(InfixOperatorExprSyntax.self), let binOp = infixExpr.operator.as(BinaryOperatorExprSyntax.self), binOp.operator.text == "!=", infixExpr.rightOperand.is(NilLiteralExprSyntax.self) diff --git a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift index ea48c5899..dc101a018 100644 --- a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift @@ -54,7 +54,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = shorthandArrayType( element: typeArgument.argument, leadingTrivia: leadingTrivia, - trailingTrivia: trailingTrivia) + trailingTrivia: trailingTrivia + ) case "Dictionary": guard let typeArguments = exactlyTwoChildren(of: genericArgumentList) else { @@ -65,7 +66,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { key: typeArguments.0.argument, value: typeArguments.1.argument, leadingTrivia: leadingTrivia, - trailingTrivia: trailingTrivia) + trailingTrivia: trailingTrivia + ) case "Optional": guard !isTypeOfUninitializedStoredVar(node) else { @@ -79,7 +81,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = shorthandOptionalType( wrapping: typeArgument.argument, leadingTrivia: leadingTrivia, - trailingTrivia: trailingTrivia) + trailingTrivia: trailingTrivia + ) default: newNode = nil @@ -141,7 +144,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let arrayTypeExpr = makeArrayTypeExpression( elementType: typeArgument.argument, leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), - rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) + rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia) + ) newNode = ExprSyntax(arrayTypeExpr) case "Dictionary": @@ -154,7 +158,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { valueType: typeArguments.1.argument, leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), colon: TokenSyntax.colonToken(trailingTrivia: .spaces(1)), - rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) + rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia) + ) newNode = ExprSyntax(dictTypeExpr) case "Optional": @@ -165,7 +170,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let optionalTypeExpr = makeOptionalTypeExpression( wrapping: typeArgument.argument, leadingTrivia: leadingTrivia, - questionMark: TokenSyntax.postfixQuestionMarkToken(trailingTrivia: trailingTrivia)) + questionMark: TokenSyntax.postfixQuestionMarkToken(trailingTrivia: trailingTrivia) + ) newNode = ExprSyntax(optionalTypeExpr) default: @@ -187,9 +193,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { /// Returns the two arguments in the given argument list, if there are exactly two elements; /// otherwise, it returns nil. - private func exactlyTwoChildren(of argumentList: GenericArgumentListSyntax) - -> (GenericArgumentSyntax, GenericArgumentSyntax)? - { + private func exactlyTwoChildren( + of argumentList: GenericArgumentListSyntax + ) -> (GenericArgumentSyntax, GenericArgumentSyntax)? { var iterator = argumentList.makeIterator() guard let first = iterator.next() else { return nil } guard let second = iterator.next() else { return nil } @@ -207,7 +213,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let result = ArrayTypeSyntax( leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), element: element, - rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) + rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia) + ) return TypeSyntax(result) } @@ -224,7 +231,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { key: key, colon: TokenSyntax.colonToken(trailingTrivia: .spaces(1)), value: value, - rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia)) + rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia) + ) return TypeSyntax(result) } @@ -258,7 +266,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let optionalType = OptionalTypeSyntax( wrappedType: wrappedType, - questionMark: TokenSyntax.postfixQuestionMarkToken(trailingTrivia: trailingTrivia)) + questionMark: TokenSyntax.postfixQuestionMarkToken(trailingTrivia: trailingTrivia) + ) return TypeSyntax(optionalType) } @@ -276,9 +285,10 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { return ArrayExprSyntax( leftSquare: leftSquare, elements: ArrayElementListSyntax([ - ArrayElementSyntax(expression: elementTypeExpr, trailingComma: nil), + ArrayElementSyntax(expression: elementTypeExpr, trailingComma: nil) ]), - rightSquare: rightSquare) + rightSquare: rightSquare + ) } /// Returns a `DictionaryExprSyntax` whose single key/value pair is the expression representations @@ -302,12 +312,14 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { key: keyTypeExpr, colon: colon, value: valueTypeExpr, - trailingComma: nil), + trailingComma: nil + ) ]) return DictionaryExprSyntax( leftSquare: leftSquare, content: .elements(dictElementList), - rightSquare: rightSquare) + rightSquare: rightSquare + ) } /// Returns an `OptionalChainingExprSyntax` whose wrapped expression is the expression @@ -337,7 +349,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { return OptionalChainingExprSyntax( expression: wrappedTypeExpr, - questionMark: questionMark) + questionMark: questionMark + ) } /// Returns the given type wrapped in parentheses. @@ -349,7 +362,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let tupleType = TupleTypeSyntax( leftParen: .leftParenToken(leadingTrivia: leadingTrivia), elements: TupleTypeElementListSyntax([tupleTypeElement]), - rightParen: .rightParenToken()) + rightParen: .rightParenToken() + ) return TypeSyntax(tupleType) } @@ -362,7 +376,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let tupleExpr = TupleExprSyntax( leftParen: .leftParenToken(leadingTrivia: leadingTrivia), elements: LabeledExprListSyntax([tupleExprElement]), - rightParen: .rightParenToken()) + rightParen: .rightParenToken() + ) return ExprSyntax(tupleExpr) } @@ -377,7 +392,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { case .identifierType(let simpleTypeIdentifier): let identifierExpr = DeclReferenceExprSyntax( baseName: simpleTypeIdentifier.name, - argumentNames: nil) + argumentNames: nil + ) // If the type has a generic argument clause, we need to construct a `SpecializeExpr` to wrap // the identifier and the generic arguments. Otherwise, we can return just the @@ -386,7 +402,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let newGenericArgumentClause = visit(genericArgumentClause) let result = GenericSpecializationExprSyntax( expression: ExprSyntax(identifierExpr), - genericArgumentClause: newGenericArgumentClause) + genericArgumentClause: newGenericArgumentClause + ) return ExprSyntax(result) } else { return ExprSyntax(identifierExpr) @@ -407,7 +424,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let result = makeArrayTypeExpression( elementType: arrayType.element, leftSquare: arrayType.leftSquare, - rightSquare: arrayType.rightSquare) + rightSquare: arrayType.rightSquare + ) return ExprSyntax(result) case .dictionaryType(let dictionaryType): @@ -416,14 +434,16 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { valueType: dictionaryType.value, leftSquare: dictionaryType.leftSquare, colon: dictionaryType.colon, - rightSquare: dictionaryType.rightSquare) + rightSquare: dictionaryType.rightSquare + ) return ExprSyntax(result) case .optionalType(let optionalType): let result = makeOptionalTypeExpression( wrapping: optionalType.wrappedType, leadingTrivia: optionalType.firstToken(viewMode: .sourceAccurate)?.leadingTrivia ?? [], - questionMark: optionalType.questionMark) + questionMark: optionalType.questionMark + ) return ExprSyntax(result) case .functionType(let functionType): @@ -442,7 +462,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let result = TupleExprSyntax( leftParen: tupleType.leftParen, elements: elementExprs, - rightParen: tupleType.rightParen) + rightParen: tupleType.rightParen + ) return ExprSyntax(result) case .someOrAnyType(let someOrAnyType): @@ -456,9 +477,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { } } - private func expressionRepresentation(of tupleTypeElements: TupleTypeElementListSyntax) - -> LabeledExprListSyntax? - { + private func expressionRepresentation( + of tupleTypeElements: TupleTypeElementListSyntax + ) -> LabeledExprListSyntax? { guard !tupleTypeElements.isEmpty else { return nil } var exprElements = [LabeledExprSyntax]() @@ -469,7 +490,9 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { label: typeElement.firstName, colon: typeElement.colon, expression: elementExpr, - trailingComma: typeElement.trailingComma)) + trailingComma: typeElement.trailingComma + ) + ) } return LabeledExprListSyntax(exprElements) } @@ -492,23 +515,26 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { let tupleExpr = TupleExprSyntax( leftParen: leftParen, elements: parameterExprs, - rightParen: rightParen) + rightParen: rightParen + ) let arrowExpr = ArrowExprSyntax( effectSpecifiers: effectSpecifiers, - arrow: arrow) + arrow: arrow + ) return InfixOperatorExprSyntax( leftOperand: tupleExpr, operator: arrowExpr, - rightOperand: returnTypeExpr) + rightOperand: returnTypeExpr + ) } /// Returns the leading and trailing trivia from the front and end of the entire given node. /// /// In other words, this is the leading trivia from the first token of the node and the trailing /// trivia from the last token. - private func boundaryTrivia(around node: Syntax) - -> (leadingTrivia: Trivia, trailingTrivia: Trivia) - { + private func boundaryTrivia( + around node: Syntax + ) -> (leadingTrivia: Trivia, trailingTrivia: Trivia) { return ( leadingTrivia: node.firstToken(viewMode: .sourceAccurate)?.leadingTrivia ?? [], trailingTrivia: node.lastToken(viewMode: .sourceAccurate)?.trailingTrivia ?? [] @@ -558,7 +584,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { isStoredProperty(patternBinding), patternBinding.initializer == nil, let variableDecl = nearestAncestor(of: patternBinding, type: VariableDeclSyntax.self), - variableDecl.bindingSpecifier.tokenKind == .keyword(.var) + variableDecl.bindingSpecifier.tokenKind == .keyword(.var) { return true } diff --git a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift index 80614b64a..a523dd47c 100644 --- a/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift +++ b/Sources/SwiftFormat/Rules/UseSynthesizedInitializer.swift @@ -50,13 +50,16 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { initializer.attributes.isEmpty, matchesPropertyList( parameters: initializer.signature.parameterClause.parameters, - properties: storedProperties), + properties: storedProperties + ), matchesAssignmentBody( variables: storedProperties, - initBody: initializer.body), + initBody: initializer.body + ), matchesAccessLevel( modifiers: initializer.modifiers, - properties: storedProperties) + properties: storedProperties + ) else { continue } @@ -83,7 +86,8 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { /// - properties: The properties from the enclosing type. /// - Returns: Whether the initializer has the same access level as the synthesized initializer. private func matchesAccessLevel( - modifiers: DeclModifierListSyntax?, properties: [VariableDeclSyntax] + modifiers: DeclModifierListSyntax?, + properties: [VariableDeclSyntax] ) -> Bool { let synthesizedAccessLevel = synthesizedInitAccessLevel(using: properties) let accessLevel = modifiers?.accessLevelModifier @@ -125,8 +129,11 @@ public final class UseSynthesizedInitializer: SyntaxLintRule { if propertyId.identifier.text != parameter.firstName.text || propertyType.description.trimmingCharacters( - in: .whitespaces) != parameter.type.description.trimmingCharacters(in: .whitespacesAndNewlines) - { return false } + in: .whitespaces + ) != parameter.type.description.trimmingCharacters(in: .whitespacesAndNewlines) + { + return false + } } return true } @@ -209,10 +216,10 @@ fileprivate func synthesizedInitAccessLevel(using properties: [VariableDeclSynta var hasFileprivate = false for property in properties { // Private takes precedence, so finding 1 private property defines the access level. - if property.modifiers.contains(where: {$0.name.tokenKind == .keyword(.private) && $0.detail == nil}) { + if property.modifiers.contains(where: { $0.name.tokenKind == .keyword(.private) && $0.detail == nil }) { return .private } - if property.modifiers.contains(where: {$0.name.tokenKind == .keyword(.fileprivate) && $0.detail == nil}) { + if property.modifiers.contains(where: { $0.name.tokenKind == .keyword(.fileprivate) && $0.detail == nil }) { hasFileprivate = true // Can't break here because a later property might be private. } diff --git a/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift index b8224b51c..6671879e4 100644 --- a/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift @@ -55,7 +55,7 @@ public final class UseWhereClausesInForLoops: SyntaxFormatRule { case .expressionStmt(let exprStmt): switch Syntax(exprStmt.expression).as(SyntaxEnum.self) { case .ifExpr(let ifExpr) - where ifExpr.conditions.count == 1 + where ifExpr.conditions.count == 1 && ifExpr.elseKeyword == nil && forInStmt.body.statements.count == 1: // Extract the condition of the IfExpr. @@ -105,7 +105,8 @@ fileprivate func updateWithWhereCondition( if lastToken?.trailingTrivia.containsSpaces == false { whereLeadingTrivia = .spaces(1) } - let whereKeyword = TokenSyntax.keyword(.where, + let whereKeyword = TokenSyntax.keyword( + .where, leadingTrivia: whereLeadingTrivia, trailingTrivia: .spaces(1) ) diff --git a/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift index 8bb5b117b..e99d1556e 100644 --- a/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift @@ -29,13 +29,19 @@ public final class ValidateDocumentationComments: SyntaxLintRule { public override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { return checkFunctionLikeDocumentation( - DeclSyntax(node), name: "init", signature: node.signature) + DeclSyntax(node), + name: "init", + signature: node.signature + ) } public override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { return checkFunctionLikeDocumentation( - DeclSyntax(node), name: node.name.text, signature: node.signature, - returnClause: node.signature.returnClause) + DeclSyntax(node), + name: node.name.text, + signature: node.signature, + returnClause: node.signature.returnClause + ) } private func checkFunctionLikeDocumentation( @@ -65,12 +71,14 @@ public final class ValidateDocumentationComments: SyntaxLintRule { signature.effectSpecifiers?.throwsClause?.throwsSpecifier, name: name, throwsDescription: docComment.throws, - node: node) + node: node + ) validateReturn( returnClause, name: name, returnsDescription: docComment.returns, - node: node) + node: node + ) let funcParameters = funcParametersIdentifiers(in: signature.parameterClause.parameters) // If the documentation of the parameters is wrong 'docCommentInfo' won't @@ -107,7 +115,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { diagnose(.removeReturnComment(funcName: name), on: node) } else if let returnClause = returnClause, returnsDescription == nil { if let returnTypeIdentifier = returnClause.type.as(IdentifierTypeSyntax.self), - returnTypeIdentifier.name.text == "Never" + returnTypeIdentifier.name.text == "Never" { return } @@ -131,7 +139,8 @@ public final class ValidateDocumentationComments: SyntaxLintRule { if !needsThrowsDesc && throwsDescription != nil { diagnose( .removeThrowsComment(funcName: name), - on: throwsOrRethrowsKeyword ?? node.firstToken(viewMode: .sourceAccurate)) + on: throwsOrRethrowsKeyword ?? node.firstToken(viewMode: .sourceAccurate) + ) } else if needsThrowsDesc && throwsDescription == nil { diagnose(.documentErrorsThrown(funcName: name), on: throwsOrRethrowsKeyword) } diff --git a/Sources/SwiftFormat/Utilities/FileIterator.swift b/Sources/SwiftFormat/Utilities/FileIterator.swift index d274d71c3..bd3a2c0f0 100644 --- a/Sources/SwiftFormat/Utilities/FileIterator.swift +++ b/Sources/SwiftFormat/Utilities/FileIterator.swift @@ -86,7 +86,8 @@ public struct FileIterator: Sequence, IteratorProtocol { dirIterator = FileManager.default.enumerator( at: next, includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles]) + options: [.skipsHiddenFiles] + ) currentDirectory = next default: @@ -114,7 +115,7 @@ public struct FileIterator: Sequence, IteratorProtocol { break } #if os(Windows) - // Windows does not consider files and directories starting with `.` as hidden but we don't want to traverse + // Windows does not consider files and directories starting with `.` as hidden but we don't want to traverse // into eg. `.build`. Manually skip any items starting with `.`. if item.lastPathComponent.hasPrefix(".") { dirIterator?.skipDescendants() diff --git a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift index 1c8054d23..cb4e07267 100644 --- a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift +++ b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift @@ -1,9 +1,8 @@ import SwiftFormat +@_spi(Rules) @_spi(Testing) import SwiftFormat import SwiftSyntax import XCTest -@_spi(Rules) @_spi(Testing) import SwiftFormat - /// DiagnosingTestCase is an XCTestCase subclass meant to inject diagnostic-specific testing /// routines into specific formatting test cases. open class DiagnosingTestCase: XCTestCase { @@ -25,7 +24,8 @@ open class DiagnosingTestCase: XCTestCase { fileURL: URL(fileURLWithPath: "/tmp/test.swift"), selection: selection, sourceFileSyntax: sourceFileSyntax, - ruleNameCache: ruleNameCache) + ruleNameCache: ruleNameCache + ) return context } @@ -49,7 +49,8 @@ open class DiagnosingTestCase: XCTestCase { emittedFindings: &emittedFindings, context: context, file: file, - line: line) + line: line + ) } // Emit test failures for any findings that did not have matches. @@ -63,7 +64,8 @@ open class DiagnosingTestCase: XCTestCase { XCTFail( "Unexpected finding '\(finding.message)' was emitted (\(locationString))", file: file, - line: line) + line: line + ) } } @@ -97,7 +99,8 @@ open class DiagnosingTestCase: XCTestCase { (line:col \(markerLocation.line):\(markerLocation.column), offset \(utf8Offset)) """, file: file, - line: line) + line: line + ) return } @@ -112,7 +115,8 @@ open class DiagnosingTestCase: XCTestCase { had the wrong message """, file: file, - line: line) + line: line + ) // Assert that a note exists for each of the expected nodes in the finding. var emittedNotes = matchedFinding.notes @@ -123,7 +127,8 @@ open class DiagnosingTestCase: XCTestCase { emittedNotes: &emittedNotes, context: context, file: file, - line: line) + line: line + ) } // Emit test failures for any notes that weren't specified. @@ -137,7 +142,8 @@ open class DiagnosingTestCase: XCTestCase { XCTFail( "Unexpected note '\(note.message)' was emitted (\(locationString))", file: file, - line: line) + line: line + ) } } @@ -170,7 +176,8 @@ open class DiagnosingTestCase: XCTestCase { (line:col \(markerLocation.line):\(markerLocation.column), offset \(utf8Offset)) """, file: file, - line: line) + line: line + ) return } @@ -185,7 +192,8 @@ open class DiagnosingTestCase: XCTestCase { had the wrong message """, file: file, - line: line) + line: line + ) } /// Asserts that the two strings are equal, providing Unix `diff`-style output if they are not. diff --git a/Sources/_SwiftFormatTestSupport/MarkedText.swift b/Sources/_SwiftFormatTestSupport/MarkedText.swift index 071a7540a..9d63a4812 100644 --- a/Sources/_SwiftFormatTestSupport/MarkedText.swift +++ b/Sources/_SwiftFormatTestSupport/MarkedText.swift @@ -10,8 +10,8 @@ // //===----------------------------------------------------------------------===// -import SwiftSyntax import SwiftFormat +import SwiftSyntax /// Encapsulates the locations of emoji markers extracted from source text. public struct MarkedText { @@ -38,7 +38,7 @@ public struct MarkedText { if marker.name == "⏩" { lastRangeStart = text.utf8.count } else if marker.name == "⏪" { - offsets.append(lastRangeStart ..< text.utf8.count) + offsets.append(lastRangeStart.. SyntaxVisitorContinueKind { - """) + """ + ) for ruleName in lintRules.sorted() { handle.write( """ visitIfEnabled(\(ruleName).visit, for: node) - """) + """ + ) } handle.write( @@ -88,27 +90,29 @@ final class PipelineGenerator: FileGenerator { return .visitChildren } - """) + """ + ) - handle.write( - """ - override func visitPost(_ node: \(nodeType)) { - - """ - ) - for ruleName in lintRules.sorted() { - handle.write( - """ - onVisitPost(rule: \(ruleName).self, for: node) + handle.write( + """ + override func visitPost(_ node: \(nodeType)) { - """) - } + """ + ) + for ruleName in lintRules.sorted() { handle.write( """ - } + onVisitPost(rule: \(ruleName).self, for: node) """ ) + } + handle.write( + """ + } + + """ + ) } handle.write( @@ -128,7 +132,8 @@ final class PipelineGenerator: FileGenerator { """ node = \(ruleName)(context: context).rewrite(node) - """) + """ + ) } handle.write( @@ -137,6 +142,7 @@ final class PipelineGenerator: FileGenerator { } } - """) + """ + ) } } diff --git a/Sources/generate-swift-format/RuleCollector.swift b/Sources/generate-swift-format/RuleCollector.swift index d3564f11c..f39dc66e0 100644 --- a/Sources/generate-swift-format/RuleCollector.swift +++ b/Sources/generate-swift-format/RuleCollector.swift @@ -11,10 +11,9 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftSyntax -import SwiftParser - @_spi(Rules) import SwiftFormat +import SwiftParser +import SwiftSyntax /// Collects information about rules in the formatter code base. final class RuleCollector { @@ -149,7 +148,8 @@ final class RuleCollector { description: description?.text, visitedNodes: visitedNodes, canFormat: canFormat, - isOptIn: ruleType.isOptIn) + isOptIn: ruleType.isOptIn + ) } return nil diff --git a/Sources/generate-swift-format/RuleDocumentationGenerator.swift b/Sources/generate-swift-format/RuleDocumentationGenerator.swift index c90cc62e4..eb2375f26 100644 --- a/Sources/generate-swift-format/RuleDocumentationGenerator.swift +++ b/Sources/generate-swift-format/RuleDocumentationGenerator.swift @@ -44,27 +44,30 @@ final class RuleDocumentationGenerator: FileGenerator { ) for detectedRule in ruleCollector.allLinters.sorted(by: { $0.typeName < $1.typeName }) { - handle.write(""" - - [\(detectedRule.typeName)](#\(detectedRule.typeName)) + handle.write( + """ + - [\(detectedRule.typeName)](#\(detectedRule.typeName)) - """) + """ + ) } for detectedRule in ruleCollector.allLinters.sorted(by: { $0.typeName < $1.typeName }) { - handle.write(""" + handle.write( + """ - ### \(detectedRule.typeName) + ### \(detectedRule.typeName) - \(detectedRule.description ?? "") - \(ruleFormatSupportDescription(for: detectedRule)) + \(detectedRule.description ?? "") + \(ruleFormatSupportDescription(for: detectedRule)) - """) + """ + ) } } private func ruleFormatSupportDescription(for rule: RuleCollector.DetectedRule) -> String { - return rule.canFormat ? - "`\(rule.typeName)` rule can format your code automatically." : - "`\(rule.typeName)` is a linter-only rule." + return rule.canFormat + ? "`\(rule.typeName)` rule can format your code automatically." : "`\(rule.typeName)` is a linter-only rule." } } diff --git a/Sources/generate-swift-format/RuleNameCacheGenerator.swift b/Sources/generate-swift-format/RuleNameCacheGenerator.swift index 55db7063b..b6be4ff96 100644 --- a/Sources/generate-swift-format/RuleNameCacheGenerator.swift +++ b/Sources/generate-swift-format/RuleNameCacheGenerator.swift @@ -53,4 +53,3 @@ final class RuleNameCacheGenerator: FileGenerator { handle.write("]\n") } } - diff --git a/Sources/generate-swift-format/main.swift b/Sources/generate-swift-format/main.swift index 75df61f9e..ea40bcd1b 100644 --- a/Sources/generate-swift-format/main.swift +++ b/Sources/generate-swift-format/main.swift @@ -16,24 +16,29 @@ import SwiftSyntax let sourcesDirectory = URL(fileURLWithPath: #file) .deletingLastPathComponent() .deletingLastPathComponent() -let rulesDirectory = sourcesDirectory +let rulesDirectory = + sourcesDirectory .appendingPathComponent("SwiftFormat") .appendingPathComponent("Rules") -let pipelineFile = sourcesDirectory +let pipelineFile = + sourcesDirectory .appendingPathComponent("SwiftFormat") .appendingPathComponent("Core") .appendingPathComponent("Pipelines+Generated.swift") -let ruleRegistryFile = sourcesDirectory +let ruleRegistryFile = + sourcesDirectory .appendingPathComponent("SwiftFormat") .appendingPathComponent("Core") .appendingPathComponent("RuleRegistry+Generated.swift") -let ruleNameCacheFile = sourcesDirectory +let ruleNameCacheFile = + sourcesDirectory .appendingPathComponent("SwiftFormat") .appendingPathComponent("Core") .appendingPathComponent("RuleNameCache+Generated.swift") -let ruleDocumentationFile = sourcesDirectory +let ruleDocumentationFile = + sourcesDirectory .appendingPathComponent("..") .appendingPathComponent("Documentation") .appendingPathComponent("RuleDocumentation.md") diff --git a/Sources/swift-format/Frontend/FormatFrontend.swift b/Sources/swift-format/Frontend/FormatFrontend.swift index ae3713a81..850086d61 100644 --- a/Sources/swift-format/Frontend/FormatFrontend.swift +++ b/Sources/swift-format/Frontend/FormatFrontend.swift @@ -35,7 +35,8 @@ class FormatFrontend: Frontend { let url = fileToProcess.url guard let source = fileToProcess.sourceText else { diagnosticsEngine.emitError( - "Unable to format \(url.relativePath): file is not readable or does not exist.") + "Unable to format \(url.relativePath): file is not readable or does not exist." + ) return } @@ -56,7 +57,8 @@ class FormatFrontend: Frontend { assumingFileURL: url, selection: fileToProcess.selection, to: &buffer, - parsingDiagnosticHandler: diagnosticHandler) + parsingDiagnosticHandler: diagnosticHandler + ) if buffer != source { let bufferData = buffer.data(using: .utf8)! // Conversion to UTF-8 cannot fail @@ -68,11 +70,13 @@ class FormatFrontend: Frontend { assumingFileURL: url, selection: fileToProcess.selection, to: &stdoutStream, - parsingDiagnosticHandler: diagnosticHandler) + parsingDiagnosticHandler: diagnosticHandler + ) } } catch SwiftFormatError.fileNotReadable { diagnosticsEngine.emitError( - "Unable to format \(url.relativePath): file is not readable or does not exist.") + "Unable to format \(url.relativePath): file is not readable or does not exist." + ) return } catch SwiftFormatError.fileContainsInvalidSyntax { guard !lintFormatOptions.ignoreUnparsableFiles else { diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index 307335883..a3ea18a4f 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -12,8 +12,8 @@ import Foundation @_spi(Internal) import SwiftFormat -import SwiftSyntax import SwiftParser +import SwiftSyntax class Frontend { /// Represents a file to be processed by the frontend and any file-specific options associated @@ -89,7 +89,8 @@ class Frontend { self.lintFormatOptions = lintFormatOptions self.diagnosticPrinter = StderrDiagnosticPrinter( - colorMode: lintFormatOptions.colorDiagnostics.map { $0 ? .on : .off } ?? .auto) + colorMode: lintFormatOptions.colorDiagnostics.map { $0 ? .on : .off } ?? .auto + ) self.diagnosticsEngine = DiagnosticsEngine(diagnosticsHandlers: [diagnosticPrinter.printDiagnostic]) } @@ -101,7 +102,8 @@ class Frontend { } else { processURLs( lintFormatOptions.paths.map(URL.init(fileURLWithPath:)), - parallel: lintFormatOptions.parallel) + parallel: lintFormatOptions.parallel + ) } } @@ -119,9 +121,11 @@ class Frontend { private func processStandardInput() { let assumedUrl = lintFormatOptions.assumeFilename.map(URL.init(fileURLWithPath:)) - guard let configuration = configuration( - fromPathOrString: lintFormatOptions.configuration, - orInferredFromSwiftFileAt: assumedUrl) + guard + let configuration = configuration( + fromPathOrString: lintFormatOptions.configuration, + orInferredFromSwiftFileAt: assumedUrl + ) else { // Already diagnosed in the called method. return @@ -131,7 +135,8 @@ class Frontend { fileHandle: FileHandle.standardInput, url: assumedUrl ?? URL(fileURLWithPath: ""), configuration: configuration, - selection: Selection(offsetRanges: lintFormatOptions.offsets)) + selection: Selection(offsetRanges: lintFormatOptions.offsets) + ) processFile(fileToProcess) } @@ -139,7 +144,8 @@ class Frontend { private func processURLs(_ urls: [URL], parallel: Bool) { precondition( !urls.isEmpty, - "processURLs(_:) should only be called when 'urls' is non-empty.") + "processURLs(_:) should only be called when 'urls' is non-empty." + ) if parallel { let filesToProcess = @@ -161,14 +167,16 @@ class Frontend { private func openAndPrepareFile(at url: URL) -> FileToProcess? { guard let sourceFile = try? FileHandle(forReadingFrom: url) else { diagnosticsEngine.emitError( - "Unable to open \(url.relativePath): file is not readable or does not exist") + "Unable to open \(url.relativePath): file is not readable or does not exist" + ) return nil } guard let configuration = configuration( fromPathOrString: lintFormatOptions.configuration, - orInferredFromSwiftFileAt: url) + orInferredFromSwiftFileAt: url + ) else { // Already diagnosed in the called method. return nil @@ -237,7 +245,8 @@ class Frontend { // Fall through to the default return at the end of the function. } catch { diagnosticsEngine.emitError( - "Unable to read configuration for \(swiftFileURL.path): \(error.localizedDescription)") + "Unable to read configuration for \(swiftFileURL.path): \(error.localizedDescription)" + ) return nil } } else { @@ -253,7 +262,8 @@ class Frontend { } } catch { diagnosticsEngine.emitError( - "Unable to read configuration for \(cwd): \(error.localizedDescription)") + "Unable to read configuration for \(cwd): \(error.localizedDescription)" + ) return nil } } diff --git a/Sources/swift-format/Frontend/LintFrontend.swift b/Sources/swift-format/Frontend/LintFrontend.swift index 4ef989826..f7c4ee52c 100644 --- a/Sources/swift-format/Frontend/LintFrontend.swift +++ b/Sources/swift-format/Frontend/LintFrontend.swift @@ -19,30 +19,35 @@ import SwiftSyntax class LintFrontend: Frontend { override func processFile(_ fileToProcess: FileToProcess) { let linter = SwiftLinter( - configuration: fileToProcess.configuration, findingConsumer: diagnosticsEngine.consumeFinding) + configuration: fileToProcess.configuration, + findingConsumer: diagnosticsEngine.consumeFinding + ) linter.debugOptions = debugOptions let url = fileToProcess.url guard let source = fileToProcess.sourceText else { diagnosticsEngine.emitError( - "Unable to lint \(url.relativePath): file is not readable or does not exist.") + "Unable to lint \(url.relativePath): file is not readable or does not exist." + ) return } do { try linter.lint( source: source, - assumingFileURL: url) { (diagnostic, location) in - guard !self.lintFormatOptions.ignoreUnparsableFiles else { - // No diagnostics should be emitted in this mode. - return - } - self.diagnosticsEngine.consumeParserDiagnostic(diagnostic, location) + assumingFileURL: url + ) { (diagnostic, location) in + guard !self.lintFormatOptions.ignoreUnparsableFiles else { + // No diagnostics should be emitted in this mode. + return + } + self.diagnosticsEngine.consumeParserDiagnostic(diagnostic, location) } } catch SwiftFormatError.fileNotReadable { diagnosticsEngine.emitError( - "Unable to lint \(url.relativePath): file is not readable or does not exist.") + "Unable to lint \(url.relativePath): file is not readable or does not exist." + ) return } catch SwiftFormatError.fileContainsInvalidSyntax { guard !lintFormatOptions.ignoreUnparsableFiles else { diff --git a/Sources/swift-format/Subcommands/DumpConfiguration.swift b/Sources/swift-format/Subcommands/DumpConfiguration.swift index 9e6f03c43..ff41c8554 100644 --- a/Sources/swift-format/Subcommands/DumpConfiguration.swift +++ b/Sources/swift-format/Subcommands/DumpConfiguration.swift @@ -18,7 +18,8 @@ extension SwiftFormatCommand { /// Dumps the tool's default configuration in JSON format to standard output. struct DumpConfiguration: ParsableCommand { static var configuration = CommandConfiguration( - abstract: "Dump the default configuration in JSON format to standard output") + abstract: "Dump the default configuration in JSON format to standard output" + ) func run() throws { let configuration = Configuration() @@ -34,7 +35,8 @@ extension SwiftFormatCommand { // This should never happen, but let's make sure we fail more gracefully than crashing, just // in case. throw FormatError( - message: "Could not dump the default configuration: the JSON was not valid UTF-8") + message: "Could not dump the default configuration: the JSON was not valid UTF-8" + ) } print(jsonString) } catch { diff --git a/Sources/swift-format/Subcommands/Format.swift b/Sources/swift-format/Subcommands/Format.swift index 548e30e18..42c2da165 100644 --- a/Sources/swift-format/Subcommands/Format.swift +++ b/Sources/swift-format/Subcommands/Format.swift @@ -17,14 +17,16 @@ extension SwiftFormatCommand { struct Format: ParsableCommand { static var configuration = CommandConfiguration( abstract: "Format Swift source code", - discussion: "When no files are specified, it expects the source from standard input.") + discussion: "When no files are specified, it expects the source from standard input." + ) /// Whether or not to format the Swift file in-place. /// /// If specified, the current file is overwritten when formatting. @Flag( name: .shortAndLong, - help: "Overwrite the current file when formatting.") + help: "Overwrite the current file when formatting." + ) var inPlace: Bool = false @OptionGroup() diff --git a/Sources/swift-format/Subcommands/Lint.swift b/Sources/swift-format/Subcommands/Lint.swift index 43b3872a9..3002c5912 100644 --- a/Sources/swift-format/Subcommands/Lint.swift +++ b/Sources/swift-format/Subcommands/Lint.swift @@ -17,11 +17,12 @@ extension SwiftFormatCommand { struct Lint: ParsableCommand { static var configuration = CommandConfiguration( abstract: "Diagnose style issues in Swift source code", - discussion: "When no files are specified, it expects the source from standard input.") + discussion: "When no files are specified, it expects the source from standard input." + ) @OptionGroup() var lintOptions: LintFormatOptions - + @Flag( name: .shortAndLong, help: "Fail on warnings." @@ -35,7 +36,7 @@ extension SwiftFormatCommand { try performanceMeasurementOptions.printingInstructionCountIfRequested { let frontend = LintFrontend(lintFormatOptions: lintOptions) frontend.run() - + if frontend.diagnosticsEngine.hasErrors || strict && frontend.diagnosticsEngine.hasWarnings { throw ExitCode.failure } diff --git a/Sources/swift-format/Subcommands/LintFormatOptions.swift b/Sources/swift-format/Subcommands/LintFormatOptions.swift index 098ad25d1..bd15dc4e7 100644 --- a/Sources/swift-format/Subcommands/LintFormatOptions.swift +++ b/Sources/swift-format/Subcommands/LintFormatOptions.swift @@ -23,7 +23,8 @@ struct LintFormatOptions: ParsableArguments { help: """ The path to a JSON file containing the configuration of the linter/formatter or a JSON \ string containing the configuration directly. - """) + """ + ) var configuration: String? /// A list of comma-separated "start:end" pairs specifying UTF-8 offsets of the ranges to format. @@ -34,7 +35,8 @@ struct LintFormatOptions: ParsableArguments { help: """ A "start:end" pair specifying UTF-8 offsets of the range to format. Multiple ranges can be formatted by specifying several --offsets arguments. - """) + """ + ) var offsets: [Range] = [] /// The filename for the source code when reading from standard input, to include in diagnostic @@ -50,23 +52,27 @@ struct LintFormatOptions: ParsableArguments { /// If set, we recursively run on all ".swift" files in any provided directories. @Flag( name: .shortAndLong, - help: "Recursively run on '.swift' files in any provided directories.") + help: "Recursively run on '.swift' files in any provided directories." + ) var recursive: Bool = false /// Whether unparsable files, due to syntax errors or unrecognized syntax, should be ignored or /// treated as containing an error. When ignored, unparsable files are output verbatim in format /// mode and no diagnostics are raised in lint mode. When not ignored, unparsable files raise a /// diagnostic in both format and lint mode. - @Flag(help: """ - Ignores unparsable files, disabling all diagnostics and formatting for files that contain \ - invalid syntax. - """) + @Flag( + help: """ + Ignores unparsable files, disabling all diagnostics and formatting for files that contain \ + invalid syntax. + """ + ) var ignoreUnparsableFiles: Bool = false /// Whether or not to run the formatter/linter in parallel. @Flag( name: .shortAndLong, - help: "Process files in parallel, simultaneously across multiple cores.") + help: "Process files in parallel, simultaneously across multiple cores." + ) var parallel: Bool = false /// Whether colors should be used in diagnostics printed to standard error. @@ -79,14 +85,17 @@ struct LintFormatOptions: ParsableArguments { Enables or disables color diagnostics when printing to standard error. The default behavior \ if this flag is omitted is to use colors if standard error is connected to a terminal, and \ to not use colors otherwise. - """) + """ + ) var colorDiagnostics: Bool? /// Whether symlinks should be followed. - @Flag(help: """ - Follow symbolic links passed on the command line, or found during directory traversal when \ - using `-r/--recursive`. - """) + @Flag( + help: """ + Follow symbolic links passed on the command line, or found during directory traversal when \ + using `-r/--recursive`. + """ + ) var followSymlinks: Bool = false /// The list of paths to Swift source files that should be formatted or linted. @@ -129,7 +138,7 @@ extension Range { public init?(argument: String) { let pair = argument.components(separatedBy: ":") if pair.count == 2, let start = Int(pair[0]), let end = Int(pair[1]), start <= end { - self = start ..< end + self = start.. { } #if compiler(>=6) -extension Range : @retroactive ExpressibleByArgument {} +extension Range: @retroactive ExpressibleByArgument {} #else -extension Range : ExpressibleByArgument {} +extension Range: ExpressibleByArgument {} #endif diff --git a/Sources/swift-format/Utilities/DiagnosticsEngine.swift b/Sources/swift-format/Utilities/DiagnosticsEngine.swift index a6cd6978f..220a2a23c 100644 --- a/Sources/swift-format/Utilities/DiagnosticsEngine.swift +++ b/Sources/swift-format/Utilities/DiagnosticsEngine.swift @@ -10,9 +10,9 @@ // //===----------------------------------------------------------------------===// +import SwiftDiagnostics import SwiftFormat import SwiftSyntax -import SwiftDiagnostics /// Unifies the handling of findings from the linter, parsing errors from the syntax parser, and /// generic errors from the frontend so that they are emitted in a uniform fashion. @@ -62,7 +62,9 @@ final class DiagnosticsEngine { Diagnostic( severity: .error, location: location.map(Diagnostic.Location.init), - message: message)) + message: message + ) + ) } /// Emits a generic warning message. @@ -76,7 +78,9 @@ final class DiagnosticsEngine { Diagnostic( severity: .warning, location: location.map(Diagnostic.Location.init), - message: message)) + message: message + ) + ) } /// Emits a finding from the linter and any of its associated notes as diagnostics. @@ -90,7 +94,9 @@ final class DiagnosticsEngine { Diagnostic( severity: .note, location: note.location.map(Diagnostic.Location.init), - message: "\(note.message)")) + message: "\(note.message)" + ) + ) } } @@ -115,13 +121,14 @@ final class DiagnosticsEngine { case .error: severity = .error case .warning: severity = .warning case .note: severity = .note - case .remark: severity = .note // should we model this? + case .remark: severity = .note // should we model this? } return Diagnostic( severity: severity, location: Diagnostic.Location(location), category: nil, - message: message.message) + message: message.message + ) } /// Converts a lint finding into a diagnostic message that can be used by the `TSCBasic` @@ -138,6 +145,7 @@ final class DiagnosticsEngine { severity: severity, location: finding.location.map(Diagnostic.Location.init), category: "\(finding.category)", - message: "\(finding.message.text)") + message: "\(finding.message.text)" + ) } } diff --git a/Sources/swift-format/Utilities/FormatError.swift b/Sources/swift-format/Utilities/FormatError.swift index 5b325cc57..b922038ee 100644 --- a/Sources/swift-format/Utilities/FormatError.swift +++ b/Sources/swift-format/Utilities/FormatError.swift @@ -15,10 +15,9 @@ import Foundation struct FormatError: LocalizedError { var message: String var errorDescription: String? { message } - + static var exitWithDiagnosticErrors: FormatError { // The diagnostics engine has already printed errors to stderr. FormatError(message: "") } } - diff --git a/Sources/swift-format/Utilities/TTY.swift b/Sources/swift-format/Utilities/TTY.swift index 35fc35841..3097b1611 100644 --- a/Sources/swift-format/Utilities/TTY.swift +++ b/Sources/swift-format/Utilities/TTY.swift @@ -17,13 +17,13 @@ func isTTY(_ fileHandle: FileHandle) -> Bool { // The implementation of this function is adapted from `TerminalController.swift` in // swift-tools-support-core. #if os(Windows) - // The TSC implementation of this function only returns `.file` or `.dumb` for Windows, - // neither of which is a TTY. - return false + // The TSC implementation of this function only returns `.file` or `.dumb` for Windows, + // neither of which is a TTY. + return false #else - if ProcessInfo.processInfo.environment["TERM"] == "dumb" { - return false - } - return isatty(fileHandle.fileDescriptor) != 0 + if ProcessInfo.processInfo.environment["TERM"] == "dumb" { + return false + } + return isatty(fileHandle.fileDescriptor) != 0 #endif } diff --git a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift index 960033604..b50b23aca 100644 --- a/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift +++ b/Tests/SwiftFormatPerformanceTests/WhitespaceLinterPerformanceTests.swift @@ -1,8 +1,7 @@ -import SwiftSyntax +@_spi(Testing) import SwiftFormat import SwiftParser +import SwiftSyntax import XCTest - -@_spi(Testing) import SwiftFormat @_spi(Testing) import _SwiftFormatTestSupport final class WhitespaceLinterPerformanceTests: DiagnosingTestCase { @@ -58,8 +57,11 @@ final class WhitespaceLinterPerformanceTests: DiagnosingTestCase { /// - expected: The formatted text. private func performWhitespaceLint(input: String, expected: String) { let sourceFileSyntax = Parser.parse(source: input) - let context = makeContext(sourceFileSyntax: sourceFileSyntax, selection: .infinite, - findingConsumer: { _ in }) + let context = makeContext( + sourceFileSyntax: sourceFileSyntax, + selection: .infinite, + findingConsumer: { _ in } + ) let linter = WhitespaceLinter(user: input, formatted: expected, context: context) linter.lint() } diff --git a/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift b/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift index 7094e5b1d..55af641ef 100644 --- a/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift +++ b/Tests/SwiftFormatTests/Core/DocumentationCommentTests.swift @@ -1,10 +1,9 @@ import Markdown +@_spi(Testing) import SwiftFormat import SwiftSyntax import SwiftSyntaxBuilder import XCTest -@_spi(Testing) import SwiftFormat - final class DocumentationCommentTests: XCTestCase { func testBriefSummaryOnly() throws { let decl: DeclSyntax = """ @@ -17,14 +16,15 @@ final class DocumentationCommentTests: XCTestCase { """ Paragraph └─ Text "A brief summary." - """) + """ + ) XCTAssertTrue(comment.bodyNodes.isEmpty) XCTAssertNil(comment.parameterLayout) XCTAssertTrue(comment.parameters.isEmpty) XCTAssertNil(comment.returns) XCTAssertNil(comment.throws) } - + func testBriefSummaryAndAdditionalParagraphs() throws { let decl: DeclSyntax = """ /// A brief summary. @@ -40,7 +40,8 @@ final class DocumentationCommentTests: XCTestCase { """ Paragraph └─ Text "A brief summary." - """) + """ + ) XCTAssertEqual( comment.bodyNodes.map { $0.debugDescription() }, [ @@ -59,7 +60,7 @@ final class DocumentationCommentTests: XCTestCase { XCTAssertNil(comment.returns) XCTAssertNil(comment.throws) } - + func testParameterOutline() throws { let decl: DeclSyntax = """ /// - Parameters: @@ -91,7 +92,7 @@ final class DocumentationCommentTests: XCTestCase { XCTAssertNil(comment.returns) XCTAssertNil(comment.throws) } - + func testSeparatedParameters() throws { let decl: DeclSyntax = """ /// - Parameter x: A value. @@ -122,7 +123,7 @@ final class DocumentationCommentTests: XCTestCase { XCTAssertNil(comment.returns) XCTAssertNil(comment.throws) } - + func testMalformedTagsGoIntoBodyNodes() throws { let decl: DeclSyntax = """ /// - Parameter: A value. @@ -165,7 +166,7 @@ final class DocumentationCommentTests: XCTestCase { XCTAssertNil(comment.parameterLayout) XCTAssertTrue(comment.parameters.isEmpty) } - + func testReturnsField() throws { let decl: DeclSyntax = """ /// - Returns: A value. @@ -187,7 +188,7 @@ final class DocumentationCommentTests: XCTestCase { ) XCTAssertNil(comment.throws) } - + func testThrowsField() throws { let decl: DeclSyntax = """ /// - Throws: An error. @@ -199,7 +200,7 @@ final class DocumentationCommentTests: XCTestCase { XCTAssertNil(comment.parameterLayout) XCTAssertTrue(comment.parameters.isEmpty) XCTAssertNil(comment.returns) - + let throwsField = try XCTUnwrap(comment.throws) XCTAssertEqual( throwsField.debugDescription(), @@ -209,7 +210,7 @@ final class DocumentationCommentTests: XCTestCase { """ ) } - + func testUnrecognizedFieldsGoIntoBodyNodes() throws { let decl: DeclSyntax = """ /// - Blahblah: Blah. diff --git a/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift b/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift index 4a1f8302f..a7a16e70d 100644 --- a/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift +++ b/Tests/SwiftFormatTests/Core/DocumentationCommentTextTests.swift @@ -1,9 +1,8 @@ +@_spi(Testing) import SwiftFormat import SwiftSyntax import SwiftSyntaxBuilder import XCTest -@_spi(Testing) import SwiftFormat - final class DocumentationCommentTextTests: XCTestCase { func testSimpleDocLineComment() throws { let decl: DeclSyntax = """ @@ -16,11 +15,11 @@ final class DocumentationCommentTextTests: XCTestCase { commentText.text, """ A simple doc comment. - + """ ) } - + func testOneLineDocBlockComment() throws { let decl: DeclSyntax = """ /** A simple doc comment. */ @@ -32,11 +31,11 @@ final class DocumentationCommentTextTests: XCTestCase { commentText.text, """ A simple doc comment.\u{0020} - + """ ) } - + func testDocBlockCommentWithASCIIArt() throws { let decl: DeclSyntax = """ /** @@ -50,7 +49,7 @@ final class DocumentationCommentTextTests: XCTestCase { commentText.text, """ A simple doc comment. - + """ ) } @@ -86,11 +85,11 @@ final class DocumentationCommentTextTests: XCTestCase { commentText.text, """ A simple doc comment. - + """ ) } - + func testMultilineDocLineComment() throws { let decl: DeclSyntax = """ /// A doc comment. @@ -108,21 +107,21 @@ final class DocumentationCommentTextTests: XCTestCase { commentText.text, """ A doc comment. - + This is a longer paragraph, containing more detail. - + - Parameter x: A parameter. - Returns: A value. - + """ ) } - + func testDocLineCommentStopsAtBlankLine() throws { let decl: DeclSyntax = """ /// This should not be part of the comment. - + /// A doc comment. func f(x: Int) -> Int {} """ @@ -132,15 +131,15 @@ final class DocumentationCommentTextTests: XCTestCase { commentText.text, """ A doc comment. - + """ ) } - + func testDocBlockCommentStopsAtBlankLine() throws { let decl: DeclSyntax = """ /** This should not be part of the comment. */ - + /** * This is part of the comment. */ @@ -154,7 +153,7 @@ final class DocumentationCommentTextTests: XCTestCase { """ This is part of the comment. so is this\u{0020} - + """ ) } diff --git a/Tests/SwiftFormatTests/Core/RuleMaskTests.swift b/Tests/SwiftFormatTests/Core/RuleMaskTests.swift index 0984b7f8a..4e46ac635 100644 --- a/Tests/SwiftFormatTests/Core/RuleMaskTests.swift +++ b/Tests/SwiftFormatTests/Core/RuleMaskTests.swift @@ -1,9 +1,8 @@ -import SwiftSyntax +@_spi(Testing) import SwiftFormat import SwiftParser +import SwiftSyntax import XCTest -@_spi(Testing) import SwiftFormat - final class RuleMaskTests: XCTestCase { /// The source converter for the text in the current test. This is implicitly unwrapped because /// each test case must prepare some source text before performing any assertions, otherwise diff --git a/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift index c65b3b7fe..389ce0853 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ArrayDeclTests.swift @@ -54,15 +54,15 @@ final class ArrayDeclTests: PrettyPrintTestCase { ] """ - // Ideally, this array would be left on 1 line without a trailing comma. We don't know if the - // comma is required when calculating the length of array elements, so the comma's length is - // always added to last element and that 1 character causes the newlines inside of the array. - + """ - let a = [ - 11111111, 2222222, 33333333, 444444, - ] + // Ideally, this array would be left on 1 line without a trailing comma. We don't know if the + // comma is required when calculating the length of array elements, so the comma's length is + // always added to last element and that 1 character causes the newlines inside of the array. + + """ + let a = [ + 11111111, 2222222, 33333333, 444444, + ] - """ + """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) } @@ -278,15 +278,15 @@ final class ArrayDeclTests: PrettyPrintTestCase { ] """ - // Ideally, this array would be left on 1 line without a trailing comma. We don't know if the - // comma is required when calculating the length of array elements, so the comma's length is - // always added to last element and that 1 character causes the newlines inside of the array. - + """ - a = [ - ("az", "by"), ("cf", "de"), - ] + // Ideally, this array would be left on 1 line without a trailing comma. We don't know if the + // comma is required when calculating the length of array elements, so the comma's length is + // always added to last element and that 1 character causes the newlines inside of the array. + + """ + a = [ + ("az", "by"), ("cf", "de"), + ] - """ + """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 32) } diff --git a/Tests/SwiftFormatTests/PrettyPrint/AssignmentExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AssignmentExprTests.swift index 77d505be6..2ab1b1d92 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/AssignmentExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/AssignmentExprTests.swift @@ -47,93 +47,101 @@ final class AssignmentExprTests: PrettyPrintTestCase { func testAssignmentOperatorFromSequenceWithFunctionCalls() { let input = - """ - result = firstOp + secondOp + someOpFetchingFunc(foo, bar: bar, baz: baz) - result = someOpFetchingFunc(foo, bar: bar, baz: baz) - result += someOpFetchingFunc(foo, bar: bar, baz: baz) - result = someOpFetchingFunc(foo, bar: bar, baz: baz) + someOtherOperand + andAThirdOneForReasons - result = firstOp + secondOp + thirdOp + someOpFetchingFunc(foo, bar, baz) + nextOp + lastOp - result += firstOp + secondOp + thirdOp + someOpFetchingFunc(foo, bar, baz) + nextOp + lastOp - """ - - let expectedWithArgBinPacking = - """ - result = - firstOp + secondOp - + someOpFetchingFunc( - foo, bar: bar, baz: baz) - result = someOpFetchingFunc( + """ + result = firstOp + secondOp + someOpFetchingFunc(foo, bar: bar, baz: baz) + result = someOpFetchingFunc(foo, bar: bar, baz: baz) + result += someOpFetchingFunc(foo, bar: bar, baz: baz) + result = someOpFetchingFunc(foo, bar: bar, baz: baz) + someOtherOperand + andAThirdOneForReasons + result = firstOp + secondOp + thirdOp + someOpFetchingFunc(foo, bar, baz) + nextOp + lastOp + result += firstOp + secondOp + thirdOp + someOpFetchingFunc(foo, bar, baz) + nextOp + lastOp + """ + + let expectedWithArgBinPacking = + """ + result = + firstOp + secondOp + + someOpFetchingFunc( foo, bar: bar, baz: baz) - result += someOpFetchingFunc( + result = someOpFetchingFunc( + foo, bar: bar, baz: baz) + result += someOpFetchingFunc( + foo, bar: bar, baz: baz) + result = + someOpFetchingFunc( foo, bar: bar, baz: baz) - result = - someOpFetchingFunc( - foo, bar: bar, baz: baz) - + someOtherOperand - + andAThirdOneForReasons - result = - firstOp + secondOp + thirdOp - + someOpFetchingFunc( - foo, bar, baz) + nextOp - + lastOp - result += - firstOp + secondOp + thirdOp - + someOpFetchingFunc( - foo, bar, baz) + nextOp - + lastOp - - """ - - var config = Configuration.forTesting - config.lineBreakBeforeEachArgument = false - assertPrettyPrintEqual( - input: input, expected: expectedWithArgBinPacking, linelength: 35, configuration: config) - - let expectedWithBreakBeforeEachArg = - """ - result = - firstOp + secondOp - + someOpFetchingFunc( - foo, - bar: bar, - baz: baz - ) - result = someOpFetchingFunc( + + someOtherOperand + + andAThirdOneForReasons + result = + firstOp + secondOp + thirdOp + + someOpFetchingFunc( + foo, bar, baz) + nextOp + + lastOp + result += + firstOp + secondOp + thirdOp + + someOpFetchingFunc( + foo, bar, baz) + nextOp + + lastOp + + """ + + var config = Configuration.forTesting + config.lineBreakBeforeEachArgument = false + assertPrettyPrintEqual( + input: input, + expected: expectedWithArgBinPacking, + linelength: 35, + configuration: config + ) + + let expectedWithBreakBeforeEachArg = + """ + result = + firstOp + secondOp + + someOpFetchingFunc( foo, bar: bar, baz: baz ) - result += someOpFetchingFunc( + result = someOpFetchingFunc( + foo, + bar: bar, + baz: baz + ) + result += someOpFetchingFunc( + foo, + bar: bar, + baz: baz + ) + result = + someOpFetchingFunc( foo, bar: bar, baz: baz - ) - result = - someOpFetchingFunc( - foo, - bar: bar, - baz: baz - ) + someOtherOperand - + andAThirdOneForReasons - result = - firstOp + secondOp + thirdOp - + someOpFetchingFunc( - foo, - bar, - baz - ) + nextOp + lastOp - result += - firstOp + secondOp + thirdOp - + someOpFetchingFunc( - foo, - bar, - baz - ) + nextOp + lastOp - - """ - config.lineBreakBeforeEachArgument = true - assertPrettyPrintEqual( - input: input, expected: expectedWithBreakBeforeEachArg, linelength: 35, configuration: config) + ) + someOtherOperand + + andAThirdOneForReasons + result = + firstOp + secondOp + thirdOp + + someOpFetchingFunc( + foo, + bar, + baz + ) + nextOp + lastOp + result += + firstOp + secondOp + thirdOp + + someOpFetchingFunc( + foo, + bar, + baz + ) + nextOp + lastOp + + """ + config.lineBreakBeforeEachArgument = true + assertPrettyPrintEqual( + input: input, + expected: expectedWithBreakBeforeEachArg, + linelength: 35, + configuration: config + ) } func testAssignmentPatternBindingFromSequenceWithFunctionCalls() { @@ -169,7 +177,11 @@ final class AssignmentExprTests: PrettyPrintTestCase { var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual( - input: input, expected: expectedWithArgBinPacking, linelength: 35, configuration: config) + input: input, + expected: expectedWithArgBinPacking, + linelength: 35, + configuration: config + ) let expectedWithBreakBeforeEachArg = """ @@ -203,6 +215,10 @@ final class AssignmentExprTests: PrettyPrintTestCase { """ config.lineBreakBeforeEachArgument = true assertPrettyPrintEqual( - input: input, expected: expectedWithBreakBeforeEachArg, linelength: 35, configuration: config) + input: input, + expected: expectedWithBreakBeforeEachArg, + linelength: 35, + configuration: config + ) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift index 668df4de2..3776b10f7 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/AttributeTests.swift @@ -127,7 +127,11 @@ final class AttributeTests: PrettyPrintTestCase { var configuration = Configuration.forTesting configuration.lineBreakBeforeEachArgument = true assertPrettyPrintEqual( - input: input, expected: expected, linelength: 32, configuration: configuration) + input: input, + expected: expected, + linelength: 32, + configuration: configuration + ) } func testAttributeFormattingRespectsDiscretionaryLineBreaks() { @@ -203,7 +207,11 @@ final class AttributeTests: PrettyPrintTestCase { var configuration = Configuration.forTesting configuration.lineBreakBeforeEachArgument = true assertPrettyPrintEqual( - input: input, expected: expected, linelength: 40, configuration: configuration) + input: input, + expected: expected, + linelength: 40, + configuration: configuration + ) } func testObjCBinPackedAttributes() { @@ -240,43 +248,47 @@ final class AttributeTests: PrettyPrintTestCase { } func testObjCAttributesPerLineBreaking() { - let input = - """ - @objc func f() {} - @objc(foo:bar:baz) - func f() {} - @objc(thisMethodHasAVeryLongName:foo:bar:) - func f() {} - @objc(thisMethodHasAVeryLongName:andThisArgumentHasANameToo:soDoesThisOne:bar:) - func f() {} - """ - - let expected = - """ - @objc func f() {} - @objc(foo:bar:baz) - func f() {} - @objc( - thisMethodHasAVeryLongName: - foo: - bar: - ) - func f() {} - @objc( - thisMethodHasAVeryLongName: - andThisArgumentHasANameToo: - soDoesThisOne: - bar: - ) - func f() {} - - """ - - var configuration = Configuration.forTesting - configuration.lineBreakBeforeEachArgument = true - assertPrettyPrintEqual( - input: input, expected: expected, linelength: 40, configuration: configuration) - } + let input = + """ + @objc func f() {} + @objc(foo:bar:baz) + func f() {} + @objc(thisMethodHasAVeryLongName:foo:bar:) + func f() {} + @objc(thisMethodHasAVeryLongName:andThisArgumentHasANameToo:soDoesThisOne:bar:) + func f() {} + """ + + let expected = + """ + @objc func f() {} + @objc(foo:bar:baz) + func f() {} + @objc( + thisMethodHasAVeryLongName: + foo: + bar: + ) + func f() {} + @objc( + thisMethodHasAVeryLongName: + andThisArgumentHasANameToo: + soDoesThisOne: + bar: + ) + func f() {} + + """ + + var configuration = Configuration.forTesting + configuration.lineBreakBeforeEachArgument = true + assertPrettyPrintEqual( + input: input, + expected: expected, + linelength: 40, + configuration: configuration + ) + } func testObjCAttributesDiscretionaryLineBreaking() { // The discretionary newlines in the 3rd function declaration are invalid, because new lines @@ -572,9 +584,9 @@ final class AttributeTests: PrettyPrintTestCase { } """ - var configuration = Configuration.forTesting - configuration.lineBreakBetweenDeclarationAttributes = true - assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: configuration) + var configuration = Configuration.forTesting + configuration.lineBreakBetweenDeclarationAttributes = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: configuration) } func testAttributesStartWithPoundIf() { @@ -585,7 +597,7 @@ final class AttributeTests: PrettyPrintTestCase { @_spi(Foo) #endif public let myVar = "Test" - + """ let expected = """ @@ -594,9 +606,9 @@ final class AttributeTests: PrettyPrintTestCase { @_spi(Foo) #endif public let myVar = "Test" - + """ - + assertPrettyPrintEqual(input: input, expected: expected, linelength: 45) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/BinaryOperatorExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/BinaryOperatorExprTests.swift index 50b283bd6..1d2bd6492 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/BinaryOperatorExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/BinaryOperatorExprTests.swift @@ -53,7 +53,11 @@ final class BinaryOperatorExprTests: PrettyPrintTestCase { var configuration = Configuration.forTesting configuration.spacesAroundRangeFormationOperators = false assertPrettyPrintEqual( - input: input, expected: expected, linelength: 80, configuration: configuration) + input: input, + expected: expected, + linelength: 80, + configuration: configuration + ) } func testRangeFormationOperatorCompaction_spacesAroundRangeFormation() { @@ -81,7 +85,11 @@ final class BinaryOperatorExprTests: PrettyPrintTestCase { var configuration = Configuration.forTesting configuration.spacesAroundRangeFormationOperators = true assertPrettyPrintEqual( - input: input, expected: expected, linelength: 80, configuration: configuration) + input: input, + expected: expected, + linelength: 80, + configuration: configuration + ) } func testRangeFormationOperatorsAreNotCompactedWhenFollowingAPostfixOperator() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/ClassDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ClassDeclTests.swift index a69ffa1f5..985d4db66 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ClassDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ClassDeclTests.swift @@ -228,7 +228,7 @@ final class ClassDeclTests: PrettyPrintTestCase { func testClassWhereClause_lineBreakAfterGenericWhereClause() { let input = - """ + """ class MyClass where S: Collection { let A: Int let B: Double @@ -244,7 +244,7 @@ final class ClassDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ class MyClass where S: Collection { let A: Int let B: Double @@ -304,7 +304,7 @@ final class ClassDeclTests: PrettyPrintTestCase { func testClassWhereClauseWithInheritance_lineBreakAfterGenericWhereClause() { let input = - """ + """ class MyClass: SuperOne where S: Collection { let A: Int let B: Double @@ -320,7 +320,7 @@ final class ClassDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ class MyClass: SuperOne where S: Collection { let A: Int let B: Double @@ -431,7 +431,7 @@ final class ClassDeclTests: PrettyPrintTestCase { func testClassFullWrap_lineBreakAfterGenericWhereClause() { let input = - """ + """ public class MyContainer: MyContainerSuperclass, MyContainerProtocol, SomeoneElsesContainerProtocol, SomeFrameworkContainerProtocol where BaseCollection: Collection, BaseCollection: P, BaseCollection.Element: Equatable, BaseCollection.Element: SomeOtherProtocol { let A: Int let B: Double @@ -440,7 +440,7 @@ final class ClassDeclTests: PrettyPrintTestCase { let expected = - """ + """ public class MyContainer< BaseCollection, SecondCollection >: MyContainerSuperclass, MyContainerProtocol, diff --git a/Tests/SwiftFormatTests/PrettyPrint/ClosureExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ClosureExprTests.swift index 089aee1ba..eb437c253 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ClosureExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ClosureExprTests.swift @@ -189,7 +189,7 @@ final class ClosureExprTests: PrettyPrintTestCase { func testClosuresWithIfs() { let input = - """ + """ let a = afunc() { if condition1 { return true @@ -209,7 +209,7 @@ final class ClosureExprTests: PrettyPrintTestCase { """ let expected = - """ + """ let a = afunc() { if condition1 { return true @@ -482,7 +482,11 @@ final class ClosureExprTests: PrettyPrintTestCase { var config = Configuration.forTesting config.prioritizeKeepingFunctionOutputTogether = true assertPrettyPrintEqual( - input: input, expected: expectedKeepingOutputTogether, linelength: 50, configuration: config) + input: input, + expected: expectedKeepingOutputTogether, + linelength: 50, + configuration: config + ) } func testClosureSignatureAttributes() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift index 70c05dfa7..c3c7766e5 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommaTests.swift @@ -9,9 +9,9 @@ final class CommaTests: PrettyPrintTestCase { 2, 3 ] - + """ - + let expected = """ let MyCollection = [ @@ -19,14 +19,14 @@ final class CommaTests: PrettyPrintTestCase { 2, 3, ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - + func testArrayCommasAbsentDisabled() { let input = """ @@ -35,9 +35,9 @@ final class CommaTests: PrettyPrintTestCase { 2, 3 ] - + """ - + let expected = """ let MyCollection = [ @@ -45,14 +45,14 @@ final class CommaTests: PrettyPrintTestCase { 2, 3 ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - + func testArrayCommasPresentEnabled() { let input = """ @@ -61,9 +61,9 @@ final class CommaTests: PrettyPrintTestCase { 2, 3, ] - + """ - + let expected = """ let MyCollection = [ @@ -71,14 +71,14 @@ final class CommaTests: PrettyPrintTestCase { 2, 3, ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - + func testArrayCommasPresentDisabled() { let input = """ @@ -87,9 +87,9 @@ final class CommaTests: PrettyPrintTestCase { 2, 3, ] - + """ - + let expected = """ let MyCollection = [ @@ -97,52 +97,52 @@ final class CommaTests: PrettyPrintTestCase { 2, 3 ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - + func testArraySingleLineCommasPresentEnabled() { let input = """ let MyCollection = [1, 2, 3,] - + """ - + // no effect expected let expected = """ let MyCollection = [1, 2, 3] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } - + func testArraySingleLineCommasPresentDisabled() { let input = """ let MyCollection = [1, 2, 3,] - + """ - + // no effect expected let expected = """ let MyCollection = [1, 2, 3] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } - + func testArrayWithCommentCommasPresentEnabled() { let input = """ @@ -152,21 +152,21 @@ final class CommaTests: PrettyPrintTestCase { ] """ - + let expected = """ let MyCollection = [ 1, 2, // some comment ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } - + func testArrayWithCommentCommasPresentDisabled() { let input = """ @@ -176,21 +176,21 @@ final class CommaTests: PrettyPrintTestCase { ] """ - + let expected = """ let MyCollection = [ 1, 2 // some comment ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } - + func testArrayWithTernaryOperatorAndCommentCommasPresentEnabled() { let input = """ @@ -200,21 +200,21 @@ final class CommaTests: PrettyPrintTestCase { ] """ - + let expected = """ let MyCollection = [ 1, true ? 1 : 2, // some comment ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } - + func testArrayWithTernaryOperatorAndCommentCommasPresentDisabled() { let input = """ @@ -224,16 +224,16 @@ final class CommaTests: PrettyPrintTestCase { ] """ - + let expected = """ let MyCollection = [ 1, true ? 1 : 2 // some comment ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) @@ -247,9 +247,9 @@ final class CommaTests: PrettyPrintTestCase { "b": 2, "c": 3 ] - + """ - + let expected = """ let MyCollection = [ @@ -257,14 +257,14 @@ final class CommaTests: PrettyPrintTestCase { "b": 2, "c": 3, ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - + func testDictionaryCommasAbsentDisabled() { let input = """ @@ -273,9 +273,9 @@ final class CommaTests: PrettyPrintTestCase { "b": 2, "c": 3 ] - + """ - + let expected = """ let MyCollection = [ @@ -283,14 +283,14 @@ final class CommaTests: PrettyPrintTestCase { "b": 2, "c": 3 ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - + func testDictionaryCommasPresentEnabled() { let input = """ @@ -299,9 +299,9 @@ final class CommaTests: PrettyPrintTestCase { "b": 2, "c": 3, ] - + """ - + let expected = """ let MyCollection = [ @@ -309,14 +309,14 @@ final class CommaTests: PrettyPrintTestCase { "b": 2, "c": 3, ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - + func testDictionaryCommasPresentDisabled() { let input = """ @@ -325,9 +325,9 @@ final class CommaTests: PrettyPrintTestCase { "b": 2, "c": 3, ] - + """ - + let expected = """ let MyCollection = [ @@ -335,49 +335,49 @@ final class CommaTests: PrettyPrintTestCase { "b": 2, "c": 3 ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 20, configuration: configuration) } - + func testDictionarySingleLineCommasPresentDisabled() { let input = """ let MyCollection = ["a": 1, "b": 2, "c": 3,] - + """ - + let expected = """ let MyCollection = [ "a": 1, "b": 2, "c": 3, ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = true assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) } - + func testDictionarySingleLineCommasPresentEnabled() { let input = """ let MyCollection = ["a": 1, "b": 2, "c": 3,] - + """ - + let expected = """ let MyCollection = [ "a": 1, "b": 2, "c": 3 ] - + """ - + var configuration = Configuration.forTesting configuration.multiElementCollectionTrailingCommas = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 40, configuration: configuration) diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift index 52226bed5..99b2259e2 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift @@ -1,5 +1,5 @@ -import _SwiftFormatTestSupport import SwiftFormat +import _SwiftFormatTestSupport final class CommentTests: PrettyPrintTestCase { func testDocumentationComments() { @@ -202,141 +202,141 @@ final class CommentTests: PrettyPrintTestCase { func testLineCommentsWithCustomLeadingSpaces() { let pairs: [(String, String)] = [ - ( - """ - // Line Comment0 - - // Line Comment1 - // Line Comment2 - let a = 123 - let b = "456" // End of line comment - let c = "More content" - - """, - """ - // Line Comment0 - - // Line Comment1 - // Line Comment2 - let a = 123 - let b = "456" // End of line comment - let c = "More content" - - """ - ), - ( - """ - // Comment 3 - // Comment 4 - - let reallyLongVariableName = 123 // This comment should not wrap - // and should not combine with this comment - - func MyFun() { - // just a comment - } - """, - """ - // Comment 3 - // Comment 4 + ( + """ + // Line Comment0 - let reallyLongVariableName = 123 // This comment should not wrap - // and should not combine with this comment + // Line Comment1 + // Line Comment2 + let a = 123 + let b = "456" // End of line comment + let c = "More content" - func MyFun() { - // just a comment - } + """, + """ + // Line Comment0 - """ - ), - ( - """ - func MyFun() { - // Comment 1 - // Comment 2 - let a = 123 + // Line Comment1 + // Line Comment2 + let a = 123 + let b = "456" // End of line comment + let c = "More content" - let b = 456 // Comment 3 - } + """ + ), + ( + """ + // Comment 3 + // Comment 4 - func MyFun() { - let c = 789 // Comment 4 - // Comment 5 - } - """, - """ - func MyFun() { - // Comment 1 - // Comment 2 - let a = 123 - - let b = 456 // Comment 3 - } + let reallyLongVariableName = 123 // This comment should not wrap + // and should not combine with this comment - func MyFun() { - let c = 789 // Comment 4 - // Comment 5 - } + func MyFun() { + // just a comment + } + """, + """ + // Comment 3 + // Comment 4 - """ - ), - ( - """ - let a = myfun(123 // Cmt 7 - ) - let a = myfun(var1: 123 // Cmt 7 - ) + let reallyLongVariableName = 123 // This comment should not wrap + // and should not combine with this comment - guard condition else { return // Cmt 6 - } + func MyFun() { + // just a comment + } - switch myvar { - case .one, .two, // three - .four: - dostuff() - default: () - } + """ + ), + ( + """ + func MyFun() { + // Comment 1 + // Comment 2 + let a = 123 - """, - """ - let a = myfun( - 123 // Cmt 7 - ) - let a = myfun( - var1: 123 // Cmt 7 - ) - - guard condition else { - return // Cmt 6 - } + let b = 456 // Comment 3 + } - switch myvar { - case .one, .two, // three - .four: - dostuff() - default: () - } + func MyFun() { + let c = 789 // Comment 4 + // Comment 5 + } + """, + """ + func MyFun() { + // Comment 1 + // Comment 2 + let a = 123 + + let b = 456 // Comment 3 + } + + func MyFun() { + let c = 789 // Comment 4 + // Comment 5 + } + + """ + ), + ( + """ + let a = myfun(123 // Cmt 7 + ) + let a = myfun(var1: 123 // Cmt 7 + ) + + guard condition else { return // Cmt 6 + } + + switch myvar { + case .one, .two, // three + .four: + dostuff() + default: () + } - """ - ), - ( - """ - let a = 123 + // comment - b + c - - let d = 123 - // Trailing Comment - """, - """ - let a = - 123 // comment - + b + c - - let d = 123 - // Trailing Comment - - """ - ), + """, + """ + let a = myfun( + 123 // Cmt 7 + ) + let a = myfun( + var1: 123 // Cmt 7 + ) + + guard condition else { + return // Cmt 6 + } + + switch myvar { + case .one, .two, // three + .four: + dostuff() + default: () + } + + """ + ), + ( + """ + let a = 123 + // comment + b + c + + let d = 123 + // Trailing Comment + """, + """ + let a = + 123 // comment + + b + c + + let d = 123 + // Trailing Comment + + """ + ), ] var config = Configuration.forTesting @@ -1004,7 +1004,7 @@ final class CommentTests: PrettyPrintTestCase { linelength: 45, whitespaceOnly: true, findings: [ - FindingSpec("1️⃣", message: "move end-of-line comment that exceeds the line length"), + FindingSpec("1️⃣", message: "move end-of-line comment that exceeds the line length") ] ) } diff --git a/Tests/SwiftFormatTests/PrettyPrint/ConsumeExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ConsumeExprTests.swift index 30a1b6a1c..dfe64d414 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ConsumeExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ConsumeExprTests.swift @@ -9,6 +9,7 @@ final class ConsumeExprTests: PrettyPrintTestCase { consume y """, - linelength: 16) + linelength: 16 + ) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/CopyExprSyntax.swift b/Tests/SwiftFormatTests/PrettyPrint/CopyExprSyntax.swift index 188121eef..5b6a65e95 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CopyExprSyntax.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CopyExprSyntax.swift @@ -9,6 +9,7 @@ final class CopyExprTests: PrettyPrintTestCase { copy y """, - linelength: 13) + linelength: 13 + ) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/DeclNameArgumentTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DeclNameArgumentTests.swift index ea7dd2c4a..0d02ae3a5 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/DeclNameArgumentTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/DeclNameArgumentTests.swift @@ -148,4 +148,3 @@ final class DeclNameArgumentTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 40) } } - diff --git a/Tests/SwiftFormatTests/PrettyPrint/DeinitializerDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DeinitializerDeclTests.swift index fc4d3fc36..4c219eb6b 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/DeinitializerDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/DeinitializerDeclTests.swift @@ -83,7 +83,7 @@ final class DeinitializerDeclTests: PrettyPrintTestCase { } """ assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 50) - + let wrapped = """ class X { // diff --git a/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift index d0df7747a..f903b9018 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/DictionaryDeclTests.swift @@ -59,16 +59,16 @@ final class DictionaryDeclTests: PrettyPrintTestCase { ] """ - // Ideally, this dictionary would be left on 1 line without a trailing comma. We don't know if - // the comma is required when calculating the length of elements, so the comma's length is - // always added to last element and that 1 character causes the newlines inside of the - // dictionary. - + """ - let a = [ - 10000: "abc", 20000: "def", 30000: "ghi", - ] + // Ideally, this dictionary would be left on 1 line without a trailing comma. We don't know if + // the comma is required when calculating the length of elements, so the comma's length is + // always added to last element and that 1 character causes the newlines inside of the + // dictionary. + + """ + let a = [ + 10000: "abc", 20000: "def", 30000: "ghi", + ] - """ + """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 50) } @@ -277,16 +277,16 @@ final class DictionaryDeclTests: PrettyPrintTestCase { ] """ - // Ideally, this dictionary would be left on 1 line without a trailing comma. We don't know if - // the comma is required when calculating the length of elements, so the comma's length is - // always added to last element and that 1 character causes the newlines inside of the - // dictionary. - + """ - a = [ - k1: ("ab", "z"), k2: ("bc", "y"), - ] + // Ideally, this dictionary would be left on 1 line without a trailing comma. We don't know if + // the comma is required when calculating the length of elements, so the comma's length is + // always added to last element and that 1 character causes the newlines inside of the + // dictionary. + + """ + a = [ + k1: ("ab", "z"), k2: ("bc", "y"), + ] - """ + """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 38) } diff --git a/Tests/SwiftFormatTests/PrettyPrint/DiscardStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DiscardStmtTests.swift index 63637da85..172f04ea4 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/DiscardStmtTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/DiscardStmtTests.swift @@ -8,6 +8,7 @@ final class DiscardStmtTests: PrettyPrintTestCase { discard self """, - linelength: 9) + linelength: 9 + ) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift index ddeb3ea6a..2bda1fc27 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/DoStmtTests.swift @@ -66,21 +66,28 @@ final class DoStmtTests: PrettyPrintTestCase { } """ - assertPrettyPrintEqual(input: input, expected: - """ - do - throws(FooError) { - foo() - } + assertPrettyPrintEqual( + input: input, + expected: + """ + do + throws(FooError) { + foo() + } - """, linelength: 18) - assertPrettyPrintEqual(input: input, expected: - """ - do throws(FooError) { - foo() - } + """, + linelength: 18 + ) + assertPrettyPrintEqual( + input: input, + expected: + """ + do throws(FooError) { + foo() + } - """, linelength: 25) + """, + linelength: 25 + ) } } - diff --git a/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift index 1561d8e8d..d51fbeae4 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/EnumDeclTests.swift @@ -285,7 +285,7 @@ final class EnumDeclTests: PrettyPrintTestCase { func testEnumWhereClause_lineBreakBeforeEachGenericRequirement() { let input = - """ + """ enum MyEnum where S: Collection { case firstCase let B: Double @@ -301,7 +301,7 @@ final class EnumDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ enum MyEnum where S: Collection { case firstCase let B: Double @@ -373,7 +373,7 @@ final class EnumDeclTests: PrettyPrintTestCase { func testEnumWhereClauseWithInheritance_lineBreakBeforeEachGenericRequirement() { let input = - """ + """ enum MyEnum: ProtoOne where S: Collection { case firstCase let B: Double @@ -389,7 +389,7 @@ final class EnumDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ enum MyEnum: ProtoOne where S: Collection { case firstCase let B: Double @@ -501,7 +501,7 @@ final class EnumDeclTests: PrettyPrintTestCase { func testEnumFullWrap_lineBreakBeforeEachGenericRequirement() { let input = - """ + """ public enum MyEnum: MyContainerProtocolOne, MyContainerProtocolTwo, SomeoneElsesContainerProtocol, SomeFrameworkContainerProtocol where BaseCollection: Collection, BaseCollection: P, BaseCollection.Element: Equatable, BaseCollection.Element: SomeOtherProtocol { case firstCase let B: Double @@ -510,7 +510,7 @@ final class EnumDeclTests: PrettyPrintTestCase { let expected = - """ + """ public enum MyEnum< BaseCollection, SecondCollection >: MyContainerProtocolOne, MyContainerProtocolTwo, diff --git a/Tests/SwiftFormatTests/PrettyPrint/ExtensionDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ExtensionDeclTests.swift index f11a1eb5b..d9b2bbe62 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ExtensionDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ExtensionDeclTests.swift @@ -122,7 +122,7 @@ final class ExtensionDeclTests: PrettyPrintTestCase { func testExtensionWhereClause_lineBreakBeforeEachGenericRequirement() { let input = - """ + """ extension MyExtension where S: Collection { let A: Int let B: Double @@ -138,7 +138,7 @@ final class ExtensionDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ extension MyExtension where S: Collection { let A: Int let B: Double @@ -210,7 +210,7 @@ final class ExtensionDeclTests: PrettyPrintTestCase { func testExtensionWhereClauseWithInheritance_lineBreakBeforeEachGenericRequirement() { let input = - """ + """ extension MyExtension: ProtoOne where S: Collection { let A: Int let B: Double @@ -226,7 +226,7 @@ final class ExtensionDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ extension MyExtension: ProtoOne where S: Collection { let A: Int let B: Double @@ -336,7 +336,7 @@ final class ExtensionDeclTests: PrettyPrintTestCase { func testExtensionFullWrap_lineBreakBeforeEachGenericRequirement() { let input = - """ + """ public extension MyContainer: MyContainerProtocolOne, MyContainerProtocolTwo, SomeoneElsesContainerProtocol, SomeFrameworkContainerProtocol where BaseCollection: Collection, BaseCollection: P, BaseCollection.Element: Equatable, BaseCollection.Element: SomeOtherProtocol { let A: Int let B: Double @@ -345,7 +345,7 @@ final class ExtensionDeclTests: PrettyPrintTestCase { let expected = - """ + """ public extension MyContainer: MyContainerProtocolOne, MyContainerProtocolTwo, SomeoneElsesContainerProtocol, diff --git a/Tests/SwiftFormatTests/PrettyPrint/FunctionDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/FunctionDeclTests.swift index 5d94fa5f2..743f11dca 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/FunctionDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/FunctionDeclTests.swift @@ -359,7 +359,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { func testFunctionWhereClause_lineBreakBeforeEachGenericRequirement() { let input = - """ + """ public func index( of element: Element, in collection: Elements ) -> Elements.Index? where Elements.Element == Element { @@ -385,7 +385,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ public func index( of element: Element, in collection: Elements ) -> Elements.Index? @@ -447,7 +447,6 @@ final class FunctionDeclTests: PrettyPrintTestCase { } """ - let expected = """ func myFun() { @@ -556,67 +555,67 @@ final class FunctionDeclTests: PrettyPrintTestCase { func testFunctionFullWrap() { let input = - """ - @discardableResult @objc - public func index(of element: Element, in collection: Elements) -> Elements.Index? where Element: Foo, Element: Bar, Elements.Element == Element { - let a = 123 - let b = "abc" - } - """ + """ + @discardableResult @objc + public func index(of element: Element, in collection: Elements) -> Elements.Index? where Element: Foo, Element: Bar, Elements.Element == Element { + let a = 123 + let b = "abc" + } + """ let expected = - """ - @discardableResult @objc - public func index< - Elements: Collection, - Element - >( - of element: Element, - in collection: Elements - ) -> Elements.Index? - where - Element: Foo, Element: Bar, - Elements.Element == Element - { - let a = 123 - let b = "abc" - } - - """ + """ + @discardableResult @objc + public func index< + Elements: Collection, + Element + >( + of element: Element, + in collection: Elements + ) -> Elements.Index? + where + Element: Foo, Element: Bar, + Elements.Element == Element + { + let a = 123 + let b = "abc" + } + + """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 30) } func testFunctionFullWrap_lineBreakBeforeEachGenericRequirement() { let input = - """ - @discardableResult @objc - public func index(of element: Element, in collection: Elements) -> Elements.Index? where Element: Foo, Element: Bar, Elements.Element == Element { - let a = 123 - let b = "abc" - } - """ + """ + @discardableResult @objc + public func index(of element: Element, in collection: Elements) -> Elements.Index? where Element: Foo, Element: Bar, Elements.Element == Element { + let a = 123 + let b = "abc" + } + """ let expected = - """ - @discardableResult @objc - public func index< - Elements: Collection, - Element - >( - of element: Element, - in collection: Elements - ) -> Elements.Index? - where - Element: Foo, - Element: Bar, - Elements.Element == Element - { - let a = 123 - let b = "abc" - } - - """ + """ + @discardableResult @objc + public func index< + Elements: Collection, + Element + >( + of element: Element, + in collection: Elements + ) -> Elements.Index? + where + Element: Foo, + Element: Bar, + Elements.Element == Element + { + let a = 123 + let b = "abc" + } + + """ var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true @@ -626,7 +625,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { func testEmptyFunction() { let input = "func foo() {}" assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 50) - + let wrapped = """ func foo() { } @@ -703,7 +702,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 23) expected = - """ + """ func name(_ x: Int) throws -> R @@ -721,7 +720,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { func testBreaksBeforeOrInsideOutput_prioritizingKeepingOutputTogether() { let input = - """ + """ func name(_ x: Int) throws -> R func name(_ x: Int) throws -> R { @@ -731,7 +730,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ var expected = - """ + """ func name( _ x: Int ) throws -> R @@ -749,19 +748,19 @@ final class FunctionDeclTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 23, configuration: config) expected = - """ - func name( - _ x: Int - ) throws -> R - - func name( - _ x: Int - ) throws -> R { - statement - statement - } + """ + func name( + _ x: Int + ) throws -> R + + func name( + _ x: Int + ) throws -> R { + statement + statement + } - """ + """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) assertPrettyPrintEqual(input: input, expected: expected, linelength: 33, configuration: config) } @@ -801,7 +800,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { func testBreaksBeforeOrInsideOutputWithAttributes_prioritizingKeepingOutputTogether() { let input = - """ + """ @objc @discardableResult func name(_ x: Int) throws -> R @@ -813,7 +812,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ @objc @discardableResult func name( @@ -894,7 +893,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { func testBreaksBeforeOrInsideOutputWithWhereClause_prioritizingKeepingOutputTogether() { var input = - """ + """ func name(_ x: Int) throws -> R where Foo == Bar func name(_ x: Int) throws -> R where Foo == Bar { @@ -904,7 +903,7 @@ final class FunctionDeclTests: PrettyPrintTestCase { """ var expected = - """ + """ func name( _ x: Int ) throws -> R @@ -924,34 +923,34 @@ final class FunctionDeclTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 23, configuration: config) input = - """ - func name(_ x: Int) throws -> R where Fooooooo == Barrrrr - - func name(_ x: Int) throws -> R where Fooooooo == Barrrrr { - statement - statement - } - """ + """ + func name(_ x: Int) throws -> R where Fooooooo == Barrrrr - expected = - """ - func name( - _ x: Int - ) throws -> R - where - Fooooooo == Barrrrr - - func name( - _ x: Int - ) throws -> R - where - Fooooooo == Barrrrr - { + func name(_ x: Int) throws -> R where Fooooooo == Barrrrr { statement statement - } + } + """ - """ + expected = + """ + func name( + _ x: Int + ) throws -> R + where + Fooooooo == Barrrrr + + func name( + _ x: Int + ) throws -> R + where + Fooooooo == Barrrrr + { + statement + statement + } + + """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 23, configuration: config) } @@ -1016,24 +1015,24 @@ final class FunctionDeclTests: PrettyPrintTestCase { func testDoesNotCollapseFunctionParameterAttributes() { let input = - """ - func foo(@ViewBuilder bar: () -> View) { - bar() - } + """ + func foo(@ViewBuilder bar: () -> View) { + bar() + } - """ + """ assertPrettyPrintEqual(input: input, expected: input, linelength: 60) } func testDoesNotCollapseStackedFunctionParameterAttributes() { let input = - """ - func foo(@FakeAttr @ViewBuilder bar: () -> View) { - bar() - } + """ + func foo(@FakeAttr @ViewBuilder bar: () -> View) { + bar() + } - """ + """ assertPrettyPrintEqual(input: input, expected: input, linelength: 80) } diff --git a/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift index 4f4a85b3b..1b725a94b 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/IfConfigTests.swift @@ -551,7 +551,7 @@ final class IfConfigTests: PrettyPrintTestCase { default: return nil } - + """ var configuration = Configuration.forTesting configuration.indentConditionalCompilationBlocks = false diff --git a/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift index 3153ccd80..46fa3f467 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/IgnoreNodeTests.swift @@ -227,7 +227,7 @@ final class IgnoreNodeTests: PrettyPrintTestCase { """ - assertPrettyPrintEqual(input: input, expected: expected, linelength: 50) + assertPrettyPrintEqual(input: input, expected: expected, linelength: 50) } func testValidComment() { diff --git a/Tests/SwiftFormatTests/PrettyPrint/InitializerDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/InitializerDeclTests.swift index d1227605a..6c15ff53f 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/InitializerDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/InitializerDeclTests.swift @@ -92,7 +92,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { func testInitializerOptionality() { let input = - """ + """ struct Struct { init? (var1: Int, var2: Double) { print("Hello World") @@ -106,9 +106,9 @@ final class InitializerDeclTests: PrettyPrintTestCase { init!() { let a = "AAAA BBBB CCCC DDDD EEEE FFFF" } } """ - + let expected = - """ + """ struct Struct { init?(var1: Int, var2: Double) { print("Hello World") @@ -128,7 +128,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { } """ - + var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false assertPrettyPrintEqual(input: input, expected: expected, linelength: 50, configuration: config) @@ -170,80 +170,80 @@ final class InitializerDeclTests: PrettyPrintTestCase { func testInitializerGenericParameters() { let input = - """ - struct Struct { - init(var1: S, var2: T) { - let a = 123 - print("Hello World") - } - init(var1: ReallyLongTypeName, var2: TypeName) { - let a = 123 - let b = 456 + """ + struct Struct { + init(var1: S, var2: T) { + let a = 123 + print("Hello World") + } + init(var1: ReallyLongTypeName, var2: TypeName) { + let a = 123 + let b = 456 + } } - } - """ + """ let expected = - """ - struct Struct { - init(var1: S, var2: T) { - let a = 123 - print("Hello World") - } - init< - ReallyLongTypeName: Conform, - TypeName - >( - var1: ReallyLongTypeName, - var2: TypeName - ) { - let a = 123 - let b = 456 + """ + struct Struct { + init(var1: S, var2: T) { + let a = 123 + print("Hello World") + } + init< + ReallyLongTypeName: Conform, + TypeName + >( + var1: ReallyLongTypeName, + var2: TypeName + ) { + let a = 123 + let b = 456 + } } - } - """ + """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 40) } func testInitializerWhereClause() { let input = - """ - struct Struct { - public init(element: Element, in collection: Elements) where Elements.Element == Element { - let a = 123 - let b = "abc" - } - public init(element: Element, in collection: Elements) where Elements.Element == Element, Element: P, Element: Equatable { - let a = 123 - let b = "abc" + """ + struct Struct { + public init(element: Element, in collection: Elements) where Elements.Element == Element { + let a = 123 + let b = "abc" + } + public init(element: Element, in collection: Elements) where Elements.Element == Element, Element: P, Element: Equatable { + let a = 123 + let b = "abc" + } } - } - """ + """ let expected = - """ - struct Struct { - public init( - element: Element, in collection: Elements - ) where Elements.Element == Element { - let a = 123 - let b = "abc" - } - public init( - element: Element, in collection: Elements - ) - where - Elements.Element == Element, Element: P, - Element: Equatable - { - let a = 123 - let b = "abc" + """ + struct Struct { + public init( + element: Element, in collection: Elements + ) where Elements.Element == Element { + let a = 123 + let b = "abc" + } + public init( + element: Element, in collection: Elements + ) + where + Elements.Element == Element, Element: P, + Element: Equatable + { + let a = 123 + let b = "abc" + } } - } - """ + """ var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false @@ -252,42 +252,42 @@ final class InitializerDeclTests: PrettyPrintTestCase { func testInitializerWhereClause_lineBreakBeforeEachGenericRequirement() { let input = - """ - struct Struct { - public init(element: Element, in collection: Elements) where Elements.Element == Element { - let a = 123 - let b = "abc" - } - public init(element: Element, in collection: Elements) where Elements.Element == Element, Element: P, Element: Equatable { - let a = 123 - let b = "abc" + """ + struct Struct { + public init(element: Element, in collection: Elements) where Elements.Element == Element { + let a = 123 + let b = "abc" + } + public init(element: Element, in collection: Elements) where Elements.Element == Element, Element: P, Element: Equatable { + let a = 123 + let b = "abc" + } } - } - """ + """ let expected = - """ - struct Struct { - public init( - element: Element, in collection: Elements - ) where Elements.Element == Element { - let a = 123 - let b = "abc" - } - public init( - element: Element, in collection: Elements - ) - where - Elements.Element == Element, - Element: P, - Element: Equatable - { - let a = 123 - let b = "abc" + """ + struct Struct { + public init( + element: Element, in collection: Elements + ) where Elements.Element == Element { + let a = 123 + let b = "abc" + } + public init( + element: Element, in collection: Elements + ) + where + Elements.Element == Element, + Element: P, + Element: Equatable + { + let a = 123 + let b = "abc" + } } - } - """ + """ var config = Configuration.forTesting config.lineBreakBeforeEachArgument = false @@ -376,7 +376,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { func testInitializerFullWrap_lineBreakBeforeEachGenericRequirement() { let input = - """ + """ struct Struct { @objc @inlinable public init(element: Element, in collection: Elements) where Elements.Element == Element, Element: Equatable, Element: P { let a = 123 @@ -386,7 +386,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ struct Struct { @objc @inlinable public init< @@ -422,7 +422,7 @@ final class InitializerDeclTests: PrettyPrintTestCase { } """ assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 50) - + let wrapped = """ struct X { // diff --git a/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift b/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift index 0616c66e3..0da0718ce 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/MacroCallTests.swift @@ -122,7 +122,7 @@ final class MacroCallTests: PrettyPrintTestCase { #Preview("Name") { EmptyView() } - + """ assertPrettyPrintEqual(input: input, expected: input, linelength: 45) } diff --git a/Tests/SwiftFormatTests/PrettyPrint/MemberAccessExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/MemberAccessExprTests.swift index 6b0ed8cf5..8ab588c04 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/MemberAccessExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/MemberAccessExprTests.swift @@ -121,8 +121,11 @@ final class MemberAccessExprTests: PrettyPrintTestCase { var configuration = Configuration.forTesting configuration.lineBreakAroundMultilineExpressionChainComponents = true assertPrettyPrintEqual( - input: input, expected: expectedWithForcedBreaks, linelength: 20, - configuration: configuration) + input: input, + expected: expectedWithForcedBreaks, + linelength: 20, + configuration: configuration + ) } func testContinuationRestorationAfterGroup() { @@ -238,8 +241,11 @@ final class MemberAccessExprTests: PrettyPrintTestCase { var configuration = Configuration.forTesting configuration.lineBreakAroundMultilineExpressionChainComponents = true assertPrettyPrintEqual( - input: input, expected: expectedWithForcedBreaking, linelength: 35, - configuration: configuration) + input: input, + expected: expectedWithForcedBreaking, + linelength: 35, + configuration: configuration + ) } func testMemberItemClosureChaining() { @@ -332,30 +338,33 @@ final class MemberAccessExprTests: PrettyPrintTestCase { var configuration = Configuration.forTesting configuration.lineBreakAroundMultilineExpressionChainComponents = true assertPrettyPrintEqual( - input: input, expected: expectedWithForcedBreaks, linelength: 50, - configuration: configuration) + input: input, + expected: expectedWithForcedBreaks, + linelength: 50, + configuration: configuration + ) } func testChainedTrailingClosureMethods() { let input = """ - var button = View.Button { Text("ABC") }.action { presentAction() }.background(.red).text(.blue).text(.red).font(.appleSans) - var button = View.Button { - // comment #0 - Text("ABC") - }.action { presentAction() }.background(.red).text(.blue).text(.red).font(.appleSans) - var button = View.Button { Text("ABC") } - .action { presentAction() }.background(.red).text(.blue) .text(.red).font(.appleSans) - var button = View.Button { Text("ABC") } - .action { - // comment #1 - presentAction() // comment #2 - }.background(.red).text(.blue) .text(.red).font(.appleSans) /* trailing comment */ - var button = View.Button { Text("ABC") }.action { presentAction() }.background(.red).text(.blue).text(.red).font(.appleSans).foo { - abc in - return abc.foo.bar - } - """ + var button = View.Button { Text("ABC") }.action { presentAction() }.background(.red).text(.blue).text(.red).font(.appleSans) + var button = View.Button { + // comment #0 + Text("ABC") + }.action { presentAction() }.background(.red).text(.blue).text(.red).font(.appleSans) + var button = View.Button { Text("ABC") } + .action { presentAction() }.background(.red).text(.blue) .text(.red).font(.appleSans) + var button = View.Button { Text("ABC") } + .action { + // comment #1 + presentAction() // comment #2 + }.background(.red).text(.blue) .text(.red).font(.appleSans) /* trailing comment */ + var button = View.Button { Text("ABC") }.action { presentAction() }.background(.red).text(.blue).text(.red).font(.appleSans).foo { + abc in + return abc.foo.bar + } + """ let expectedNoForcedBreaks = """ @@ -425,8 +434,11 @@ final class MemberAccessExprTests: PrettyPrintTestCase { var configuration = Configuration.forTesting configuration.lineBreakAroundMultilineExpressionChainComponents = true assertPrettyPrintEqual( - input: input, expected: expectedWithForcedBreaks, linelength: 50, - configuration: configuration) + input: input, + expected: expectedWithForcedBreaks, + linelength: 50, + configuration: configuration + ) } func testChainedSubscriptExprs() { @@ -514,7 +526,10 @@ final class MemberAccessExprTests: PrettyPrintTestCase { var configuration = Configuration.forTesting configuration.lineBreakAroundMultilineExpressionChainComponents = true assertPrettyPrintEqual( - input: input, expected: expectedWithForcedBreaks, linelength: 50, - configuration: configuration) + input: input, + expected: expectedWithForcedBreaks, + linelength: 50, + configuration: configuration + ) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/ParameterPackTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ParameterPackTests.swift index c4615dfab..0e666e041 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ParameterPackTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ParameterPackTests.swift @@ -14,7 +14,8 @@ final class ParameterPackTests: PrettyPrintTestCase { > {} """, - linelength: 22) + linelength: 22 + ) } func testPackExpansionsAndElements() { @@ -27,7 +28,8 @@ final class ParameterPackTests: PrettyPrintTestCase { of: each value) """, - linelength: 25) + linelength: 25 + ) assertPrettyPrintEqual( input: """ @@ -41,6 +43,7 @@ final class ParameterPackTests: PrettyPrintTestCase { ) """, - linelength: 7) + linelength: 7 + ) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift index eaa33ac3a..abe0400b7 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift @@ -1,10 +1,9 @@ import SwiftFormat +@_spi(Rules) @_spi(Testing) import SwiftFormat import SwiftOperators -import SwiftSyntax import SwiftParser +import SwiftSyntax import XCTest - -@_spi(Rules) @_spi(Testing) import SwiftFormat @_spi(Testing) import _SwiftFormatTestSupport class PrettyPrintTestCase: DiagnosingTestCase { @@ -45,11 +44,15 @@ class PrettyPrintTestCase: DiagnosingTestCase { configuration: configuration, selection: markedInput.selection, whitespaceOnly: whitespaceOnly, - findingConsumer: { emittedFindings.append($0) }) + findingConsumer: { emittedFindings.append($0) } + ) assertStringsEqualWithDiff( - formatted, expected, + formatted, + expected, "Pretty-printed result was not what was expected", - file: file, line: line) + file: file, + line: line + ) // FIXME: It would be nice to check findings when whitespaceOnly == false, but their locations // are wrong. @@ -60,7 +63,8 @@ class PrettyPrintTestCase: DiagnosingTestCase { emittedFindings: emittedFindings, context: context, file: file, - line: line) + line: line + ) } // Idempotency check: Running the formatter multiple times should not change the outcome. @@ -75,7 +79,12 @@ class PrettyPrintTestCase: DiagnosingTestCase { findingConsumer: { _ in } // Ignore findings during the idempotence check. ) assertStringsEqualWithDiff( - reformatted, formatted, "Pretty printer is not idempotent", file: file, line: line) + reformatted, + formatted, + "Pretty printer is not idempotent", + file: file, + line: line + ) } } @@ -98,18 +107,20 @@ class PrettyPrintTestCase: DiagnosingTestCase { // Ignore folding errors for unrecognized operators so that we fallback to a reasonable default. let sourceFileSyntax = OperatorTable.standardOperators.foldAll(Parser.parse(source: source)) { _ in } - .as(SourceFileSyntax.self)! + .as(SourceFileSyntax.self)! let context = makeContext( sourceFileSyntax: sourceFileSyntax, configuration: configuration, selection: selection, - findingConsumer: findingConsumer) + findingConsumer: findingConsumer + ) let printer = PrettyPrinter( context: context, source: source, node: Syntax(sourceFileSyntax), printTokenStream: false, - whitespaceOnly: whitespaceOnly) + whitespaceOnly: whitespaceOnly + ) return (printer.prettyPrint(), context) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/ProtocolDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ProtocolDeclTests.swift index d10db6c93..2bad29363 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/ProtocolDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/ProtocolDeclTests.swift @@ -89,7 +89,6 @@ final class ProtocolDeclTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 50) } - func testProtocolAttributes() { let input = """ @@ -152,7 +151,7 @@ final class ProtocolDeclTests: PrettyPrintTestCase { func doStuff(firstArg: Foo, second second: Bar, third third: Baz) -> Output } """ - + let expected = """ protocol MyProtocol { @@ -177,7 +176,7 @@ final class ProtocolDeclTests: PrettyPrintTestCase { } """ - + assertPrettyPrintEqual(input: input, expected: expected, linelength: 30) } @@ -189,7 +188,7 @@ final class ProtocolDeclTests: PrettyPrintTestCase { init(reallyLongLabel: Int, anotherLongLabel: Bool) } """ - + let expected = """ protocol MyProtocol { @@ -200,7 +199,7 @@ final class ProtocolDeclTests: PrettyPrintTestCase { } """ - + assertPrettyPrintEqual(input: input, expected: expected, linelength: 30) } diff --git a/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift b/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift index 65226cf5e..fadbc59fe 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/RespectsExistingLineBreaksTests.swift @@ -24,8 +24,11 @@ final class RespectsExistingLineBreaksTests: PrettyPrintTestCase { """ assertPrettyPrintEqual( - input: input, expected: expectedRespecting, linelength: 12, - configuration: configuration(respectingExistingLineBreaks: true)) + input: input, + expected: expectedRespecting, + linelength: 12, + configuration: configuration(respectingExistingLineBreaks: true) + ) let expectedNotRespecting = """ @@ -36,8 +39,11 @@ final class RespectsExistingLineBreaksTests: PrettyPrintTestCase { """ assertPrettyPrintEqual( - input: input, expected: expectedNotRespecting, linelength: 25, - configuration: configuration(respectingExistingLineBreaks: false)) + input: input, + expected: expectedNotRespecting, + linelength: 25, + configuration: configuration(respectingExistingLineBreaks: false) + ) } func testCodeBlocksAndMemberDecls() { @@ -82,8 +88,11 @@ final class RespectsExistingLineBreaksTests: PrettyPrintTestCase { // No changes expected when respecting existing newlines. assertPrettyPrintEqual( - input: input, expected: input + "\n", linelength: 80, - configuration: configuration(respectingExistingLineBreaks: true)) + input: input, + expected: input + "\n", + linelength: 80, + configuration: configuration(respectingExistingLineBreaks: true) + ) let expectedNotRespecting = """ @@ -110,8 +119,11 @@ final class RespectsExistingLineBreaksTests: PrettyPrintTestCase { """ assertPrettyPrintEqual( - input: input, expected: expectedNotRespecting, linelength: 80, - configuration: configuration(respectingExistingLineBreaks: false)) + input: input, + expected: expectedNotRespecting, + linelength: 80, + configuration: configuration(respectingExistingLineBreaks: false) + ) } func testSemicolons() { @@ -130,8 +142,11 @@ final class RespectsExistingLineBreaksTests: PrettyPrintTestCase { // the same line if they were originally like that and likewise preserve newlines after // semicolons if present. assertPrettyPrintEqual( - input: input, expected: input + "\n", linelength: 80, - configuration: configuration(respectingExistingLineBreaks: true)) + input: input, + expected: input + "\n", + linelength: 80, + configuration: configuration(respectingExistingLineBreaks: true) + ) let expectedNotRespecting = """ @@ -150,8 +165,11 @@ final class RespectsExistingLineBreaksTests: PrettyPrintTestCase { // When not respecting newlines every semicolon-delimited statement or declaration should end up // on its own line. assertPrettyPrintEqual( - input: input, expected: expectedNotRespecting, linelength: 80, - configuration: configuration(respectingExistingLineBreaks: false)) + input: input, + expected: expectedNotRespecting, + linelength: 80, + configuration: configuration(respectingExistingLineBreaks: false) + ) } func testInvalidBreaksAreAlwaysRejected() { @@ -176,8 +194,11 @@ final class RespectsExistingLineBreaksTests: PrettyPrintTestCase { """ assertPrettyPrintEqual( - input: input, expected: expectedRespecting, linelength: 80, - configuration: configuration(respectingExistingLineBreaks: true)) + input: input, + expected: expectedRespecting, + linelength: 80, + configuration: configuration(respectingExistingLineBreaks: true) + ) let expectedNotRespecting = """ @@ -186,8 +207,11 @@ final class RespectsExistingLineBreaksTests: PrettyPrintTestCase { """ assertPrettyPrintEqual( - input: input, expected: expectedNotRespecting, linelength: 80, - configuration: configuration(respectingExistingLineBreaks: false)) + input: input, + expected: expectedNotRespecting, + linelength: 80, + configuration: configuration(respectingExistingLineBreaks: false) + ) } /// Creates a new configuration with the given value for `respectsExistingLineBreaks` and default diff --git a/Tests/SwiftFormatTests/PrettyPrint/SemicolonTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SemicolonTests.swift index 80b746152..68f0420f8 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/SemicolonTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/SemicolonTests.swift @@ -12,20 +12,20 @@ final class SemiColonTypeTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 50) } - + func testNoSemicolon() { let input = """ - var foo = false - guard !foo else { return } - defer { foo = true } + var foo = false + guard !foo else { return } + defer { foo = true } - struct Foo { - var foo = false - var bar = true - var baz = false - } - """ + struct Foo { + var foo = false + var bar = true + var baz = false + } + """ assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 50) } diff --git a/Tests/SwiftFormatTests/PrettyPrint/StringTests.swift b/Tests/SwiftFormatTests/PrettyPrint/StringTests.swift index 3c9f937e7..d97d3e0a0 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/StringTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/StringTests.swift @@ -50,7 +50,8 @@ final class StringTests: PrettyPrintTestCase { input: input, expected: expected, linelength: 30, - configuration: config) + configuration: config + ) } func testMultilineStringIsNotReformattedWithIgnore() { @@ -109,32 +110,32 @@ final class StringTests: PrettyPrintTestCase { func testMultilineStringWithInterpolations() { let input = - #""" - if true { - guard let opt else { - functionCall(""" - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero \(2) \(testVariable) ids risus placerat imperdiet. Praesent fringilla vel nisi sed fermentum. In vitae purus feugiat, euismod nulla in, rhoncus leo. Suspendisse feugiat sapien lobortis facilisis malesuada. Aliquam feugiat suscipit accumsan. Praesent tempus fermentum est, vel blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt efficitur ante id fermentum. - """) - } + #""" + if true { + guard let opt else { + functionCall(""" + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero \(2) \(testVariable) ids risus placerat imperdiet. Praesent fringilla vel nisi sed fermentum. In vitae purus feugiat, euismod nulla in, rhoncus leo. Suspendisse feugiat sapien lobortis facilisis malesuada. Aliquam feugiat suscipit accumsan. Praesent tempus fermentum est, vel blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt efficitur ante id fermentum. + """) } - """# + } + """# let expected = - #""" - if true { - guard let opt else { - functionCall( - """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero \(2) \ - \(testVariable) ids risus placerat imperdiet. Praesent fringilla vel nisi sed fermentum. In \ - vitae purus feugiat, euismod nulla in, rhoncus leo. Suspendisse feugiat sapien lobortis \ - facilisis malesuada. Aliquam feugiat suscipit accumsan. Praesent tempus fermentum est, vel \ - blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt efficitur ante id fermentum. - """) - } + #""" + if true { + guard let opt else { + functionCall( + """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero \(2) \ + \(testVariable) ids risus placerat imperdiet. Praesent fringilla vel nisi sed fermentum. In \ + vitae purus feugiat, euismod nulla in, rhoncus leo. Suspendisse feugiat sapien lobortis \ + facilisis malesuada. Aliquam feugiat suscipit accumsan. Praesent tempus fermentum est, vel \ + blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt efficitur ante id fermentum. + """) } + } - """# + """# var config = Configuration() config.reflowMultilineStringLiterals = .onlyLinesOverLength @@ -143,25 +144,25 @@ final class StringTests: PrettyPrintTestCase { func testMutlilineStringsRespectsHardLineBreaks() { let input = - #""" - """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero ids risus placerat imperdiet. Praesent fringilla vel nisi sed fermentum. In vitae purus feugiat, euismod nulla in, rhoncus leo. - Suspendisse feugiat sapien lobortis facilisis malesuada. Aliquam feugiat suscipit accumsan. Praesent tempus fermentum est, vel blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt efficitur ante id fermentum. - """ - """# + #""" + """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero ids risus placerat imperdiet. Praesent fringilla vel nisi sed fermentum. In vitae purus feugiat, euismod nulla in, rhoncus leo. + Suspendisse feugiat sapien lobortis facilisis malesuada. Aliquam feugiat suscipit accumsan. Praesent tempus fermentum est, vel blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt efficitur ante id fermentum. + """ + """# let expected = - #""" - """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero ids risus placerat \ - imperdiet. Praesent fringilla vel nisi sed fermentum. In vitae purus feugiat, euismod nulla in, \ - rhoncus leo. - Suspendisse feugiat sapien lobortis facilisis malesuada. Aliquam feugiat suscipit accumsan. \ - Praesent tempus fermentum est, vel blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt \ - efficitur ante id fermentum. - """ + #""" + """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rutrum libero ids risus placerat \ + imperdiet. Praesent fringilla vel nisi sed fermentum. In vitae purus feugiat, euismod nulla in, \ + rhoncus leo. + Suspendisse feugiat sapien lobortis facilisis malesuada. Aliquam feugiat suscipit accumsan. \ + Praesent tempus fermentum est, vel blandit mi pretium a. Proin in posuere sapien. Nunc tincidunt \ + efficitur ante id fermentum. + """ - """# + """# var config = Configuration() config.reflowMultilineStringLiterals = .onlyLinesOverLength @@ -273,19 +274,19 @@ final class StringTests: PrettyPrintTestCase { func testMultilineStringWithWordLongerThanLineLength() { let input = - #""" - """ - there isn't an opportunity to break up this long url: https://www.cool-math-games.org/games/id?=01913310-b7c3-77d8-898e-300ccd451ea8 - """ - """# + #""" + """ + there isn't an opportunity to break up this long url: https://www.cool-math-games.org/games/id?=01913310-b7c3-77d8-898e-300ccd451ea8 + """ + """# let expected = - #""" - """ - there isn't an opportunity to break up this long url: \ - https://www.cool-math-games.org/games/id?=01913310-b7c3-77d8-898e-300ccd451ea8 - """ + #""" + """ + there isn't an opportunity to break up this long url: \ + https://www.cool-math-games.org/games/id?=01913310-b7c3-77d8-898e-300ccd451ea8 + """ - """# + """# var config = Configuration() config.reflowMultilineStringLiterals = .onlyLinesOverLength diff --git a/Tests/SwiftFormatTests/PrettyPrint/StructDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/StructDeclTests.swift index 664fc59d6..b05e203df 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/StructDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/StructDeclTests.swift @@ -208,7 +208,7 @@ final class StructDeclTests: PrettyPrintTestCase { func testStructWhereClause_lineBreakBeforeEachGenericRequirement() { let input = - """ + """ struct MyStruct where S: Collection { let A: Int let B: Double @@ -224,7 +224,7 @@ final class StructDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ struct MyStruct where S: Collection { let A: Int let B: Double @@ -295,7 +295,7 @@ final class StructDeclTests: PrettyPrintTestCase { func testStructWhereClauseWithInheritance_lineBreakBeforeEachGenericRequirement() { let input = - """ + """ struct MyStruct: ProtoOne where S: Collection { let A: Int let B: Double @@ -311,7 +311,7 @@ final class StructDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ struct MyStruct: ProtoOne where S: Collection { let A: Int let B: Double diff --git a/Tests/SwiftFormatTests/PrettyPrint/SubscriptDeclTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SubscriptDeclTests.swift index 9a06c0bab..f7dae1118 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/SubscriptDeclTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/SubscriptDeclTests.swift @@ -166,7 +166,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { func testSubscriptGenericWhere_lineBreakBeforeEachGenericRequirement() { let input = - """ + """ struct MyStruct { subscript(var1: Element, var2: Elements) -> Double where Elements.Element == Element { return 1.23 @@ -178,7 +178,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { """ let expected = - """ + """ struct MyStruct { subscript( var1: Element, var2: Elements @@ -262,7 +262,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { func testBreaksBeforeOrInsideOutput() { let input = - """ + """ protocol MyProtocol { subscript(index: Int) -> R } @@ -276,7 +276,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { """ var expected = - """ + """ protocol MyProtocol { subscript(index: Int) -> R @@ -295,29 +295,29 @@ final class SubscriptDeclTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 26) expected = - """ - protocol MyProtocol { - subscript(index: Int) - -> R - } - - struct MyStruct { - subscript(index: Int) - -> R - { - statement - statement - } - } - - """ + """ + protocol MyProtocol { + subscript(index: Int) + -> R + } + + struct MyStruct { + subscript(index: Int) + -> R + { + statement + statement + } + } + + """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 27) assertPrettyPrintEqual(input: input, expected: expected, linelength: 30) } func testBreaksBeforeOrInsideOutput_prioritizingKeepingOutputTogether() { let input = - """ + """ protocol MyProtocol { subscript(index: Int) -> R } @@ -331,7 +331,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { """ var expected = - """ + """ protocol MyProtocol { subscript( index: Int @@ -353,124 +353,124 @@ final class SubscriptDeclTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 26, configuration: config) expected = - """ - protocol MyProtocol { - subscript( - index: Int - ) -> R - } - - struct MyStruct { - subscript( - index: Int - ) -> R { - statement - statement - } - } - - """ + """ + protocol MyProtocol { + subscript( + index: Int + ) -> R + } + + struct MyStruct { + subscript( + index: Int + ) -> R { + statement + statement + } + } + + """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 27, configuration: config) assertPrettyPrintEqual(input: input, expected: expected, linelength: 30, configuration: config) } func testSubscriptFullWrap() { let input = - """ - struct MyStruct { - @discardableResult @objc - subscript(var1: Element, var2: ManyElements) -> ManyElements.Index? where Element: Foo, Element: Bar, ManyElements.Element == Element { - get { - let out = vals[var1][var2] - return out - } - set(newValue) { - let tmp = compute(newValue) - vals[var1][var2] = tmp + """ + struct MyStruct { + @discardableResult @objc + subscript(var1: Element, var2: ManyElements) -> ManyElements.Index? where Element: Foo, Element: Bar, ManyElements.Element == Element { + get { + let out = vals[var1][var2] + return out + } + set(newValue) { + let tmp = compute(newValue) + vals[var1][var2] = tmp + } } } - } - """ + """ let expected = - """ - struct MyStruct { - @discardableResult @objc - subscript< - ManyElements: Collection, - Element - >( - var1: Element, - var2: ManyElements - ) -> ManyElements.Index? - where - Element: Foo, Element: Bar, - ManyElements.Element - == Element - { - get { - let out = vals[var1][var2] - return out - } - set(newValue) { - let tmp = compute(newValue) - vals[var1][var2] = tmp - } - } - } - - """ + """ + struct MyStruct { + @discardableResult @objc + subscript< + ManyElements: Collection, + Element + >( + var1: Element, + var2: ManyElements + ) -> ManyElements.Index? + where + Element: Foo, Element: Bar, + ManyElements.Element + == Element + { + get { + let out = vals[var1][var2] + return out + } + set(newValue) { + let tmp = compute(newValue) + vals[var1][var2] = tmp + } + } + } + + """ assertPrettyPrintEqual(input: input, expected: expected, linelength: 34) } func testSubscriptFullWrap_lineBreakBeforeEachGenericRequirement() { let input = - """ - struct MyStruct { - @discardableResult @objc - subscript(var1: Element, var2: ManyElements) -> ManyElements.Index? where Element: Foo, Element: Bar, ManyElements.Element == Element { - get { - let out = vals[var1][var2] - return out - } - set(newValue) { - let tmp = compute(newValue) - vals[var1][var2] = tmp + """ + struct MyStruct { + @discardableResult @objc + subscript(var1: Element, var2: ManyElements) -> ManyElements.Index? where Element: Foo, Element: Bar, ManyElements.Element == Element { + get { + let out = vals[var1][var2] + return out + } + set(newValue) { + let tmp = compute(newValue) + vals[var1][var2] = tmp + } } } - } - """ + """ let expected = - """ - struct MyStruct { - @discardableResult @objc - subscript< - ManyElements: Collection, - Element - >( - var1: Element, - var2: ManyElements - ) -> ManyElements.Index? - where - Element: Foo, - Element: Bar, - ManyElements.Element - == Element - { - get { - let out = vals[var1][var2] - return out - } - set(newValue) { - let tmp = compute(newValue) - vals[var1][var2] = tmp - } - } - } - - """ + """ + struct MyStruct { + @discardableResult @objc + subscript< + ManyElements: Collection, + Element + >( + var1: Element, + var2: ManyElements + ) -> ManyElements.Index? + where + Element: Foo, + Element: Bar, + ManyElements.Element + == Element + { + get { + let out = vals[var1][var2] + return out + } + set(newValue) { + let tmp = compute(newValue) + vals[var1][var2] = tmp + } + } + } + + """ var config = Configuration.forTesting config.lineBreakBeforeEachGenericRequirement = true @@ -486,7 +486,7 @@ final class SubscriptDeclTests: PrettyPrintTestCase { } """ assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 50) - + let wrapped = """ struct X { // diff --git a/Tests/SwiftFormatTests/PrettyPrint/SubscriptExprTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SubscriptExprTests.swift index b832120df..fe9bc36cf 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/SubscriptExprTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/SubscriptExprTests.swift @@ -65,14 +65,14 @@ final class SubscriptExprTests: PrettyPrintTestCase { func testSubscriptSettersWithTrailingClosures() { let input = - """ + """ myCollection[index] { $0 < $1 } = someValue myCollection[label: index] { arg1, arg2 in foo() } = someValue myCollection[index, default: someDefaultValue] { arg1, arg2 in foo() } = someValue """ let expected = - """ + """ myCollection[index] { $0 < $1 } = someValue myCollection[label: index] { arg1, arg2 in foo() diff --git a/Tests/SwiftFormatTests/PrettyPrint/SwitchStmtTests.swift b/Tests/SwiftFormatTests/PrettyPrint/SwitchStmtTests.swift index 6928defd5..4c02f4a3f 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/SwitchStmtTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/SwitchStmtTests.swift @@ -277,7 +277,6 @@ final class SwitchStmtTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 35) } - func testSwitchExpression2() { let input = """ @@ -555,6 +554,10 @@ final class SwitchStmtTests: PrettyPrintTestCase { var configuration = Configuration.forTesting configuration.indentSwitchCaseLabels = true assertPrettyPrintEqual( - input: input, expected: expected, linelength: 40, configuration: configuration) + input: input, + expected: expected, + linelength: 40, + configuration: configuration + ) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift index 0000be6ac..7fa561816 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceLintTests.swift @@ -35,7 +35,7 @@ final class WhitespaceLintTests: WhitespaceTestCase { """, findings: [ - FindingSpec("1️⃣", message: "use spaces for spacing"), + FindingSpec("1️⃣", message: "use spaces for spacing") ] ) } diff --git a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift index 78c49752e..4a707e779 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/WhitespaceTestCase.swift @@ -1,9 +1,8 @@ import SwiftFormat -import SwiftSyntax +@_spi(Testing) import SwiftFormat import SwiftParser +import SwiftSyntax import XCTest - -@_spi(Testing) import SwiftFormat @_spi(Testing) import _SwiftFormatTestSupport class WhitespaceTestCase: DiagnosingTestCase { @@ -40,9 +39,13 @@ class WhitespaceTestCase: DiagnosingTestCase { sourceFileSyntax: sourceFileSyntax, configuration: configuration, selection: .infinite, - findingConsumer: { emittedFindings.append($0) }) + findingConsumer: { emittedFindings.append($0) } + ) let linter = WhitespaceLinter( - user: markedText.textWithoutMarkers, formatted: expected, context: context) + user: markedText.textWithoutMarkers, + formatted: expected, + context: context + ) linter.lint() assertFindings( @@ -51,6 +54,7 @@ class WhitespaceTestCase: DiagnosingTestCase { emittedFindings: emittedFindings, context: context, file: file, - line: line) + line: line + ) } } diff --git a/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift b/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift index 81965c38c..b3fabac22 100644 --- a/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift +++ b/Tests/SwiftFormatTests/Rules/AllPublicDeclarationsHaveDocumentationTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCase { func testPublicDeclsWithoutDocs() { @@ -21,22 +20,22 @@ final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCas /// Comment. public struct Foo {} struct Foo {} - + 4️⃣public actor Bar {} /// Comment. public actor Bar {} actor Bar {} - + 5️⃣public class Baz {} /// Comment. public class Baz {} class Baz {} - + 6️⃣public enum Qux {} /// Comment. public enum Qux {} enum Qux {} - + 7️⃣public typealias MyType = Int /// Comment. public typealias MyType = Int @@ -56,7 +55,7 @@ final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCas FindingSpec("4️⃣", message: "add a documentation comment for 'Bar'"), FindingSpec("5️⃣", message: "add a documentation comment for 'Baz'"), FindingSpec("6️⃣", message: "add a documentation comment for 'Qux'"), - FindingSpec("7️⃣", message: "add a documentation comment for 'MyType'") + FindingSpec("7️⃣", message: "add a documentation comment for 'MyType'"), ] ) } @@ -111,7 +110,7 @@ final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCas FindingSpec("4️⃣", message: "add a documentation comment for 'Bar'"), FindingSpec("5️⃣", message: "add a documentation comment for 'Baz'"), FindingSpec("6️⃣", message: "add a documentation comment for 'Qux'"), - FindingSpec("7️⃣", message: "add a documentation comment for 'MyType'") + FindingSpec("7️⃣", message: "add a documentation comment for 'MyType'"), ] ) } @@ -129,7 +128,7 @@ final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCas } """, findings: [ - FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'"), + FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'") ] ) } @@ -147,7 +146,7 @@ final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCas } """, findings: [ - FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'"), + FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'") ] ) } @@ -165,7 +164,7 @@ final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCas } """, findings: [ - FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'"), + FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'") ] ) } @@ -183,7 +182,7 @@ final class AllPublicDeclarationsHaveDocumentationTests: LintOrFormatRuleTestCas } """, findings: [ - FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'"), + FindingSpec("1️⃣", message: "add a documentation comment for 'MyType'") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift index c6b6267ce..acb3535da 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCase { func testArray() { diff --git a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift index 1dbca2191..5d8ffe3db 100644 --- a/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift +++ b/Tests/SwiftFormatTests/Rules/AlwaysUseLowerCamelCaseTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class AlwaysUseLowerCamelCaseTests: LintOrFormatRuleTestCase { func testInvalidVariableCasing() { @@ -57,7 +56,7 @@ final class AlwaysUseLowerCamelCaseTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "rename the enum case 'UpperCamelCase' using lowerCamelCase"), + FindingSpec("1️⃣", message: "rename the enum case 'UpperCamelCase' using lowerCamelCase") ] ) @@ -229,7 +228,10 @@ final class AlwaysUseLowerCamelCaseTests: LintOrFormatRuleTestCase { findings: [ FindingSpec("1️⃣", message: "rename the function 'function_Without_Test_Attribute' using lowerCamelCase"), FindingSpec("2️⃣", message: "rename the function 'function_With_Non_Test_Attribute' using lowerCamelCase"), - FindingSpec("3️⃣", message: "rename the function 'function_With_Test_Attribute_From_Foo_Module' using lowerCamelCase"), + FindingSpec( + "3️⃣", + message: "rename the function 'function_With_Test_Attribute_From_Foo_Module' using lowerCamelCase" + ), ] ) } diff --git a/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift b/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift index b50bb8411..185fe92f4 100644 --- a/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift +++ b/Tests/SwiftFormatTests/Rules/AmbiguousTrailingClosureOverloadTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class AmbiguousTrailingClosureOverloadTests: LintOrFormatRuleTestCase { func testAmbiguousOverloads() { @@ -29,29 +28,33 @@ final class AmbiguousTrailingClosureOverloadTests: LintOrFormatRuleTestCase { """, findings: [ FindingSpec( - "1️⃣", message: "rename 'strong(mad:)' so it is no longer ambiguous when called with a trailing closure", + "1️⃣", + message: "rename 'strong(mad:)' so it is no longer ambiguous when called with a trailing closure", notes: [ NoteSpec("2️⃣", message: "ambiguous overload 'strong(bad:)' is here"), NoteSpec("3️⃣", message: "ambiguous overload 'strong(sad:)' is here"), ] ), FindingSpec( - "4️⃣", message: "rename 'the(cheat:)' so it is no longer ambiguous when called with a trailing closure", + "4️⃣", + message: "rename 'the(cheat:)' so it is no longer ambiguous when called with a trailing closure", notes: [ - NoteSpec("5️⃣", message: "ambiguous overload 'the(sneak:)' is here"), + NoteSpec("5️⃣", message: "ambiguous overload 'the(sneak:)' is here") ] ), FindingSpec( - "6️⃣", message: "rename 'the(kingOfTown:)' so it is no longer ambiguous when called with a trailing closure", + "6️⃣", + message: "rename 'the(kingOfTown:)' so it is no longer ambiguous when called with a trailing closure", notes: [ NoteSpec("7️⃣", message: "ambiguous overload 'the(cheatCommandos:)' is here"), NoteSpec("8️⃣", message: "ambiguous overload 'the(brothersStrong:)' is here"), ] ), FindingSpec( - "9️⃣", message: "rename 'hom(estar:)' so it is no longer ambiguous when called with a trailing closure", + "9️⃣", + message: "rename 'hom(estar:)' so it is no longer ambiguous when called with a trailing closure", notes: [ - NoteSpec("🔟", message: "ambiguous overload 'hom(sar:)' is here"), + NoteSpec("🔟", message: "ambiguous overload 'hom(sar:)' is here") ] ), ] diff --git a/Tests/SwiftFormatTests/Rules/AvoidRetroactiveConformancesTests.swift b/Tests/SwiftFormatTests/Rules/AvoidRetroactiveConformancesTests.swift index 2616d2581..4e6d45239 100644 --- a/Tests/SwiftFormatTests/Rules/AvoidRetroactiveConformancesTests.swift +++ b/Tests/SwiftFormatTests/Rules/AvoidRetroactiveConformancesTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class AvoidRetroactiveConformancesTests: LintOrFormatRuleTestCase { func testRetroactiveConformanceIsDiagnosed() { @@ -10,7 +9,7 @@ final class AvoidRetroactiveConformancesTests: LintOrFormatRuleTestCase { extension Int: 1️⃣@retroactive Identifiable {} """, findings: [ - FindingSpec("1️⃣", message: "do not declare retroactive conformances"), + FindingSpec("1️⃣", message: "do not declare retroactive conformances") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift index 6c96f6d8a..a297a1b3a 100644 --- a/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift +++ b/Tests/SwiftFormatTests/Rules/BeginDocumentationCommentWithOneLineSummaryTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport // FIXME: We should place the diagnostic somewhere in the comment, not on the declaration. final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTestCase { @@ -45,7 +44,10 @@ final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTe findings: [ FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), FindingSpec("2️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), - FindingSpec("3️⃣", message: #"terminate this sentence with a period: "Should fail because it doesn't have a period""#), + FindingSpec( + "3️⃣", + message: #"terminate this sentence with a period: "Should fail because it doesn't have a period""# + ), ] ) } @@ -86,83 +88,98 @@ final class BeginDocumentationCommentWithOneLineSummaryTests: LintOrFormatRuleTe 4️⃣public class testNoPeriod {} """, findings: [ - FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This block comment should not succeed, struct.""#), - FindingSpec("2️⃣", message: #"add a blank comment line after this sentence: "This block comment should not succeed, class.""#), - FindingSpec("3️⃣", message: #"add a blank comment line after this sentence: "This block comment should not succeed, enum.""#), - FindingSpec("4️⃣", message: #"terminate this sentence with a period: "Should fail because it doesn't have a period""#), + FindingSpec( + "1️⃣", + message: #"add a blank comment line after this sentence: "This block comment should not succeed, struct.""# + ), + FindingSpec( + "2️⃣", + message: #"add a blank comment line after this sentence: "This block comment should not succeed, class.""# + ), + FindingSpec( + "3️⃣", + message: #"add a blank comment line after this sentence: "This block comment should not succeed, enum.""# + ), + FindingSpec( + "4️⃣", + message: #"terminate this sentence with a period: "Should fail because it doesn't have a period""# + ), ] ) } func testApproximationsOnMacOS() { #if os(macOS) - // Let macOS also verify that the fallback mode works, which gives us signal about whether it - // will also succeed on Linux (where the linguistic APIs are not currently available). - BeginDocumentationCommentWithOneLineSummary._forcesFallbackModeForTesting = true - - assertLint( - BeginDocumentationCommentWithOneLineSummary.self, - """ - /// Returns a bottle of Dr Pepper from the vending machine. - public func drPepper(from vendingMachine: VendingMachine) -> Soda {} - - /// Contains a comment as description that needs a sentence - /// of two lines of code. - public var twoLinesForOneSentence = "test" - - /// The background color of the view. - var backgroundColor: UIColor + // Let macOS also verify that the fallback mode works, which gives us signal about whether it + // will also succeed on Linux (where the linguistic APIs are not currently available). + BeginDocumentationCommentWithOneLineSummary._forcesFallbackModeForTesting = true - /// Returns the sum of the numbers. - /// - /// - Parameter numbers: The numbers to sum. - /// - Returns: The sum of the numbers. - func sum(_ numbers: [Int]) -> Int { - // ... - } + assertLint( + BeginDocumentationCommentWithOneLineSummary.self, + """ + /// Returns a bottle of Dr Pepper from the vending machine. + public func drPepper(from vendingMachine: VendingMachine) -> Soda {} + + /// Contains a comment as description that needs a sentence + /// of two lines of code. + public var twoLinesForOneSentence = "test" + + /// The background color of the view. + var backgroundColor: UIColor + + /// Returns the sum of the numbers. + /// + /// - Parameter numbers: The numbers to sum. + /// - Returns: The sum of the numbers. + func sum(_ numbers: [Int]) -> Int { + // ... + } - /// This docline should not succeed. - /// There are two sentences without a blank line between them. - 1️⃣struct Test {} + /// This docline should not succeed. + /// There are two sentences without a blank line between them. + 1️⃣struct Test {} - /// This docline should not succeed. There are two sentences. - 2️⃣public enum Token { case comma, semicolon, identifier } + /// This docline should not succeed. There are two sentences. + 2️⃣public enum Token { case comma, semicolon, identifier } - /// Should fail because it doesn't have a period - 3️⃣public class testNoPeriod {} - """, - findings: [ - FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), - FindingSpec("2️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), - FindingSpec("3️⃣", message: #"terminate this sentence with a period: "Should fail because it doesn't have a period""#), - ] - ) + /// Should fail because it doesn't have a period + 3️⃣public class testNoPeriod {} + """, + findings: [ + FindingSpec("1️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), + FindingSpec("2️⃣", message: #"add a blank comment line after this sentence: "This docline should not succeed.""#), + FindingSpec( + "3️⃣", + message: #"terminate this sentence with a period: "Should fail because it doesn't have a period""# + ), + ] + ) #endif } - + func testSentenceTerminationInsideQuotes() { assertLint( BeginDocumentationCommentWithOneLineSummary.self, """ /// Creates an instance with the same raw value as `x` failing iff `x.kind != Subject.kind`. struct TestBackTick {} - + /// A set of `Diagnostic` that can answer the question ‘was there an error?’ in O(1). struct TestSingleSmartQuotes {} - + /// A set of `Diagnostic` that can answer the question 'was there an error?' in O(1). struct TestSingleStraightQuotes {} - + /// A set of `Diagnostic` that can answer the question “was there an error?” in O(1). struct TestDoubleSmartQuotes {} - + /// A set of `Diagnostic` that can answer the question "was there an error?" in O(1). struct TestDoubleStraightQuotes {} - + /// A set of `Diagnostic` that can answer the question “was there /// an error?” in O(1). struct TestTwoLinesDoubleSmartQuotes {} - + /// A set of `Diagnostic` that can answer the question "was there /// an error?" in O(1). struct TestTwoLinesDoubleStraightQuotes {} diff --git a/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift b/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift index 55a174d3d..3804701ae 100644 --- a/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift +++ b/Tests/SwiftFormatTests/Rules/DoNotUseSemicolonsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { func testSemicolonUse() { @@ -120,7 +119,7 @@ final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { ] ) } - + func testBlockCommentAtEndOfBlock() { assertFormatting( DoNotUseSemicolons.self, @@ -131,7 +130,7 @@ final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { print("hello") /* block comment */ """, findings: [ - FindingSpec("1️⃣", message: "remove ';'"), + FindingSpec("1️⃣", message: "remove ';'") ] ) @@ -148,7 +147,7 @@ final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove ';'"), + FindingSpec("1️⃣", message: "remove ';'") ] ) } @@ -164,7 +163,7 @@ final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { /* block comment */ print("world") """, findings: [ - FindingSpec("1️⃣", message: "remove ';' and move the next statement to a new line"), + FindingSpec("1️⃣", message: "remove ';' and move the next statement to a new line") ] ) @@ -181,7 +180,7 @@ final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove ';'"), + FindingSpec("1️⃣", message: "remove ';'") ] ) } @@ -222,7 +221,7 @@ final class DoNotUseSemicolonsTests: LintOrFormatRuleTestCase { for _ in 0..<10 { g() } """, findings: [ - FindingSpec("1️⃣", message: "remove ';'"), + FindingSpec("1️⃣", message: "remove ';'") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift index 801a89a8c..2eb12dd82 100644 --- a/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift +++ b/Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase { func testRepetitiveProperties() { @@ -71,24 +70,23 @@ final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove the suffix 'Thing' from the name of the variable 'defaultThing'"), + FindingSpec("1️⃣", message: "remove the suffix 'Thing' from the name of the variable 'defaultThing'") ] ) } - func testIgnoreSingleDecl() { assertLint( DontRepeatTypeInStaticProperties.self, - """ - struct Foo { - // swift-format-ignore: DontRepeatTypeInStaticProperties - static let defaultFoo: Int - static let 1️⃣alternateFoo: Int - } - """, + """ + struct Foo { + // swift-format-ignore: DontRepeatTypeInStaticProperties + static let defaultFoo: Int + static let 1️⃣alternateFoo: Int + } + """, findings: [ - FindingSpec("1️⃣", message: "remove the suffix 'Foo' from the name of the variable 'alternateFoo'"), + FindingSpec("1️⃣", message: "remove the suffix 'Foo' from the name of the variable 'alternateFoo'") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift b/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift index e41130251..88e425ac6 100644 --- a/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift +++ b/Tests/SwiftFormatTests/Rules/FileScopedDeclarationPrivacyTests.swift @@ -1,9 +1,8 @@ import SwiftFormat +@_spi(Rules) import SwiftFormat import SwiftSyntax import _SwiftFormatTestSupport -@_spi(Rules) import SwiftFormat - private typealias TestConfiguration = ( original: String, desired: FileScopedDeclarationPrivacyConfiguration.AccessLevel, @@ -154,7 +153,7 @@ final class FileScopedDeclarationPrivacyTests: LintOrFormatRuleTestCase { testConfigurations: changingTestConfigurations ) { original, expected in [ - FindingSpec("1️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations"), + FindingSpec("1️⃣", message: "replace '\(original)' with '\(expected)' on file-scoped declarations") ] } } @@ -175,7 +174,9 @@ final class FileScopedDeclarationPrivacyTests: LintOrFormatRuleTestCase { let markedSource = MarkedText(textWithMarkers: source) let substitutedExpected = markedSource.textWithoutMarkers.replacingOccurrences( - of: "$access$", with: testConfig.expected) + of: "$access$", + with: testConfig.expected + ) // Only use the findings if the output was expected to change. If it didn't change, then the // rule wouldn't have emitted anything. @@ -193,7 +194,8 @@ final class FileScopedDeclarationPrivacyTests: LintOrFormatRuleTestCase { findings: findingSpecs, configuration: configuration, file: file, - line: line) + line: line + ) } } } diff --git a/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift b/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift index a1eff743d..8b9a7d985 100644 --- a/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift +++ b/Tests/SwiftFormatTests/Rules/FullyIndirectEnumTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport class FullyIndirectEnumTests: LintOrFormatRuleTestCase { func testAllIndirectCases() { @@ -35,7 +34,7 @@ class FullyIndirectEnumTests: LintOrFormatRuleTestCase { NoteSpec("3️⃣", message: "remove 'indirect' here"), NoteSpec("4️⃣", message: "remove 'indirect' here"), ] - ), + ) ] ) } @@ -72,7 +71,7 @@ class FullyIndirectEnumTests: LintOrFormatRuleTestCase { NoteSpec("3️⃣", message: "remove 'indirect' here"), NoteSpec("4️⃣", message: "remove 'indirect' here"), ] - ), + ) ] ) } diff --git a/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift b/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift index 01c32b0ef..abb8c148b 100644 --- a/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift +++ b/Tests/SwiftFormatTests/Rules/GroupNumericLiteralsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class GroupNumericLiteralsTests: LintOrFormatRuleTestCase { func testNumericGrouping() { diff --git a/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift b/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift index 902438c96..6c4545dba 100644 --- a/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift +++ b/Tests/SwiftFormatTests/Rules/IdentifiersMustBeASCIITests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class IdentifiersMustBeASCIITests: LintOrFormatRuleTestCase { func testInvalidIdentifiers() { diff --git a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift index 7e28f0958..f4d8cb26f 100644 --- a/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift +++ b/Tests/SwiftFormatTests/Rules/ImportsXCTestVisitorTests.swift @@ -1,47 +1,54 @@ import SwiftFormat +@_spi(Rules) @_spi(Testing) import SwiftFormat import SwiftParser import XCTest -@_spi(Rules) @_spi(Testing) import SwiftFormat - class ImportsXCTestVisitorTests: XCTestCase { func testDoesNotImportXCTest() throws { XCTAssertEqual( - try makeContextAndSetImportsXCTest(source: """ - import Foundation - """), + try makeContextAndSetImportsXCTest( + source: """ + import Foundation + """ + ), .doesNotImportXCTest ) } func testImportsXCTest() throws { XCTAssertEqual( - try makeContextAndSetImportsXCTest(source: """ - import Foundation - import XCTest - """), + try makeContextAndSetImportsXCTest( + source: """ + import Foundation + import XCTest + """ + ), .importsXCTest ) } func testImportsSpecificXCTestDecl() throws { XCTAssertEqual( - try makeContextAndSetImportsXCTest(source: """ - import Foundation - import class XCTest.XCTestCase - """), + try makeContextAndSetImportsXCTest( + source: """ + import Foundation + import class XCTest.XCTestCase + """ + ), .importsXCTest ) } func testImportsXCTestInsideConditional() throws { XCTAssertEqual( - try makeContextAndSetImportsXCTest(source: """ - import Foundation - #if SOME_FEATURE_FLAG - import XCTest - #endif - """), + try makeContextAndSetImportsXCTest( + source: """ + import Foundation + #if SOME_FEATURE_FLAG + import XCTest + #endif + """ + ), .importsXCTest ) } @@ -56,7 +63,8 @@ class ImportsXCTestVisitorTests: XCTestCase { findingConsumer: { _ in }, fileURL: URL(fileURLWithPath: "/tmp/test.swift"), sourceFileSyntax: sourceFile, - ruleNameCache: ruleNameCache) + ruleNameCache: ruleNameCache + ) setImportsXCTest(context: context, sourceFile: sourceFile) return context.importsXCTest } diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index 953f1e254..49c26bd1f 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -1,10 +1,9 @@ import SwiftFormat +@_spi(Rules) @_spi(Testing) import SwiftFormat import SwiftOperators import SwiftParser import SwiftSyntax import XCTest - -@_spi(Rules) @_spi(Testing) import SwiftFormat @_spi(Testing) import _SwiftFormatTestSupport class LintOrFormatRuleTestCase: DiagnosingTestCase { @@ -41,20 +40,23 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { sourceFileSyntax: sourceFileSyntax, configuration: configuration, selection: .infinite, - findingConsumer: { emittedFindings.append($0) }) + findingConsumer: { emittedFindings.append($0) } + ) var emittedPipelineFindings = [Finding]() // Disable default rules, so only select rule runs in pipeline configuration.rules = [type.ruleName: true] let pipeline = SwiftLinter( configuration: configuration, - findingConsumer: { emittedPipelineFindings.append($0) }) + findingConsumer: { emittedPipelineFindings.append($0) } + ) pipeline.debugOptions.insert(.disablePrettyPrint) try! pipeline.lint( syntax: sourceFileSyntax, source: unmarkedSource, operatorTable: OperatorTable.standardOperators, - assumingFileURL: URL(string: file.description)!) + assumingFileURL: URL(string: file.description)! + ) // Check that pipeline produces the expected findings assertFindings( @@ -63,7 +65,8 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { emittedFindings: emittedPipelineFindings, context: context, file: file, - line: line) + line: line + ) } /// Asserts that the result of applying a formatter to the provided input code yields the output. @@ -103,7 +106,8 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { sourceFileSyntax: sourceFileSyntax, configuration: configuration, selection: .infinite, - findingConsumer: { emittedFindings.append($0) }) + findingConsumer: { emittedFindings.append($0) } + ) let formatter = formatType.init(context: context) let actual = formatter.visit(sourceFileSyntax) @@ -115,7 +119,8 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { emittedFindings: emittedFindings, context: context, file: file, - line: line) + line: line + ) // Verify that the pretty printer can consume the transformed tree (e.g., it does not contain // any unfolded `SequenceExpr`s). Then do a whitespace-insensitive comparison of the two trees @@ -134,22 +139,36 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { whitespaceInsensitiveText(of: actual), whitespaceInsensitiveText(of: prettyPrintedTree), "After pretty-printing and removing fluid whitespace, the files did not match", - file: file, line: line) + file: file, + line: line + ) var emittedPipelineFindings = [Finding]() // Disable default rules, so only select rule runs in pipeline configuration.rules = [formatType.ruleName: true] let pipeline = SwiftFormatter( - configuration: configuration, findingConsumer: { emittedPipelineFindings.append($0) }) + configuration: configuration, + findingConsumer: { emittedPipelineFindings.append($0) } + ) pipeline.debugOptions.insert(.disablePrettyPrint) var pipelineActual = "" try! pipeline.format( - syntax: sourceFileSyntax, source: originalSource, operatorTable: OperatorTable.standardOperators, - assumingFileURL: nil, selection: .infinite, to: &pipelineActual) + syntax: sourceFileSyntax, + source: originalSource, + operatorTable: OperatorTable.standardOperators, + assumingFileURL: nil, + selection: .infinite, + to: &pipelineActual + ) assertStringsEqualWithDiff(pipelineActual, expected) assertFindings( - expected: findings, markerLocations: markedInput.markers, - emittedFindings: emittedPipelineFindings, context: context, file: file, line: line) + expected: findings, + markerLocations: markedInput.markers, + emittedFindings: emittedPipelineFindings, + context: context, + file: file, + line: line + ) } } diff --git a/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift b/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift index 7ce22b686..9d4d61f4b 100644 --- a/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NeverForceUnwrapTests: LintOrFormatRuleTestCase { func testUnsafeUnwrap() { diff --git a/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift index 0fd51be28..907264222 100644 --- a/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NeverUseForceTryTests: LintOrFormatRuleTestCase { func testInvalidTryExpression() { @@ -38,7 +37,7 @@ final class NeverUseForceTryTests: LintOrFormatRuleTestCase { findings: [] ) } - + func testAllowForceTryInTestAttributeFunction() { assertLint( NeverUseForceTry.self, diff --git a/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift b/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift index c79ee3db0..2de8f4ee3 100644 --- a/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NeverUseImplicitlyUnwrappedOptionalsTests: LintOrFormatRuleTestCase { func testInvalidVariableUnwrapping() { diff --git a/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift b/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift index f020059cb..6735b8861 100644 --- a/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoAccessLevelOnExtensionDeclarationTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { func testExtensionDeclarationAccessLevel() { @@ -55,7 +54,7 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { NoteSpec("9️⃣", message: "add 'public' access modifier to this declaration"), NoteSpec("🔟", message: "add 'public' access modifier to this declaration"), ] - ), + ) ] ) } @@ -76,7 +75,7 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove this redundant 'internal' access modifier from this extension"), + FindingSpec("1️⃣", message: "remove this redundant 'internal' access modifier from this extension") ] ) } @@ -146,9 +145,9 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { "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"), + NoteSpec("2️⃣", message: "add 'package' access modifier to this declaration") ] - ), + ) ] ) } @@ -169,11 +168,12 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { findings: [ FindingSpec( "1️⃣", - message: "remove this 'private' access modifier and declare each member inside this extension as 'fileprivate'", + message: + "remove this 'private' access modifier and declare each member inside this extension as 'fileprivate'", notes: [ - NoteSpec("2️⃣", message: "add 'fileprivate' access modifier to this declaration"), + NoteSpec("2️⃣", message: "add 'fileprivate' access modifier to this declaration") ] - ), + ) ] ) } @@ -192,7 +192,7 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move this 'public' access modifier to precede each member inside this extension"), + FindingSpec("1️⃣", message: "move this 'public' access modifier to precede each member inside this extension") ] ) } @@ -250,7 +250,7 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { NoteSpec("8️⃣", message: "add 'public' access modifier to this declaration"), NoteSpec("9️⃣", message: "add 'public' access modifier to this declaration"), ] - ), + ) ] ) } @@ -340,7 +340,7 @@ final class NoAccessLevelOnExtensionDeclarationTests: LintOrFormatRuleTestCase { NoteSpec("8️⃣", message: "add 'public' access modifier to this declaration"), NoteSpec("9️⃣", message: "add 'public' access modifier to this declaration"), ] - ), + ) ] ) } diff --git a/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift b/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift index 83cb939c3..9ac022653 100644 --- a/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoAssignmentInExpressionsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { func testAssignmentInExpressionContextIsDiagnosed() { @@ -13,7 +12,7 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { foo(bar, baz = quux, a + b) """, findings: [ - FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + FindingSpec("1️⃣", message: "move this assignment expression into its own statement") ] ) } @@ -69,7 +68,7 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + FindingSpec("1️⃣", message: "move this assignment expression into its own statement") ] ) } @@ -89,7 +88,7 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + FindingSpec("1️⃣", message: "move this assignment expression into its own statement") ] ) } @@ -111,7 +110,7 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + FindingSpec("1️⃣", message: "move this assignment expression into its own statement") ] ) } @@ -131,7 +130,7 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + FindingSpec("1️⃣", message: "move this assignment expression into its own statement") ] ) } @@ -151,7 +150,7 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + FindingSpec("1️⃣", message: "move this assignment expression into its own statement") ] ) } @@ -171,7 +170,7 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + FindingSpec("1️⃣", message: "move this assignment expression into its own statement") ] ) } @@ -221,7 +220,7 @@ final class NoAssignmentInExpressionsTests: LintOrFormatRuleTestCase { someRegularFunction(a = b) """, findings: [ - FindingSpec("1️⃣", message: "move this assignment expression into its own statement"), + FindingSpec("1️⃣", message: "move this assignment expression into its own statement") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift b/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift index bc2fe31c7..afd82e297 100644 --- a/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoBlockCommentsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NoBlockCommentsTests: LintOrFormatRuleTestCase { func testDiagnoseBlockComments() { diff --git a/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift b/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift index 873b60797..66e7da94a 100644 --- a/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoCasesWithOnlyFallthroughTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { func testFallthroughCases() { @@ -330,7 +329,7 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'") ] ) } @@ -352,7 +351,7 @@ final class NoCasesWithOnlyFallthroughTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'"), + FindingSpec("1️⃣", message: "combine this fallthrough-only 'case' and the following 'case' into a single 'case'") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift b/Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift index 294a38210..30f9471fc 100644 --- a/Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift @@ -1,106 +1,105 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NoEmptyLinesOpeningClosingBracesTests: LintOrFormatRuleTestCase { func testNoEmptyLinesOpeningClosingBracesInCodeBlock() { assertFormatting( NoEmptyLinesOpeningClosingBraces.self, input: """ - func f() {1️⃣ - - // - return - - - 2️⃣} - """, + func f() {1️⃣ + + // + return + + + 2️⃣} + """, expected: """ - func f() { - // - return - } - """, + func f() { + // + return + } + """, findings: [ FindingSpec("1️⃣", message: "remove empty line after '{'"), FindingSpec("2️⃣", message: "remove empty lines before '}'"), ] ) } - + func testNoEmptyLinesOpeningClosingBracesInMemberBlock() { assertFormatting( NoEmptyLinesOpeningClosingBraces.self, input: """ - struct {1️⃣ - - let x: Int - - let y: Int + struct {1️⃣ - 2️⃣} - """, + let x: Int + + let y: Int + + 2️⃣} + """, expected: """ - struct { - let x: Int - - let y: Int - } - """, + struct { + let x: Int + + let y: Int + } + """, findings: [ FindingSpec("1️⃣", message: "remove empty line after '{'"), FindingSpec("2️⃣", message: "remove empty line before '}'"), ] ) } - + func testNoEmptyLinesOpeningClosingBracesInAccessorBlock() { assertFormatting( NoEmptyLinesOpeningClosingBraces.self, input: """ - var x: Int {1️⃣ - - // - return _x - - 2️⃣} - - var y: Int {3️⃣ - - get 5️⃣{ - - // - return _y + var x: Int {1️⃣ - 6️⃣ } - - set 7️⃣{ - - // - _x = newValue - - 8️⃣ } + // + return _x + + 2️⃣} + + var y: Int {3️⃣ + + get 5️⃣{ - 4️⃣} - """, + // + return _y + + 6️⃣ } + + set 7️⃣{ + + // + _x = newValue + + 8️⃣ } + + 4️⃣} + """, expected: """ - var x: Int { + var x: Int { + // + return _x + } + + var y: Int { + get { // - return _x + return _y } - - var y: Int { - get { - // - return _y - } - - set { - // - _x = newValue - } + + set { + // + _x = newValue } - """, + } + """, findings: [ FindingSpec("1️⃣", message: "remove empty line after '{'"), FindingSpec("2️⃣", message: "remove empty line before '}'"), @@ -118,19 +117,19 @@ final class NoEmptyLinesOpeningClosingBracesTests: LintOrFormatRuleTestCase { assertFormatting( NoEmptyLinesOpeningClosingBraces.self, input: """ - let closure = {1️⃣ - - // - return - - 2️⃣} - """, + let closure = {1️⃣ + + // + return + + 2️⃣} + """, expected: """ - let closure = { - // - return - } - """, + let closure = { + // + return + } + """, findings: [ FindingSpec("1️⃣", message: "remove empty line after '{'"), FindingSpec("2️⃣", message: "remove empty line before '}'"), diff --git a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift index d5b6275a7..0e11de115 100644 --- a/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoEmptyTrailingClosureParenthesesTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NoEmptyTrailingClosureParenthesesTests: LintOrFormatRuleTestCase { func testInvalidEmptyParenTrailingClosure() { diff --git a/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift b/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift index e4cbf1450..ab95fb2ec 100644 --- a/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoLabelsInCasePatternsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NoLabelsInCasePatternsTests: LintOrFormatRuleTestCase { func testRedundantCaseLabels() { diff --git a/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift b/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift index 704680f2f..8339bed9a 100644 --- a/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoLeadingUnderscoresTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NoLeadingUnderscoresTests: LintOrFormatRuleTestCase { func testVars() { @@ -135,7 +134,7 @@ final class NoLeadingUnderscoresTests: LintOrFormatRuleTestCase { infix operator <> : _BazPrecedence """, findings: [ - FindingSpec("1️⃣", message: "remove the leading '_' from the name '_FooPrecedence'"), + FindingSpec("1️⃣", message: "remove the leading '_' from the name '_FooPrecedence'") ] ) } @@ -148,7 +147,7 @@ final class NoLeadingUnderscoresTests: LintOrFormatRuleTestCase { typealias 1️⃣_Bar = Bar """, findings: [ - FindingSpec("1️⃣", message: "remove the leading '_' from the name '_Bar'"), + FindingSpec("1️⃣", message: "remove the leading '_' from the name '_Bar'") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift index 7f87043ed..7925b57cf 100644 --- a/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoParensAroundConditionsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NoParensAroundConditionsTests: LintOrFormatRuleTestCase { func testParensAroundConditions() { diff --git a/Tests/SwiftFormatTests/Rules/NoPlaygroundLiteralsTests.swift b/Tests/SwiftFormatTests/Rules/NoPlaygroundLiteralsTests.swift index 91d659053..3cc205fde 100644 --- a/Tests/SwiftFormatTests/Rules/NoPlaygroundLiteralsTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoPlaygroundLiteralsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NoPlaygroundLiteralsTests: LintOrFormatRuleTestCase { func testColorLiterals() { @@ -40,9 +39,18 @@ final class NoPlaygroundLiteralsTests: LintOrFormatRuleTestCase { _ = #fileLiteral(resourceName: "secrets.json") """, findings: [ - FindingSpec("1️⃣", message: "replace '#fileLiteral' with a call to a method such as 'Bundle.url(forResource:withExtension:)'"), - FindingSpec("2️⃣", message: "replace '#fileLiteral' with a call to a method such as 'Bundle.url(forResource:withExtension:)'"), - FindingSpec("3️⃣", message: "replace '#fileLiteral' with a call to a method such as 'Bundle.url(forResource:withExtension:)'"), + FindingSpec( + "1️⃣", + message: "replace '#fileLiteral' with a call to a method such as 'Bundle.url(forResource:withExtension:)'" + ), + FindingSpec( + "2️⃣", + message: "replace '#fileLiteral' with a call to a method such as 'Bundle.url(forResource:withExtension:)'" + ), + FindingSpec( + "3️⃣", + message: "replace '#fileLiteral' with a call to a method such as 'Bundle.url(forResource:withExtension:)'" + ), ] ) } diff --git a/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift b/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift index 7c0c5a88d..20247a289 100644 --- a/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoVoidReturnOnFunctionSignatureTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class NoVoidReturnOnFunctionSignatureTests: LintOrFormatRuleTestCase { func testVoidReturns() { diff --git a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift index b5c2f9449..ae0d84188 100644 --- a/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OmitReturnsTests.swift @@ -1,119 +1,122 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class OmitReturnsTests: LintOrFormatRuleTestCase { func testOmitReturnInFunction() { assertFormatting( OmitExplicitReturns.self, input: """ - func test() -> Bool { - 1️⃣return false - } - """, + func test() -> Bool { + 1️⃣return false + } + """, expected: """ - func test() -> Bool { - false - } - """, + func test() -> Bool { + false + } + """, findings: [ FindingSpec("1️⃣", message: "'return' can be omitted because body consists of a single expression") - ]) + ] + ) } func testOmitReturnInClosure() { assertFormatting( OmitExplicitReturns.self, input: """ - vals.filter { - 1️⃣return $0.count == 1 - } - """, + vals.filter { + 1️⃣return $0.count == 1 + } + """, expected: """ - vals.filter { - $0.count == 1 - } - """, + vals.filter { + $0.count == 1 + } + """, findings: [ FindingSpec("1️⃣", message: "'return' can be omitted because body consists of a single expression") - ]) + ] + ) } func testOmitReturnInSubscript() { assertFormatting( OmitExplicitReturns.self, input: """ - struct Test { - subscript(x: Int) -> Bool { - 1️⃣return false + struct Test { + subscript(x: Int) -> Bool { + 1️⃣return false + } } - } - struct Test { - subscript(x: Int) -> Bool { - get { - 2️⃣return false + struct Test { + subscript(x: Int) -> Bool { + get { + 2️⃣return false + } + set { } } - set { } } - } - """, + """, expected: """ - struct Test { - subscript(x: Int) -> Bool { - false + struct Test { + subscript(x: Int) -> Bool { + false + } } - } - struct Test { - subscript(x: Int) -> Bool { - get { - false + struct Test { + subscript(x: Int) -> Bool { + get { + false + } + set { } } - set { } } - } - """, + """, findings: [ FindingSpec("1️⃣", message: "'return' can be omitted because body consists of a single expression"), - FindingSpec("2️⃣", message: "'return' can be omitted because body consists of a single expression") - ]) + FindingSpec("2️⃣", message: "'return' can be omitted because body consists of a single expression"), + ] + ) } func testOmitReturnInComputedVars() { assertFormatting( OmitExplicitReturns.self, input: """ - var x: Int { - 1️⃣return 42 - } - - struct Test { var x: Int { - get { - 2️⃣return 42 + 1️⃣return 42 + } + + struct Test { + var x: Int { + get { + 2️⃣return 42 + } + set { } } - set { } } - } - """, + """, expected: """ - var x: Int { - 42 - } - - struct Test { var x: Int { - get { - 42 + 42 + } + + struct Test { + var x: Int { + get { + 42 + } + set { } } - set { } } - } - """, + """, findings: [ FindingSpec("1️⃣", message: "'return' can be omitted because body consists of a single expression"), - FindingSpec("2️⃣", message: "'return' can be omitted because body consists of a single expression") - ]) + FindingSpec("2️⃣", message: "'return' can be omitted because body consists of a single expression"), + ] + ) } } diff --git a/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift b/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift index 623418884..23c809a8d 100644 --- a/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift +++ b/Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class OneCasePerLineTests: LintOrFormatRuleTestCase { @@ -68,7 +67,7 @@ final class OneCasePerLineTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move 'a' to its own 'case' declaration"), + FindingSpec("1️⃣", message: "move 'a' to its own 'case' declaration") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift b/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift index cb951bbd5..3b84e467d 100644 --- a/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift +++ b/Tests/SwiftFormatTests/Rules/OneVariableDeclarationPerLineTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { func testMultipleVariableBindings() { @@ -157,7 +156,7 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { let c: Int """, findings: [ - FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'let'") ] ) } @@ -174,7 +173,7 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { let /* c */ c: Int """, findings: [ - FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'let'"), + FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'let'") ] ) } @@ -220,7 +219,7 @@ final class OneVariableDeclarationPerLineTests: LintOrFormatRuleTestCase { var y = "foo" { didSet { print("changed") } } """, findings: [ - FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'var'"), + FindingSpec("1️⃣", message: "split this variable declaration to introduce only one variable per 'var'") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift b/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift index b36a01afb..d18d03061 100644 --- a/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift +++ b/Tests/SwiftFormatTests/Rules/OnlyOneTrailingClosureArgumentTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class OnlyOneTrailingClosureArgumentTests: LintOrFormatRuleTestCase { func testInvalidTrailingClosureCall() { @@ -16,7 +15,10 @@ final class OnlyOneTrailingClosureArgumentTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "revise this function call to avoid using both closure arguments and a trailing closure"), + FindingSpec( + "1️⃣", + message: "revise this function call to avoid using both closure arguments and a trailing closure" + ) ] ) } diff --git a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift index a61a07539..73d33aa77 100644 --- a/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift +++ b/Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class OrderedImportsTests: LintOrFormatRuleTestCase { func testInvalidImportsOrder() { @@ -55,7 +54,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { ] ) } - + func testImportsOrderWithoutModuleType() { assertFormatting( OrderedImports.self, @@ -84,7 +83,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { ] ) } - + func testImportsOrderWithDocComment() { assertFormatting( OrderedImports.self, @@ -119,11 +118,11 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { let a = 3 """, findings: [ - FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("1️⃣", message: "sort import statements lexicographically") ] ) } - + func testValidOrderedImport() { assertFormatting( OrderedImports.self, @@ -214,7 +213,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { input: input, expected: expected, findings: [ - FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("1️⃣", message: "sort import statements lexicographically") ] ) } @@ -235,7 +234,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { foo();bar();baz();quxxe(); """, findings: [ - FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("1️⃣", message: "sort import statements lexicographically") ] ) } @@ -255,7 +254,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { foo();bar();baz();quxxe(); """, findings: [ - FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("1️⃣", message: "sort import statements lexicographically") ] ) } @@ -390,7 +389,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { zeta """, findings: [ - FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("1️⃣", message: "sort import statements lexicographically") ] ) } @@ -413,7 +412,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { bar() """, findings: [ - FindingSpec("1️⃣", message: "remove this duplicate import"), + FindingSpec("1️⃣", message: "remove this duplicate import") ] ) } @@ -614,7 +613,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { baz() """, findings: [ - FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("1️⃣", message: "sort import statements lexicographically") ] ) } @@ -648,7 +647,7 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase { bar() // calls the bar """, findings: [ - FindingSpec("1️⃣", message: "sort import statements lexicographically"), + FindingSpec("1️⃣", message: "sort import statements lexicographically") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/ReplaceForEachWithForLoopTests.swift b/Tests/SwiftFormatTests/Rules/ReplaceForEachWithForLoopTests.swift index 295f3bfe1..6f225b249 100644 --- a/Tests/SwiftFormatTests/Rules/ReplaceForEachWithForLoopTests.swift +++ b/Tests/SwiftFormatTests/Rules/ReplaceForEachWithForLoopTests.swift @@ -1,7 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat - +import _SwiftFormatTestSupport final class ReplaceForEachWithForLoopTests: LintOrFormatRuleTestCase { func test() { @@ -27,7 +25,7 @@ final class ReplaceForEachWithForLoopTests: LintOrFormatRuleTestCase { findings: [ FindingSpec("1️⃣", message: "replace use of '.forEach { ... }' with for-in loop"), FindingSpec("2️⃣", message: "replace use of '.forEach { ... }' with for-in loop"), - FindingSpec("3️⃣", message: "replace use of '.forEach { ... }' with for-in loop") + FindingSpec("3️⃣", message: "replace use of '.forEach { ... }' with for-in loop"), ] ) } diff --git a/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift b/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift index 67a1837e1..17e76607f 100644 --- a/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift +++ b/Tests/SwiftFormatTests/Rules/ReturnVoidInsteadOfEmptyTupleTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class ReturnVoidInsteadOfEmptyTupleTests: LintOrFormatRuleTestCase { func testBasic() { diff --git a/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift b/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift index f44ea743d..fa30409e4 100644 --- a/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift +++ b/Tests/SwiftFormatTests/Rules/TypeNamesShouldBeCapitalizedTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { func testConstruction() { @@ -112,7 +111,10 @@ final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase { FindingSpec("2️⃣", message: "rename the associated type '_value' using UpperCamelCase; for example, '_Value'"), FindingSpec("3️⃣", message: "rename the struct '_data' using UpperCamelCase; for example, '_Data'"), FindingSpec("4️⃣", message: "rename the type alias '_x' using UpperCamelCase; for example, '_X'"), - FindingSpec("5️⃣", message: "rename the actor '_internalActor' using UpperCamelCase; for example, '_InternalActor'"), + FindingSpec( + "5️⃣", + message: "rename the actor '_internalActor' using UpperCamelCase; for example, '_InternalActor'" + ), FindingSpec("6️⃣", message: "rename the enum '__e' using UpperCamelCase; for example, '__E'"), FindingSpec("7️⃣", message: "rename the class '_myClass' using UpperCamelCase; for example, '_MyClass'"), FindingSpec("8️⃣", message: "rename the actor '__greeter' using UpperCamelCase; for example, '__Greeter'"), diff --git a/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift b/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift index f9d54d2fc..b488bdcd1 100644 --- a/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseEarlyExitsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class UseEarlyExitsTests: LintOrFormatRuleTestCase { func testBasicIfElse() { @@ -25,7 +24,7 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { trueBlock() """, findings: [ - FindingSpec("1️⃣", message: "replace this 'if/else' block with a 'guard' statement containing the early exit"), + FindingSpec("1️⃣", message: "replace this 'if/else' block with a 'guard' statement containing the early exit") ] ) } @@ -51,7 +50,7 @@ final class UseEarlyExitsTests: LintOrFormatRuleTestCase { return """, findings: [ - FindingSpec("1️⃣", message: "replace this 'if/else' block with a 'guard' statement containing the early exit"), + FindingSpec("1️⃣", message: "replace this 'if/else' block with a 'guard' statement containing the early exit") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift b/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift index d15c6acfa..062de9927 100644 --- a/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseExplicitNilCheckInConditionsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class UseExplicitNilCheckInConditionsTests: LintOrFormatRuleTestCase { func testIfExpressions() { @@ -124,7 +123,7 @@ final class UseExplicitNilCheckInConditionsTests: LintOrFormatRuleTestCase { if (x ? y : z) != nil {} """, findings: [ - FindingSpec("1️⃣", message: "compare this value using `!= nil` instead of binding and discarding it"), + FindingSpec("1️⃣", message: "compare this value using `!= nil` instead of binding and discarding it") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift b/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift index 3748173e7..bf6912635 100644 --- a/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseLetInEveryBoundCaseVariableTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class UseLetInEveryBoundCaseVariableTests: LintOrFormatRuleTestCase { func testSwitchCase() { @@ -19,11 +18,26 @@ final class UseLetInEveryBoundCaseVariableTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("2️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("3️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("4️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("5️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec( + "1️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "2️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "3️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "4️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "5️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), ] ) } @@ -42,11 +56,26 @@ final class UseLetInEveryBoundCaseVariableTests: LintOrFormatRuleTestCase { if case let x as SomeType = someValue {} """, findings: [ - FindingSpec("1️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("2️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("3️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("4️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("5️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec( + "1️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "2️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "3️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "4️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "5️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), ] ) } @@ -65,11 +94,26 @@ final class UseLetInEveryBoundCaseVariableTests: LintOrFormatRuleTestCase { guard case let x as SomeType = someValue else {} """, findings: [ - FindingSpec("1️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("2️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("3️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("4️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("5️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec( + "1️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "2️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "3️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "4️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "5️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), ] ) } @@ -88,11 +132,26 @@ final class UseLetInEveryBoundCaseVariableTests: LintOrFormatRuleTestCase { for case let x as SomeType in {} """, findings: [ - FindingSpec("1️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("2️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("3️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("4️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("5️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec( + "1️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "2️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "3️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "4️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "5️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), ] ) } @@ -111,11 +170,26 @@ final class UseLetInEveryBoundCaseVariableTests: LintOrFormatRuleTestCase { while case let x as SomeType = iter.next() {} """, findings: [ - FindingSpec("1️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("2️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("3️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("4️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), - FindingSpec("5️⃣", message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables"), + FindingSpec( + "1️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "2️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "3️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "4️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), + FindingSpec( + "5️⃣", + message: "move this 'let' keyword inside the 'case' pattern, before each of the bound variables" + ), ] ) } diff --git a/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift b/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift index ce1f3d797..188c7e4f8 100644 --- a/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseShorthandTypeNamesTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { func testNamesInTypeContextsAreShortened() { @@ -560,7 +559,7 @@ final class UseShorthandTypeNamesTests: LintOrFormatRuleTestCase { let c: Int? """, findings: [ - FindingSpec("1️⃣", message: "use shorthand syntax for this 'Optional' type"), + FindingSpec("1️⃣", message: "use shorthand syntax for this 'Optional' type") ] ) } diff --git a/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift b/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift index c5086c0d6..0d66e4654 100644 --- a/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class UseSingleLinePropertyGetterTests: LintOrFormatRuleTestCase { func testMultiLinePropertyGetter() { @@ -65,7 +64,10 @@ final class UseSingleLinePropertyGetterTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove 'get {...}' around the accessor and move its body directly into the computed property"), + FindingSpec( + "1️⃣", + message: "remove 'get {...}' around the accessor and move its body directly into the computed property" + ) ] ) } diff --git a/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift b/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift index a21c99ba8..858393acd 100644 --- a/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseSynthesizedInitializerTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { func testMemberwiseInitializerIsDiagnosed() { @@ -21,7 +20,10 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + FindingSpec( + "1️⃣", + message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer" + ) ] ) } @@ -41,7 +43,10 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + FindingSpec( + "1️⃣", + message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer" + ) ] ) } @@ -64,7 +69,10 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + FindingSpec( + "1️⃣", + message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer" + ) ] ) } @@ -87,7 +95,10 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + FindingSpec( + "1️⃣", + message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer" + ) ] ) } @@ -353,7 +364,10 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + FindingSpec( + "1️⃣", + message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer" + ) ] ) } @@ -376,7 +390,10 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + FindingSpec( + "1️⃣", + message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer" + ) ] ) } @@ -428,9 +445,18 @@ final class UseSynthesizedInitializerTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), - FindingSpec("2️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), - FindingSpec("3️⃣", message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer"), + FindingSpec( + "1️⃣", + message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer" + ), + FindingSpec( + "2️⃣", + message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer" + ), + FindingSpec( + "3️⃣", + message: "remove this explicit initializer, which is identical to the compiler-synthesized initializer" + ), ] ) } diff --git a/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift index d5c460c72..1691e3c0b 100644 --- a/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseTripleSlashForDocumentationCommentsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCase { func testRemoveDocBlockComments() { @@ -37,7 +36,7 @@ final class UseTripleSlashForDocumentationCommentsTests: LintOrFormatRuleTestCas ] ) } - + func testRemoveDocBlockCommentsWithoutStars() { assertFormatting( UseTripleSlashForDocumentationComments.self, diff --git a/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift b/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift index c4660673d..1110e63ca 100644 --- a/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseWhereClausesInForLoopsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport final class UseWhereClausesInForLoopsTests: LintOrFormatRuleTestCase { func testForLoopWhereClauses() { diff --git a/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift index 2b1fcbeb2..38e41c1d3 100644 --- a/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift @@ -1,6 +1,5 @@ -import _SwiftFormatTestSupport - @_spi(Rules) import SwiftFormat +import _SwiftFormatTestSupport // FIXME: Diagnostics should be emitted inside the comment, not at the beginning of the declaration. final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { @@ -26,8 +25,15 @@ final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { 2️⃣func testInvalidParameterDesc(command: String, stdin: String) -> String {} """, findings: [ - FindingSpec("1️⃣", message: "replace the plural 'Parameters:' section with a singular inline 'Parameter' section"), - FindingSpec("2️⃣", message: "replace the singular inline 'Parameter' section with a plural 'Parameters:' section that has the parameters nested inside it"), + FindingSpec( + "1️⃣", + message: "replace the plural 'Parameters:' section with a singular inline 'Parameter' section" + ), + FindingSpec( + "2️⃣", + message: + "replace the singular inline 'Parameter' section with a plural 'Parameters:' section that has the parameters nested inside it" + ), ] ) } @@ -216,7 +222,10 @@ final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { } """, findings: [ - FindingSpec("1️⃣", message: "change the parameters of the documentation of 'incorrectParam' to match its parameters"), + FindingSpec( + "1️⃣", + message: "change the parameters of the documentation of 'incorrectParam' to match its parameters" + ) ] ) } diff --git a/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift index 2f556e629..965c90fe3 100644 --- a/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift +++ b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift @@ -1,6 +1,5 @@ -import XCTest - @_spi(Internal) import SwiftFormat +import XCTest final class FileIteratorTests: XCTestCase { private var tmpdir: URL! @@ -10,7 +9,8 @@ final class FileIteratorTests: XCTestCase { for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: FileManager.default.temporaryDirectory, - create: true) + create: true + ) // Create a simple file tree used by the tests below. try touch("project/real1.swift") @@ -43,7 +43,8 @@ final class FileIteratorTests: XCTestCase { func testTraversesHiddenFilesIfExplicitlySpecified() { let seen = allFilesSeen( iteratingOver: [tmpURL("project/.build"), tmpURL("project/.hidden.swift")], - followSymlinks: false) + followSymlinks: false + ) XCTAssertEqual(seen.count, 2) XCTAssertTrue(seen.contains { $0.hasSuffix("project/.build/generated.swift") }) XCTAssertTrue(seen.contains { $0.hasSuffix("project/.hidden.swift") }) @@ -69,7 +70,9 @@ extension FileIteratorTests { private func touch(_ path: String) throws { let fileURL = tmpURL(path) try FileManager.default.createDirectory( - at: fileURL.deletingLastPathComponent(), withIntermediateDirectories: true) + at: fileURL.deletingLastPathComponent(), + withIntermediateDirectories: true + ) struct FailedToCreateFileError: Error { let url: URL } @@ -81,7 +84,9 @@ extension FileIteratorTests { /// Create a symlink between files or directories in the test's temporary space. private func symlink(_ source: String, to target: String) throws { try FileManager.default.createSymbolicLink( - at: tmpURL(source), withDestinationURL: tmpURL(target)) + at: tmpURL(source), + withDestinationURL: tmpURL(target) + ) } /// Computes the list of all files seen by using `FileIterator` to iterate over the given URLs. From 40d3f3e9c106335eecf1904be2a59f17dba54a73 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Tue, 1 Oct 2024 08:29:04 -0400 Subject: [PATCH 260/332] Re-enable format check (the default behavior). --- .github/workflows/pull_request.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b95dcd4fa..d49113a87 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -12,6 +12,5 @@ jobs: name: Soundness uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main with: - format_check_enabled: false license_header_check_enabled: false license_header_check_project_name: "Swift.org" From 65bac1eaac8e12bddb77e27bb336d0b4e47fd87e Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 3 Oct 2024 17:00:59 -0700 Subject: [PATCH 261/332] Make `build-script-helper.py` pass flake8 linting In preparation for https://github.com/swiftlang/github-workflows/pull/14 --- .flake8 | 4 ++++ build-script-helper.py | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..4f16d6847 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] + +# Match the maximum line length in Swift files. +max-line-length = 120 diff --git a/build-script-helper.py b/build-script-helper.py index abde94bb2..6842ae4e8 100755 --- a/build-script-helper.py +++ b/build-script-helper.py @@ -15,12 +15,12 @@ from __future__ import print_function import argparse -import sys -import os, platform +import json +import os import subprocess +import sys from pathlib import Path -from typing import List, Union, Optional -import json +from typing import List, Optional, Union # ----------------------------------------------------------------------------- # General utilities From ee8342ed26b8981143b735024b4c6c6710017593 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 3 Oct 2024 17:20:40 -0700 Subject: [PATCH 262/332] Update YAML files to pass yamllint In preparation for https://github.com/swiftlang/github-workflows/pull/13 --- .github/workflows/publish_release.yml | 85 +++++++++++++-------------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 481093b36..2528ebf6f 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -41,30 +41,30 @@ jobs: swift_syntax_tag: ${{ steps.swift_syntax_tag.outputs.swift_syntax_tag }} swift_format_version: ${{ steps.swift_format_version.outputs.swift_format_version }} steps: - - name: Determine swift-syntax tag to depend on - id: swift_syntax_tag - shell: bash - run: | - if [[ "${{ github.event.inputs.prerelease }}" == "false" ]]; then - SWIFT_SYNTAX_TAG="${{ github.event.inputs.swift_syntax_tag }}" - else - git clone https://github.com/swiftlang/swift-syntax.git - cd swift-syntax - SWIFT_SYNTAX_TAG="$(git tag | grep ${{ github.event.inputs.swift_syntax_tag }}-prerelease | sort -r | head -1)" - fi + - name: Determine swift-syntax tag to depend on + id: swift_syntax_tag + shell: bash + run: | + if [[ "${{ github.event.inputs.prerelease }}" == "false" ]]; then + SWIFT_SYNTAX_TAG="${{ github.event.inputs.swift_syntax_tag }}" + else + git clone https://github.com/swiftlang/swift-syntax.git + cd swift-syntax + SWIFT_SYNTAX_TAG="$(git tag | grep ${{ github.event.inputs.swift_syntax_tag }}-prerelease | sort -r | head -1)" + fi - echo "Using swift-syntax tag: $SWIFT_SYNTAX_TAG" - echo "swift_syntax_tag=$SWIFT_SYNTAX_TAG" >> "$GITHUB_OUTPUT" - - name: Determine swift-format prerelease version - id: swift_format_version - run: | - if [[ "${{ github.event.inputs.prerelease }}" == "false" ]]; then - SWIFT_FORMAT_VERSION="${{ github.event.inputs.swift_format_version }}" - else - SWIFT_FORMAT_VERSION="${{ github.event.inputs.swift_format_version }}-prerelease-$(date +'%Y-%m-%d')" - fi - echo "Using swift-format version: $SWIFT_FORMAT_VERSION" - echo "swift_format_version=$SWIFT_FORMAT_VERSION" >> "$GITHUB_OUTPUT" + echo "Using swift-syntax tag: $SWIFT_SYNTAX_TAG" + echo "swift_syntax_tag=$SWIFT_SYNTAX_TAG" >> "$GITHUB_OUTPUT" + - name: Determine swift-format prerelease version + id: swift_format_version + run: | + if [[ "${{ github.event.inputs.prerelease }}" == "false" ]]; then + SWIFT_FORMAT_VERSION="${{ github.event.inputs.swift_format_version }}" + else + SWIFT_FORMAT_VERSION="${{ github.event.inputs.swift_format_version }}-prerelease-$(date +'%Y-%m-%d')" + fi + echo "Using swift-format version: $SWIFT_FORMAT_VERSION" + echo "swift_format_version=$SWIFT_FORMAT_VERSION" >> "$GITHUB_OUTPUT" test_debug: name: Test in Debug configuration uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main @@ -88,23 +88,22 @@ jobs: permissions: contents: write steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Create release commits - run: bash .github/workflows/create-release-commits.sh '${{ needs.define_tags.outputs.swift_syntax_tag }}' '${{ needs.define_tags.outputs.swift_format_version }}' - - name: Tag release - run: | - git tag "${{ needs.define_tags.outputs.swift_format_version }}" - git push origin "${{ needs.define_tags.outputs.swift_format_version }}" - - name: Create release - env: - GH_TOKEN: ${{ github.token }} - run: | - if [[ "${{ github.event.inputs.prerelease }}" != "true" ]]; then - # Only create a release automatically for prereleases. For real releases, release notes should be crafted by hand. - exit - fi - gh release create "${{ needs.define_tags.outputs.swift_format_version }}" \ - --title "${{ needs.define_tags.outputs.swift_format_version }}" \ - --prerelease - + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create release commits + run: bash .github/workflows/create-release-commits.sh '${{ needs.define_tags.outputs.swift_syntax_tag }}' '${{ needs.define_tags.outputs.swift_format_version }}' + - name: Tag release + run: | + git tag "${{ needs.define_tags.outputs.swift_format_version }}" + git push origin "${{ needs.define_tags.outputs.swift_format_version }}" + - name: Create release + env: + GH_TOKEN: ${{ github.token }} + run: | + if [[ "${{ github.event.inputs.prerelease }}" != "true" ]]; then + # Only create a release automatically for prereleases. For real releases, release notes should be crafted by hand. + exit + fi + gh release create "${{ needs.define_tags.outputs.swift_format_version }}" \ + --title "${{ needs.define_tags.outputs.swift_format_version }}" \ + --prerelease From 1e7278025f2083fd2c2bca3341540e090951633f Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Sun, 6 Oct 2024 00:30:59 +0900 Subject: [PATCH 263/332] Add a relative symlink to FileIteratorTests --- .../Utilities/FileIteratorTests.swift | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift index 965c90fe3..691e568c6 100644 --- a/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift +++ b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift @@ -18,6 +18,7 @@ final class FileIteratorTests: XCTestCase { try touch("project/.hidden.swift") try touch("project/.build/generated.swift") try symlink("project/link.swift", to: "project/.hidden.swift") + try symlink("project/rellink.swift", relativeTo: ".hidden.swift") } override func tearDownWithError() throws { @@ -55,7 +56,10 @@ final class FileIteratorTests: XCTestCase { // passed to the iterator. This is meant to avoid situations where a symlink could be hidden by // shell expansion; for example, if the user writes `swift-format --no-follow-symlinks *`, if // the current directory contains a symlink, they would probably *not* expect it to be followed. - let seen = allFilesSeen(iteratingOver: [tmpURL("project/link.swift")], followSymlinks: false) + let seen = allFilesSeen( + iteratingOver: [tmpURL("project/link.swift"), tmpURL("project/rellink.swift")], + followSymlinks: false + ) XCTAssertTrue(seen.isEmpty) } } @@ -81,7 +85,7 @@ extension FileIteratorTests { } } - /// Create a symlink between files or directories in the test's temporary space. + /// Create a absolute symlink between files or directories in the test's temporary space. private func symlink(_ source: String, to target: String) throws { try FileManager.default.createSymbolicLink( at: tmpURL(source), @@ -89,6 +93,14 @@ extension FileIteratorTests { ) } + /// Create a relative symlink between files or directories in the test's temporary space. + private func symlink(_ source: String, relativeTo target: String) throws { + try FileManager.default.createSymbolicLink( + atPath: tmpURL(source).path, + withDestinationPath: target + ) + } + /// Computes the list of all files seen by using `FileIterator` to iterate over the given URLs. private func allFilesSeen(iteratingOver urls: [URL], followSymlinks: Bool) -> [String] { let iterator = FileIterator(urls: urls, followSymlinks: followSymlinks) From f824817fc015c15e6e3762391bf9d82e5cd1e572 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Sun, 6 Oct 2024 00:32:57 +0900 Subject: [PATCH 264/332] Fix relative symlinks when using `--follow-symlinks` --- Sources/SwiftFormat/Utilities/FileIterator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/Utilities/FileIterator.swift b/Sources/SwiftFormat/Utilities/FileIterator.swift index bd3a2c0f0..b833bc6f4 100644 --- a/Sources/SwiftFormat/Utilities/FileIterator.swift +++ b/Sources/SwiftFormat/Utilities/FileIterator.swift @@ -79,7 +79,7 @@ public struct FileIterator: Sequence, IteratorProtocol { else { break } - next = URL(fileURLWithPath: destination) + next = URL(fileURLWithPath: destination, relativeTo: next) fallthrough case .typeDirectory: @@ -135,7 +135,7 @@ public struct FileIterator: Sequence, IteratorProtocol { else { break } - path = destination + path = URL(fileURLWithPath: destination, isDirectory: false, relativeTo: item).path fallthrough case .typeRegular: From 8e547318f8ec30a0c6e9211b977a485118b7368a Mon Sep 17 00:00:00 2001 From: Tim Condon <0xTim@users.noreply.github.com> Date: Sun, 6 Oct 2024 00:14:45 +0100 Subject: [PATCH 265/332] Update README.md Make the README platform agnostic --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6f08b6ec3..e82387c3f 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,9 @@ For example, if you are using Xcode 13.3 (Swift 5.6), you will need If you are mainly interested in using swift-format (rather than developing it), then you can get it in three different ways: -### Included in Xcode +### Included in the Swift Toolchain -Xcode 16 and above include swift-format in the toolchain. You can run `swift-format` from anywhere on the system using `swift format` (notice the space instead of dash). To find the path at which `swift-format` is installed, run `xcrun --find swift-format`. +Swift 6 (included with Xcode 16) and above include swift-format in the toolchain. You can run `swift-format` from anywhere on the system using `swift format` (notice the space instead of dash). To find the path at which `swift-format` is installed on macOS, run `xcrun --find swift-format`. ### Installing via Homebrew From b8f8f9ec933fb00466caa0828e9804dcda7b2aa6 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 7 Oct 2024 08:31:53 -0400 Subject: [PATCH 266/332] Fix broken links in PrettyPrinter.md --- Documentation/PrettyPrinter.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/PrettyPrinter.md b/Documentation/PrettyPrinter.md index 2883d65a5..c37c5ce6a 100644 --- a/Documentation/PrettyPrinter.md +++ b/Documentation/PrettyPrinter.md @@ -31,7 +31,7 @@ The available cases are: `syntax`, `break`, `spaces`, `open`, `close`, `newlines`, `comment`, and `verbatim`. The behavior of each of them is described below with pseudocode examples. -See: [`Token.swift`](../Sources/SwiftFormatPrettyPrint/Token.swift) +See: [`Token.swift`](../Sources/SwiftFormat/PrettyPrint/Token.swift) #### Syntax @@ -326,7 +326,7 @@ beginning of a source file). When we have visited all nodes in the AST, the array of printing tokens is then passed on to the *scan* phase of the pretty-printer. -See: [`TokenStreamCreator.swift`](../Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift) +See: [`TokenStreamCreator.swift`](../Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift) ## Scan @@ -346,7 +346,7 @@ After having iterated over the entire list of tokens and calculated their lengths, we then loop over the tokens and call `print` for each token with its corresponding length. -See: [`PrettyPrint.swift:prettyPrint()`](../Sources/SwiftFormatPrettyPrint/PrettyPrint.swift) +See: [`PrettyPrint.swift:prettyPrint()`](../Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift) ### Syntax Tokens @@ -421,7 +421,7 @@ The logic for the `print` function is fairly complex and varies depending on the kind of token or break being printed. Rather than explain it here, we recommend viewing its documented source directly. -See: [`PrettyPrint.swift:printToken(...)`](../Sources/SwiftFormatPrettyPrint/PrettyPrint.swift) +See: [`PrettyPrint.swift:printToken(...)`](../Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift) ## Differences from Oppen's Algorithm From c46f2c6a9cf04e0fbaed76959b1eaf7b5bc15b80 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 7 Oct 2024 14:20:29 -0700 Subject: [PATCH 267/332] Support building swift-format using SwiftPM for toolchain builds on Windows This allows us to also run tests of swift-format on Windows. --- Package.swift | 44 +++++++++++++------ .../NoEmptyLineOpeningClosingBraces.swift | 4 +- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/Package.swift b/Package.swift index d8c46cb4f..1f853d81f 100644 --- a/Package.swift +++ b/Package.swift @@ -47,22 +47,22 @@ let package = Package( .target( name: "SwiftFormat", - dependencies: [ + dependencies: omittingExternalDependenciesIfNecessary([ .product(name: "Markdown", package: "swift-markdown"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "SwiftOperators", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), - ], + ]), exclude: ["CMakeLists.txt"] ), .target( name: "_SwiftFormatTestSupport", - dependencies: [ + dependencies: omittingExternalDependenciesIfNecessary([ "SwiftFormat", .product(name: "SwiftOperators", package: "swift-syntax"), - ] + ]) ), .plugin( name: "Format Source Code", @@ -93,36 +93,34 @@ let package = Package( .executableTarget( name: "generate-swift-format", dependencies: [ - "SwiftFormat", - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), + "SwiftFormat" ] ), .executableTarget( name: "swift-format", - dependencies: [ + dependencies: omittingExternalDependenciesIfNecessary([ "_SwiftFormatInstructionCounter", "SwiftFormat", .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), - ], + ]), exclude: ["CMakeLists.txt"], linkerSettings: swiftformatLinkSettings ), .testTarget( name: "SwiftFormatPerformanceTests", - dependencies: [ + dependencies: omittingExternalDependenciesIfNecessary([ "SwiftFormat", "_SwiftFormatTestSupport", .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), - ] + ]) ), .testTarget( name: "SwiftFormatTests", - dependencies: [ + dependencies: omittingExternalDependenciesIfNecessary([ "SwiftFormat", "_SwiftFormatTestSupport", .product(name: "Markdown", package: "swift-markdown"), @@ -130,7 +128,7 @@ let package = Package( .product(name: "SwiftParser", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - ] + ]) ), ] ) @@ -149,10 +147,28 @@ var installAction: Bool { hasEnvironmentVariable("SWIFTFORMAT_CI_INSTALL") } /// remote dependency. var useLocalDependencies: Bool { hasEnvironmentVariable("SWIFTCI_USE_LOCAL_DEPS") } +var omitExternalDependencies: Bool { hasEnvironmentVariable("SWIFTFORMAT_OMIT_EXTERNAL_DEPENDENCIES") } + +func omittingExternalDependenciesIfNecessary( + _ dependencies: [Target.Dependency] +) -> [Target.Dependency] { + guard omitExternalDependencies else { + return dependencies + } + return dependencies.filter { dependency in + if case .productItem(_, let package, _, _) = dependency { + return package == nil + } + return true + } +} + // MARK: - Dependencies var dependencies: [Package.Dependency] { - if useLocalDependencies { + if omitExternalDependencies { + return [] + } else if useLocalDependencies { return [ .package(path: "../swift-argument-parser"), .package(path: "../swift-markdown"), diff --git a/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift b/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift index 4e5da2dcb..b01d173bf 100644 --- a/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift +++ b/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift @@ -86,7 +86,9 @@ public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule { let (trimmedLeadingTrivia, count) = first.leadingTrivia.trimmingSuperfluousNewlines() if trimmedLeadingTrivia.sourceLength != first.leadingTriviaLength { diagnose(.removeEmptyLinesAfter(count), on: first, anchor: .leadingTrivia(0)) - result[index] = first.with(\.leadingTrivia, trimmedLeadingTrivia) + var first = first + first.leadingTrivia = trimmedLeadingTrivia + result[index] = first } } return rewrite(result).as(C.self)! From 69ad8985426ef231b36da11d3997064458d3a9ce Mon Sep 17 00:00:00 2001 From: Tim Condon <0xTim@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:26:47 +0100 Subject: [PATCH 268/332] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e82387c3f..db2e090a9 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ then you can get it in three different ways: ### Included in the Swift Toolchain -Swift 6 (included with Xcode 16) and above include swift-format in the toolchain. You can run `swift-format` from anywhere on the system using `swift format` (notice the space instead of dash). To find the path at which `swift-format` is installed on macOS, run `xcrun --find swift-format`. +Swift 6 (included with Xcode 16) and above include swift-format in the toolchain. You can run `swift-format` from anywhere on the system using `swift format` (notice the space instead of dash). To find the path at which `swift-format` is installed in Xcode, run `xcrun --find swift-format`. ### Installing via Homebrew From 231be3c04e22bd9a380403efca8f9199e81c4c2c Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 10 Oct 2024 16:02:42 -0700 Subject: [PATCH 269/332] Fix infinite looping if swift-format is run in a directory that doesn't contain a `.swift-format` file https://github.com/swiftlang/swift-format/pull/802 this sort of infinite looping issue on Windows but introduced it on Unix platforms where `URL.deletingLastPathComponent` on `/` returns `/..`. --- Sources/SwiftFormat/API/Configuration.swift | 19 +++++++++++++------ .../API/ConfigurationTests.swift | 9 +++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index e1584a5c5..9d90ee38e 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -425,17 +425,12 @@ public struct Configuration: Codable, Equatable { candidateDirectory.appendPathComponent("placeholder") } repeat { - let previousDirectory = candidateDirectory candidateDirectory.deleteLastPathComponent() - // if deleting a path component resulted in no change, terminate the loop - if candidateDirectory == previousDirectory { - break - } let candidateFile = candidateDirectory.appendingPathComponent(".swift-format") if FileManager.default.isReadableFile(atPath: candidateFile.path) { return candidateFile } - } while true + } while !candidateDirectory.isRoot return nil } @@ -477,3 +472,15 @@ public struct NoAssignmentInExpressionsConfiguration: Codable, Equatable { public init() {} } + +fileprivate extension URL { + var isRoot: Bool { + #if os(Windows) + // FIXME: We should call into Windows' native check to check if this path is a root once https://github.com/swiftlang/swift-foundation/issues/976 is fixed. + // https://github.com/swiftlang/swift-format/issues/844 + return self.pathComponents.count == 1 + #else + return self.path == "/" + #endif + } +} diff --git a/Tests/SwiftFormatTests/API/ConfigurationTests.swift b/Tests/SwiftFormatTests/API/ConfigurationTests.swift index 6834e9f55..2df50aa3b 100644 --- a/Tests/SwiftFormatTests/API/ConfigurationTests.swift +++ b/Tests/SwiftFormatTests/API/ConfigurationTests.swift @@ -17,4 +17,13 @@ final class ConfigurationTests: XCTestCase { XCTAssertEqual(defaultInitConfig, emptyJSONConfig) } + + func testMissingConfigurationFile() { + #if os(Windows) + let path = #"C:\test.swift"# + #else + let path = "/test.swift" + #endif + XCTAssertNil(Configuration.url(forConfigurationFileApplyingTo: URL(fileURLWithPath: path))) + } } From 0ea619ad3ead030cb72c70a386d35303f3c4db97 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 10 Oct 2024 16:25:57 -0700 Subject: [PATCH 270/332] Add `@ahoppen` and `@bnbarham` as code owners for CI directories --- CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index fdeb9b7ac..28d802dc0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -10,5 +10,5 @@ * @ahoppen @allevato @bnbarham @shawnhyam -.github/ @shahmishal -.swiftci/ @shahmishal +.github/ @ahoppen @bnbarham @shahmishal +.swiftci/ @ahoppen @bnbarham @shahmishal From ba8f585b726fa47f577b0d07528774093032bff5 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 11 Oct 2024 13:28:02 -0700 Subject: [PATCH 271/332] Fix infinite searching for `.swift-format` file on Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Due to https://github.com/swiftlang/swift-foundation/issues/980, the we may end up with a `path == ""` instead of `/`. We didn’t catch this in the test because we only end up with an empty path when searching for a `.swift-format` file from a `.swift-file` that was not at the root of a directory. --- Sources/SwiftFormat/API/Configuration.swift | 4 +++- .../API/ConfigurationTests.swift | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 9d90ee38e..7cc0d3855 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -480,7 +480,9 @@ fileprivate extension URL { // https://github.com/swiftlang/swift-format/issues/844 return self.pathComponents.count == 1 #else - return self.path == "/" + // On Linux, we may end up with an string for the path due to https://github.com/swiftlang/swift-foundation/issues/980 + // TODO: Remove the check for "" once https://github.com/swiftlang/swift-foundation/issues/980 is fixed. + return self.path == "/" || self.path == "" #endif } } diff --git a/Tests/SwiftFormatTests/API/ConfigurationTests.swift b/Tests/SwiftFormatTests/API/ConfigurationTests.swift index 2df50aa3b..e96331db9 100644 --- a/Tests/SwiftFormatTests/API/ConfigurationTests.swift +++ b/Tests/SwiftFormatTests/API/ConfigurationTests.swift @@ -26,4 +26,21 @@ final class ConfigurationTests: XCTestCase { #endif XCTAssertNil(Configuration.url(forConfigurationFileApplyingTo: URL(fileURLWithPath: path))) } + + func testMissingConfigurationFileInSubdirectory() { + #if os(Windows) + let path = #"C:\whatever\test.swift"# + #else + let path = "/whatever/test.swift" + #endif + XCTAssertNil(Configuration.url(forConfigurationFileApplyingTo: URL(fileURLWithPath: path))) + } + + func testMissingConfigurationFileMountedDirectory() throws { + #if !os(Windows) + try XCTSkipIf(true, #"\\ file mounts are only a concept on Windows"#) + #endif + let path = #"\\mount\test.swift"# + XCTAssertNil(Configuration.url(forConfigurationFileApplyingTo: URL(fileURLWithPath: path))) + } } From 1babba4d62e00aca39c93345c8c5ee367640b6c2 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 7 Oct 2024 14:20:29 -0700 Subject: [PATCH 272/332] Support building only tests using SwiftPM This allows us to build everything required for the executable using CMake and only the test targets using SwiftPM, enabling us to run tests on Windows from build.ps1 with minimal overhead. --- Package.swift | 263 ++++++++++++++++++++++++++------------------------ 1 file changed, 137 insertions(+), 126 deletions(-) diff --git a/Package.swift b/Package.swift index 1f853d81f..f9f828c7f 100644 --- a/Package.swift +++ b/Package.swift @@ -14,123 +14,143 @@ import Foundation import PackageDescription +var products: [Product] = [ + .executable( + name: "swift-format", + targets: ["swift-format"] + ), + .library( + name: "SwiftFormat", + targets: ["SwiftFormat"] + ), + .plugin( + name: "FormatPlugin", + targets: ["Format Source Code"] + ), + .plugin( + name: "LintPlugin", + targets: ["Lint Source Code"] + ), +] + +var targets: [Target] = [ + .target( + name: "_SwiftFormatInstructionCounter", + exclude: ["CMakeLists.txt"] + ), + + .target( + name: "SwiftFormat", + dependencies: [ + .product(name: "Markdown", package: "swift-markdown"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), + .product(name: "SwiftOperators", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), + ], + exclude: ["CMakeLists.txt"] + ), + .target( + name: "_SwiftFormatTestSupport", + dependencies: [ + "SwiftFormat", + .product(name: "SwiftOperators", package: "swift-syntax"), + ] + ), + .plugin( + name: "Format Source Code", + capability: .command( + intent: .sourceCodeFormatting(), + permissions: [ + .writeToPackageDirectory(reason: "This command formats the Swift source files") + ] + ), + dependencies: [ + .target(name: "swift-format") + ], + path: "Plugins/FormatPlugin" + ), + .plugin( + name: "Lint Source Code", + capability: .command( + intent: .custom( + verb: "lint-source-code", + description: "Lint source code for a specified target." + ) + ), + dependencies: [ + .target(name: "swift-format") + ], + path: "Plugins/LintPlugin" + ), + .executableTarget( + name: "generate-swift-format", + dependencies: [ + "SwiftFormat" + ] + ), + .executableTarget( + name: "swift-format", + dependencies: [ + "_SwiftFormatInstructionCounter", + "SwiftFormat", + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + ], + exclude: ["CMakeLists.txt"], + linkerSettings: swiftformatLinkSettings + ), + + .testTarget( + name: "SwiftFormatPerformanceTests", + dependencies: [ + "SwiftFormat", + "_SwiftFormatTestSupport", + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + ] + ), + .testTarget( + name: "SwiftFormatTests", + dependencies: [ + "SwiftFormat", + "_SwiftFormatTestSupport", + .product(name: "Markdown", package: "swift-markdown"), + .product(name: "SwiftOperators", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), + ] + ), +] + +if buildOnlyTests { + products = [] + targets = targets.compactMap { target in + guard target.isTest || target.name == "_SwiftFormatTestSupport" else { + return nil + } + target.dependencies = target.dependencies.filter { dependency in + if case .byNameItem(name: "_SwiftFormatTestSupport", _) = dependency { + return true + } + return false + } + return target + } +} + let package = Package( name: "swift-format", platforms: [ .macOS("12.0"), .iOS("13.0"), ], - products: [ - .executable( - name: "swift-format", - targets: ["swift-format"] - ), - .library( - name: "SwiftFormat", - targets: ["SwiftFormat"] - ), - .plugin( - name: "FormatPlugin", - targets: ["Format Source Code"] - ), - .plugin( - name: "LintPlugin", - targets: ["Lint Source Code"] - ), - ], + products: products, dependencies: dependencies, - targets: [ - .target( - name: "_SwiftFormatInstructionCounter", - exclude: ["CMakeLists.txt"] - ), - - .target( - name: "SwiftFormat", - dependencies: omittingExternalDependenciesIfNecessary([ - .product(name: "Markdown", package: "swift-markdown"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - .product(name: "SwiftOperators", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), - ]), - exclude: ["CMakeLists.txt"] - ), - .target( - name: "_SwiftFormatTestSupport", - dependencies: omittingExternalDependenciesIfNecessary([ - "SwiftFormat", - .product(name: "SwiftOperators", package: "swift-syntax"), - ]) - ), - .plugin( - name: "Format Source Code", - capability: .command( - intent: .sourceCodeFormatting(), - permissions: [ - .writeToPackageDirectory(reason: "This command formats the Swift source files") - ] - ), - dependencies: [ - .target(name: "swift-format") - ], - path: "Plugins/FormatPlugin" - ), - .plugin( - name: "Lint Source Code", - capability: .command( - intent: .custom( - verb: "lint-source-code", - description: "Lint source code for a specified target." - ) - ), - dependencies: [ - .target(name: "swift-format") - ], - path: "Plugins/LintPlugin" - ), - .executableTarget( - name: "generate-swift-format", - dependencies: [ - "SwiftFormat" - ] - ), - .executableTarget( - name: "swift-format", - dependencies: omittingExternalDependenciesIfNecessary([ - "_SwiftFormatInstructionCounter", - "SwiftFormat", - .product(name: "ArgumentParser", package: "swift-argument-parser"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - ]), - exclude: ["CMakeLists.txt"], - linkerSettings: swiftformatLinkSettings - ), - - .testTarget( - name: "SwiftFormatPerformanceTests", - dependencies: omittingExternalDependenciesIfNecessary([ - "SwiftFormat", - "_SwiftFormatTestSupport", - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - ]) - ), - .testTarget( - name: "SwiftFormatTests", - dependencies: omittingExternalDependenciesIfNecessary([ - "SwiftFormat", - "_SwiftFormatTestSupport", - .product(name: "Markdown", package: "swift-markdown"), - .product(name: "SwiftOperators", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - ]) - ), - ] + targets: targets ) // MARK: - Parse build arguments @@ -147,26 +167,17 @@ var installAction: Bool { hasEnvironmentVariable("SWIFTFORMAT_CI_INSTALL") } /// remote dependency. var useLocalDependencies: Bool { hasEnvironmentVariable("SWIFTCI_USE_LOCAL_DEPS") } -var omitExternalDependencies: Bool { hasEnvironmentVariable("SWIFTFORMAT_OMIT_EXTERNAL_DEPENDENCIES") } - -func omittingExternalDependenciesIfNecessary( - _ dependencies: [Target.Dependency] -) -> [Target.Dependency] { - guard omitExternalDependencies else { - return dependencies - } - return dependencies.filter { dependency in - if case .productItem(_, let package, _, _) = dependency { - return package == nil - } - return true - } -} +/// Build only tests targets and test support modules. +/// +/// This is used to test swift-format on Windows, where the modules required for the `swift-format` executable are +/// built using CMake. When using this setting, the caller is responsible for passing the required search paths to +/// the `swift test` invocation so that all pre-built modules can be found. +var buildOnlyTests: Bool { hasEnvironmentVariable("SWIFTFORMAT_BUILD_ONLY_TESTS") } // MARK: - Dependencies var dependencies: [Package.Dependency] { - if omitExternalDependencies { + if buildOnlyTests { return [] } else if useLocalDependencies { return [ From 79cef55b88bdbbd39efcc1b2796c076c34fa535f Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 13 Oct 2024 22:07:41 -0700 Subject: [PATCH 273/332] Allow building against a single dynamic swift-syntax library When `SWIFTSYNTAX_BUILD_DYNAMIC_LIBRARY` is specified, change swift-syntax dependencies to depend on a single `_SwiftSyntaxDynamic` product instead of separate products for each module. This allows us to build SourceKit-LSP on Windows using SwiftPM without exceeding the maximum symbol limit and thus run SourceKit-LSP tests on Windows. See https://github.com/swiftlang/sourcekit-lsp/pull/1754 and https://github.com/swiftlang/swift-syntax/pull/2879. --- Package.swift | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Package.swift b/Package.swift index f9f828c7f..251a6cae1 100644 --- a/Package.swift +++ b/Package.swift @@ -42,21 +42,21 @@ var targets: [Target] = [ .target( name: "SwiftFormat", dependencies: [ - .product(name: "Markdown", package: "swift-markdown"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - .product(name: "SwiftOperators", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), - ], + .product(name: "Markdown", package: "swift-markdown") + ] + + swiftSyntaxDependencies([ + "SwiftOperators", "SwiftParser", "SwiftParserDiagnostics", "SwiftSyntax", "SwiftSyntaxBuilder", + ]), exclude: ["CMakeLists.txt"] ), .target( name: "_SwiftFormatTestSupport", dependencies: [ - "SwiftFormat", - .product(name: "SwiftOperators", package: "swift-syntax"), + "SwiftFormat" ] + + swiftSyntaxDependencies([ + "SwiftOperators", "SwiftParser", "SwiftParserDiagnostics", "SwiftSyntax", "SwiftSyntaxBuilder", + ]) ), .plugin( name: "Format Source Code", @@ -98,7 +98,7 @@ var targets: [Target] = [ .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), - ], + ] + swiftSyntaxDependencies(["SwiftParser", "SwiftSyntax"]), exclude: ["CMakeLists.txt"], linkerSettings: swiftformatLinkSettings ), @@ -110,7 +110,7 @@ var targets: [Target] = [ "_SwiftFormatTestSupport", .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), - ] + ] + swiftSyntaxDependencies(["SwiftParser", "SwiftSyntax"]) ), .testTarget( name: "SwiftFormatTests", @@ -153,6 +153,14 @@ let package = Package( targets: targets ) +func swiftSyntaxDependencies(_ names: [String]) -> [Target.Dependency] { + if buildDynamicSwiftSyntaxLibrary { + return [.product(name: "_SwiftSyntaxDynamic", package: "swift-syntax")] + } else { + return names.map { .product(name: $0, package: "swift-syntax") } + } +} + // MARK: - Parse build arguments func hasEnvironmentVariable(_ name: String) -> Bool { @@ -174,6 +182,15 @@ var useLocalDependencies: Bool { hasEnvironmentVariable("SWIFTCI_USE_LOCAL_DEPS" /// the `swift test` invocation so that all pre-built modules can be found. var buildOnlyTests: Bool { hasEnvironmentVariable("SWIFTFORMAT_BUILD_ONLY_TESTS") } +/// Whether swift-syntax is being built as a single dynamic library instead of as a separate library per module. +/// +/// This means that the swift-syntax symbols don't need to be statically linked, which alles us to stay below the +/// maximum number of exported symbols on Windows, in turn allowing us to build sourcekit-lsp using SwiftPM on Windows +/// and run its tests. +var buildDynamicSwiftSyntaxLibrary: Bool { + hasEnvironmentVariable("SWIFTSYNTAX_BUILD_DYNAMIC_LIBRARY") +} + // MARK: - Dependencies var dependencies: [Package.Dependency] { From a5298f7b1fa618bee72d040469dd67c53fdd24cf Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 14 Oct 2024 11:18:48 -0700 Subject: [PATCH 274/332] Remove duplicated dependencies in Package.swift Not sure how I missed this. --- Package.swift | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Package.swift b/Package.swift index 251a6cae1..228b13f5f 100644 --- a/Package.swift +++ b/Package.swift @@ -96,8 +96,6 @@ var targets: [Target] = [ "_SwiftFormatInstructionCounter", "SwiftFormat", .product(name: "ArgumentParser", package: "swift-argument-parser"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), ] + swiftSyntaxDependencies(["SwiftParser", "SwiftSyntax"]), exclude: ["CMakeLists.txt"], linkerSettings: swiftformatLinkSettings @@ -108,8 +106,6 @@ var targets: [Target] = [ dependencies: [ "SwiftFormat", "_SwiftFormatTestSupport", - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), ] + swiftSyntaxDependencies(["SwiftParser", "SwiftSyntax"]) ), .testTarget( @@ -118,11 +114,7 @@ var targets: [Target] = [ "SwiftFormat", "_SwiftFormatTestSupport", .product(name: "Markdown", package: "swift-markdown"), - .product(name: "SwiftOperators", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - ] + ] + swiftSyntaxDependencies(["SwiftOperators", "SwiftParser", "SwiftSyntax", "SwiftSyntaxBuilder"]) ), ] From ef73806f30f24736156221665543e73bff8afff8 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Thu, 17 Oct 2024 02:29:28 +0900 Subject: [PATCH 275/332] Fix test by deduplicating files in `FileIterator.next()` --- Sources/SwiftFormat/Utilities/FileIterator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/Utilities/FileIterator.swift b/Sources/SwiftFormat/Utilities/FileIterator.swift index b833bc6f4..0ce11354d 100644 --- a/Sources/SwiftFormat/Utilities/FileIterator.swift +++ b/Sources/SwiftFormat/Utilities/FileIterator.swift @@ -97,12 +97,12 @@ public struct FileIterator: Sequence, IteratorProtocol { output = next } } - if let out = output, visited.contains(out.absoluteURL.standardized.path) { + if let out = output, visited.contains(out.absoluteURL.standardized.standardizedFileURL.path) { output = nil } } if let out = output { - visited.insert(out.absoluteURL.standardized.path) + visited.insert(out.absoluteURL.standardized.standardizedFileURL.path) } return output } From 5f90fb3d8646b74946bd9917c72d49d8acdeba16 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Thu, 17 Oct 2024 04:25:38 +0900 Subject: [PATCH 276/332] Remove `isDirectory: false` so that `URL` can infer it --- Sources/SwiftFormat/Utilities/FileIterator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/Utilities/FileIterator.swift b/Sources/SwiftFormat/Utilities/FileIterator.swift index 0ce11354d..8189f18cf 100644 --- a/Sources/SwiftFormat/Utilities/FileIterator.swift +++ b/Sources/SwiftFormat/Utilities/FileIterator.swift @@ -135,7 +135,7 @@ public struct FileIterator: Sequence, IteratorProtocol { else { break } - path = URL(fileURLWithPath: destination, isDirectory: false, relativeTo: item).path + path = URL(fileURLWithPath: destination, relativeTo: item).path fallthrough case .typeRegular: From b9065b18d8435618635e0f619e62a97e7dee55fb Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Fri, 18 Oct 2024 03:18:39 +0900 Subject: [PATCH 277/332] Simplify the code by replacing `.abosoluteURL.standardized` with `.standardizedFileURL` --- Sources/SwiftFormat/Utilities/FileIterator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/Utilities/FileIterator.swift b/Sources/SwiftFormat/Utilities/FileIterator.swift index 8189f18cf..9d1a1d2cf 100644 --- a/Sources/SwiftFormat/Utilities/FileIterator.swift +++ b/Sources/SwiftFormat/Utilities/FileIterator.swift @@ -97,12 +97,12 @@ public struct FileIterator: Sequence, IteratorProtocol { output = next } } - if let out = output, visited.contains(out.absoluteURL.standardized.standardizedFileURL.path) { + if let out = output, visited.contains(out.standardizedFileURL.path) { output = nil } } if let out = output { - visited.insert(out.absoluteURL.standardized.standardizedFileURL.path) + visited.insert(out.standardizedFileURL.path) } return output } From 8581f2c6964de247aec49aea06f705778f073a1b Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 17 Oct 2024 15:10:59 -0700 Subject: [PATCH 278/332] Skip missing configuration file tests on Windows for Swift < 6.0.2 We require https://github.com/swiftlang/swift-foundation/pull/983 to be in the toolchain for these test to pass. --- .../API/ConfigurationTests.swift | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Tests/SwiftFormatTests/API/ConfigurationTests.swift b/Tests/SwiftFormatTests/API/ConfigurationTests.swift index e96331db9..86a9d8dd2 100644 --- a/Tests/SwiftFormatTests/API/ConfigurationTests.swift +++ b/Tests/SwiftFormatTests/API/ConfigurationTests.swift @@ -18,8 +18,11 @@ final class ConfigurationTests: XCTestCase { XCTAssertEqual(defaultInitConfig, emptyJSONConfig) } - func testMissingConfigurationFile() { + func testMissingConfigurationFile() throws { #if os(Windows) + #if compiler(<6.0.2) + try XCTSkipIf(true, "Requires https://github.com/swiftlang/swift-foundation/pull/983") + #endif let path = #"C:\test.swift"# #else let path = "/test.swift" @@ -27,8 +30,11 @@ final class ConfigurationTests: XCTestCase { XCTAssertNil(Configuration.url(forConfigurationFileApplyingTo: URL(fileURLWithPath: path))) } - func testMissingConfigurationFileInSubdirectory() { + func testMissingConfigurationFileInSubdirectory() throws { #if os(Windows) + #if compiler(<6.0.2) + try XCTSkipIf(true, "Requires https://github.com/swiftlang/swift-foundation/pull/983") + #endif let path = #"C:\whatever\test.swift"# #else let path = "/whatever/test.swift" @@ -37,7 +43,11 @@ final class ConfigurationTests: XCTestCase { } func testMissingConfigurationFileMountedDirectory() throws { - #if !os(Windows) + #if os(Windows) + #if compiler(<6.0.2) + try XCTSkipIf(true, "Requires https://github.com/swiftlang/swift-foundation/pull/983") + #endif + #else try XCTSkipIf(true, #"\\ file mounts are only a concept on Windows"#) #endif let path = #"\\mount\test.swift"# From 8f4f6e2724856c57a3644cba695bc1b2b1062303 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 17 Oct 2024 15:54:08 -0700 Subject: [PATCH 279/332] Make tests pass using Swift 5.9 and Swift 5.10 on Windows - We need to disable a few file traversal tests because of Foundation issues - We need to use `URL(fileURLWithPath:)` instead of `URL(string:)` to construct a URL from a string --- .../Rules/LintOrFormatRuleTestCase.swift | 2 +- .../Utilities/FileIteratorTests.swift | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index 49c26bd1f..9ae0050e0 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -55,7 +55,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { syntax: sourceFileSyntax, source: unmarkedSource, operatorTable: OperatorTable.standardOperators, - assumingFileURL: URL(string: file.description)! + assumingFileURL: URL(fileURLWithPath: file.description) ) // Check that pipeline produces the expected findings diff --git a/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift index 965c90fe3..d2bef50c4 100644 --- a/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift +++ b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift @@ -24,14 +24,20 @@ final class FileIteratorTests: XCTestCase { try FileManager.default.removeItem(at: tmpdir) } - func testNoFollowSymlinks() { + func testNoFollowSymlinks() throws { + #if os(Windows) && compiler(<5.10) + try XCTSkipIf(true, "Foundation does not follow symlinks on Windows") + #endif let seen = allFilesSeen(iteratingOver: [tmpdir], followSymlinks: false) XCTAssertEqual(seen.count, 2) XCTAssertTrue(seen.contains { $0.hasSuffix("project/real1.swift") }) XCTAssertTrue(seen.contains { $0.hasSuffix("project/real2.swift") }) } - func testFollowSymlinks() { + func testFollowSymlinks() throws { + #if os(Windows) && compiler(<5.10) + try XCTSkipIf(true, "Foundation does not follow symlinks on Windows") + #endif let seen = allFilesSeen(iteratingOver: [tmpdir], followSymlinks: true) XCTAssertEqual(seen.count, 3) XCTAssertTrue(seen.contains { $0.hasSuffix("project/real1.swift") }) @@ -40,7 +46,10 @@ final class FileIteratorTests: XCTestCase { XCTAssertTrue(seen.contains { $0.hasSuffix("project/.hidden.swift") }) } - func testTraversesHiddenFilesIfExplicitlySpecified() { + func testTraversesHiddenFilesIfExplicitlySpecified() throws { + #if os(Windows) && compiler(<5.10) + try XCTSkipIf(true, "Foundation does not follow symlinks on Windows") + #endif let seen = allFilesSeen( iteratingOver: [tmpURL("project/.build"), tmpURL("project/.hidden.swift")], followSymlinks: false From 39ee6a2efda73fb89ee6019f4ec356e8a3bb6db5 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 24 Oct 2024 06:59:26 -0700 Subject: [PATCH 280/332] Run Windows tests before tagging a release The GitHub workflows enabled Windows in the testing matrix. We needed to port the pre-build commands that apply the release commits to Windows to make the Windows checks pass. --- .github/workflows/create-release-commits.sh | 28 ------ .github/workflows/publish_release.yml | 99 +++++++++++++++++---- 2 files changed, 81 insertions(+), 46 deletions(-) delete mode 100755 .github/workflows/create-release-commits.sh diff --git a/.github/workflows/create-release-commits.sh b/.github/workflows/create-release-commits.sh deleted file mode 100755 index 81fa8eba9..000000000 --- a/.github/workflows/create-release-commits.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -SWIFT_SYNTAX_TAG="$1" -SWIFT_FORMAT_VERSION="$2" - -if [[ -z "$SWIFT_SYNTAX_TAG" || -z "$SWIFT_FORMAT_VERSION" ]]; then - echo "Update the Package manifest to reference a specific version of swift-syntax and embed the given version in the swift-format --version command" - echo "Usage create-release-commits.sh " - echo " SWIFT_SYNTAX_TAG: The tag of swift-syntax to depend on" - echo " SWIFT_FORMAT_VERSION: The version of swift-format that is about to be released" - exit 1 -fi - -# Without this, we can't perform git operations in GitHub actions. -git config --global --add safe.directory "$(realpath .)" - -git config --local user.name 'swift-ci' -git config --local user.email 'swift-ci@users.noreply.github.com' - -sed -E -i "s#branch: \"(main|release/[0-9]+\.[0-9]+)\"#from: \"$SWIFT_SYNTAX_TAG\"#" Package.swift -git add Package.swift -git commit -m "Change swift-syntax dependency to $SWIFT_SYNTAX_TAG" - -sed -E -i "s#print\(\".*\"\)#print\(\"$SWIFT_FORMAT_VERSION\"\)#" Sources/swift-format/PrintVersion.swift -git add Sources/swift-format/PrintVersion.swift -git commit -m "Change version to $SWIFT_FORMAT_VERSION" diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 2528ebf6f..2081696ec 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -34,12 +34,12 @@ jobs: echo "${{ github.triggering_actor }} is not allowed to create a release" exit 1 fi - define_tags: - name: Determine dependent swift-syntax version and prerelease date + create_release_commits: + name: Create release commits runs-on: ubuntu-latest outputs: - swift_syntax_tag: ${{ steps.swift_syntax_tag.outputs.swift_syntax_tag }} swift_format_version: ${{ steps.swift_format_version.outputs.swift_format_version }} + release_commit_patch: ${{ steps.create_release_commits.outputs.release_commit_patch }} steps: - name: Determine swift-syntax tag to depend on id: swift_syntax_tag @@ -65,37 +65,100 @@ jobs: fi echo "Using swift-format version: $SWIFT_FORMAT_VERSION" echo "swift_format_version=$SWIFT_FORMAT_VERSION" >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create release commits + id: create_release_commits + run: | + # Without this, we can't perform git operations in GitHub actions. + git config --global --add safe.directory "$(realpath .)" + git config --local user.name 'swift-ci' + git config --local user.email 'swift-ci@users.noreply.github.com' + + BASE_COMMIT=$(git rev-parse HEAD) + + sed -E -i "s#branch: \"(main|release/[0-9]+\.[0-9]+)\"#from: \"${{ steps.swift_syntax_tag.outputs.swift_syntax_tag }}\"#" Package.swift + git add Package.swift + git commit -m "Change swift-syntax dependency to ${{ steps.swift_syntax_tag.outputs.swift_syntax_tag }}" + + sed -E -i "s#print\(\".*\"\)#print\(\"${{ steps.swift_format_version.outputs.swift_format_version }}\"\)#" Sources/swift-format/PrintVersion.swift + git add Sources/swift-format/PrintVersion.swift + git commit -m "Change version to ${{ steps.swift_format_version.outputs.swift_format_version }}" + + { + echo 'release_commit_patch<> "$GITHUB_OUTPUT" test_debug: name: Test in Debug configuration - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main - needs: define_tags + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@windows-pre-build + needs: create_release_commits with: - pre_build_command: bash .github/workflows/create-release-commits.sh '${{ needs.define_tags.outputs.swift_syntax_tag }}' '${{ needs.define_tags.outputs.swift_format_version }}' + linux_pre_build_command: | + git config --global --add safe.directory "$(realpath .)" + git config --local user.name 'swift-ci' + git config --local user.email 'swift-ci@users.noreply.github.com' + git am << EOF + ${{ needs.create_release_commits.outputs.release_commit_patch }} + EOF + windows_pre_build_command: | + git config --local user.name "swift-ci" + git config --local user.email "swift-ci@users.noreply.github.com" + echo @" + ${{ needs.create_release_commits.outputs.release_commit_patch }} + "@ > $env:TEMP\patch.diff + # For some reason `git am` fails in Powershell with the following error. Executing it in cmd works... + # fatal: empty ident name (for <>) not allowed + cmd /c "type $env:TEMP\patch.diff | git am || (exit /b 1)" # We require that releases of swift-format build without warnings - build_command: swift test -Xswiftc -warnings-as-errors + linux_build_command: swift test -Xswiftc -warnings-as-errors + windows_build_command: swift test -Xswiftc -warnings-as-errors test_release: name: Test in Release configuration - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main - needs: define_tags + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@windows-pre-build + needs: create_release_commits with: - pre_build_command: bash .github/workflows/create-release-commits.sh '${{ needs.define_tags.outputs.swift_syntax_tag }}' '${{ needs.define_tags.outputs.swift_format_version }}' + linux_pre_build_command: | + git config --global --add safe.directory "$(realpath .)" + git config --local user.name 'swift-ci' + git config --local user.email 'swift-ci@users.noreply.github.com' + git am << EOF + ${{ needs.create_release_commits.outputs.release_commit_patch }} + EOF + windows_pre_build_command: | + git config --local user.name "swift-ci" + git config --local user.email "swift-ci@users.noreply.github.com" + echo @" + ${{ needs.create_release_commits.outputs.release_commit_patch }} + "@ > $env:TEMP\patch.diff + # For some reason `git am` fails in Powershell with the following error. Executing it in cmd works... + # fatal: empty ident name (for <>) not allowed + cmd /c "type $env:TEMP\patch.diff | git am || (exit /b 1)" # We require that releases of swift-format build without warnings - build_command: swift test -c release -Xswiftc -warnings-as-errors + linux_build_command: swift test -Xswiftc -warnings-as-errors + windows_build_command: swift test -Xswiftc -warnings-as-errors create_tag: name: Create Tag runs-on: ubuntu-latest - needs: [check_triggering_actor, test_debug, test_release, define_tags] + needs: [check_triggering_actor, test_debug, create_release_commits] permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Create release commits - run: bash .github/workflows/create-release-commits.sh '${{ needs.define_tags.outputs.swift_syntax_tag }}' '${{ needs.define_tags.outputs.swift_format_version }}' + - name: Apply release commits + run: | + git config --global --add safe.directory "$(realpath .)" + git config --local user.name 'swift-ci' + git config --local user.email 'swift-ci@users.noreply.github.com' + git am << EOF + ${{ needs.create_release_commits.outputs.release_commit_patch }} + EOF - name: Tag release run: | - git tag "${{ needs.define_tags.outputs.swift_format_version }}" - git push origin "${{ needs.define_tags.outputs.swift_format_version }}" + git tag "${{ needs.create_release_commits.outputs.swift_format_version }}" + git push origin "${{ needs.create_release_commits.outputs.swift_format_version }}" - name: Create release env: GH_TOKEN: ${{ github.token }} @@ -104,6 +167,6 @@ jobs: # Only create a release automatically for prereleases. For real releases, release notes should be crafted by hand. exit fi - gh release create "${{ needs.define_tags.outputs.swift_format_version }}" \ - --title "${{ needs.define_tags.outputs.swift_format_version }}" \ + gh release create "${{ needs.create_release_commits.outputs.swift_format_version }}" \ + --title "${{ needs.create_release_commits.outputs.swift_format_version }}" \ --prerelease From ce212cab75d84524c66757d597b165a7d353c36d Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 24 Oct 2024 08:42:44 -0700 Subject: [PATCH 281/332] Use matrix to run debug and release --- .github/workflows/publish_release.yml | 40 +++++++-------------------- 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 2081696ec..03c4f04f5 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -90,10 +90,14 @@ jobs: git format-patch "$BASE_COMMIT"..HEAD --stdout echo EOF } >> "$GITHUB_OUTPUT" - test_debug: - name: Test in Debug configuration - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@windows-pre-build + test: + name: Test in ${{ matrix.release && 'Release' || 'Debug' }} configuration + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main needs: create_release_commits + strategy: + fail-fast: false + matrix: + release: [true, false] with: linux_pre_build_command: | git config --global --add safe.directory "$(realpath .)" @@ -112,36 +116,12 @@ jobs: # fatal: empty ident name (for <>) not allowed cmd /c "type $env:TEMP\patch.diff | git am || (exit /b 1)" # We require that releases of swift-format build without warnings - linux_build_command: swift test -Xswiftc -warnings-as-errors - windows_build_command: swift test -Xswiftc -warnings-as-errors - test_release: - name: Test in Release configuration - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@windows-pre-build - needs: create_release_commits - with: - linux_pre_build_command: | - git config --global --add safe.directory "$(realpath .)" - git config --local user.name 'swift-ci' - git config --local user.email 'swift-ci@users.noreply.github.com' - git am << EOF - ${{ needs.create_release_commits.outputs.release_commit_patch }} - EOF - windows_pre_build_command: | - git config --local user.name "swift-ci" - git config --local user.email "swift-ci@users.noreply.github.com" - echo @" - ${{ needs.create_release_commits.outputs.release_commit_patch }} - "@ > $env:TEMP\patch.diff - # For some reason `git am` fails in Powershell with the following error. Executing it in cmd works... - # fatal: empty ident name (for <>) not allowed - cmd /c "type $env:TEMP\patch.diff | git am || (exit /b 1)" - # We require that releases of swift-format build without warnings - linux_build_command: swift test -Xswiftc -warnings-as-errors - windows_build_command: swift test -Xswiftc -warnings-as-errors + linux_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' }} + windows_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' }} create_tag: name: Create Tag runs-on: ubuntu-latest - needs: [check_triggering_actor, test_debug, create_release_commits] + needs: [check_triggering_actor, test, create_release_commits] permissions: contents: write steps: From c3b0c9f1bf24a0a6ad587999a450d506e2a3405b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 25 Oct 2024 12:59:59 -0700 Subject: [PATCH 282/332] Prepare for integer generics with new generic argument node --- .../Rules/UseShorthandTypeNames.swift | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift index dc101a018..67a8d8aa1 100644 --- a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift @@ -47,24 +47,28 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { switch node.name.text { case "Array": - guard let typeArgument = genericArgumentList.firstAndOnly else { + guard let argument = genericArgumentList.firstAndOnly, + case .type(let typeArgument) = argument else { newNode = nil break } + newNode = shorthandArrayType( - element: typeArgument.argument, + element: typeArgument, leadingTrivia: leadingTrivia, trailingTrivia: trailingTrivia ) case "Dictionary": - guard let typeArguments = exactlyTwoChildren(of: genericArgumentList) else { + guard let arguments = exactlyTwoChildren(of: genericArgumentList), + case .type(let type0Argument) = arguments.0.argument, + caes .type(let type1Argument) = arguments.1.argument else { newNode = nil break } newNode = shorthandDictionaryType( - key: typeArguments.0.argument, - value: typeArguments.1.argument, + key: type0Argument, + value: type1Argument, leadingTrivia: leadingTrivia, trailingTrivia: trailingTrivia ) @@ -74,12 +78,13 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = nil break } - guard let typeArgument = genericArgumentList.firstAndOnly else { + guard let argument = genericArgumentList.firstAndOnly, + case .type(let typeArgument) = argument else { newNode = nil break } newNode = shorthandOptionalType( - wrapping: typeArgument.argument, + wrapping: typeArgument, leadingTrivia: leadingTrivia, trailingTrivia: trailingTrivia ) @@ -137,25 +142,28 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { switch expression.baseName.text { case "Array": - guard let typeArgument = genericArgumentList.firstAndOnly else { + guard let argument = genericArgumentList.firstAndOnly, + case .type(let typeArgument) = argument else { newNode = nil break } let arrayTypeExpr = makeArrayTypeExpression( - elementType: typeArgument.argument, + elementType: typeArgument, leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia) ) newNode = ExprSyntax(arrayTypeExpr) case "Dictionary": - guard let typeArguments = exactlyTwoChildren(of: genericArgumentList) else { + guard let arguments = exactlyTwoChildren(of: genericArgumentList), + case .type(let type0Argument) = arguments.0.argument, + case .type(let type1Argument) = arguments.1.argument else { newNode = nil break } let dictTypeExpr = makeDictionaryTypeExpression( - keyType: typeArguments.0.argument, - valueType: typeArguments.1.argument, + keyType: type0Argument, + valueType: type1Argument, leftSquare: TokenSyntax.leftSquareToken(leadingTrivia: leadingTrivia), colon: TokenSyntax.colonToken(trailingTrivia: .spaces(1)), rightSquare: TokenSyntax.rightSquareToken(trailingTrivia: trailingTrivia) @@ -163,12 +171,13 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = ExprSyntax(dictTypeExpr) case "Optional": - guard let typeArgument = genericArgumentList.firstAndOnly else { + guard let argument = genericArgumentList.firstAndOnly, + case .type(let typeArgument) = argument else { newNode = nil break } let optionalTypeExpr = makeOptionalTypeExpression( - wrapping: typeArgument.argument, + wrapping: typeArgument, leadingTrivia: leadingTrivia, questionMark: TokenSyntax.postfixQuestionMarkToken(trailingTrivia: trailingTrivia) ) From dfb366a022a222fbca84591af8486669f460afa4 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 25 Oct 2024 14:02:19 -0700 Subject: [PATCH 283/332] Fix formatting --- .../Rules/UseShorthandTypeNames.swift | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift index 67a8d8aa1..d24b32b51 100644 --- a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift @@ -47,12 +47,10 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { switch node.name.text { case "Array": - guard let argument = genericArgumentList.firstAndOnly, - case .type(let typeArgument) = argument else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly else { newNode = nil break } - newNode = shorthandArrayType( element: typeArgument, leadingTrivia: leadingTrivia, @@ -60,9 +58,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { ) case "Dictionary": - guard let arguments = exactlyTwoChildren(of: genericArgumentList), - case .type(let type0Argument) = arguments.0.argument, - caes .type(let type1Argument) = arguments.1.argument else { + guard case (.type(let type0Argument), .type(let type1Argument)) = exactlyTwoChildren(of: genericArgumentList) + else { newNode = nil break } @@ -78,8 +75,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = nil break } - guard let argument = genericArgumentList.firstAndOnly, - case .type(let typeArgument) = argument else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly else { newNode = nil break } @@ -142,8 +138,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { switch expression.baseName.text { case "Array": - guard let argument = genericArgumentList.firstAndOnly, - case .type(let typeArgument) = argument else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly else { newNode = nil break } @@ -155,9 +150,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = ExprSyntax(arrayTypeExpr) case "Dictionary": - guard let arguments = exactlyTwoChildren(of: genericArgumentList), - case .type(let type0Argument) = arguments.0.argument, - case .type(let type1Argument) = arguments.1.argument else { + guard case (.type(let type0Argument), .type(let type1Argument)) = exactlyTwoChildren(of: genericArgumentList) + else { newNode = nil break } @@ -171,8 +165,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = ExprSyntax(dictTypeExpr) case "Optional": - guard let argument = genericArgumentList.firstAndOnly, - case .type(let typeArgument) = argument else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly else { newNode = nil break } From 9cbc942f5907c569bfd29564376db184a7ef9953 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 28 Oct 2024 13:43:39 -0700 Subject: [PATCH 284/332] Update UseShorthandTypeNames.swift --- .../SwiftFormat/Rules/UseShorthandTypeNames.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift index d24b32b51..35ff84507 100644 --- a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift @@ -47,7 +47,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { switch node.name.text { case "Array": - guard case .type(let typeArgument) = genericArgumentList.firstAndOnly else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly.argument else { newNode = nil break } @@ -58,7 +58,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { ) case "Dictionary": - guard case (.type(let type0Argument), .type(let type1Argument)) = exactlyTwoChildren(of: genericArgumentList) + guard let arguments = exactlyTwoChildren(of: genericArgumentList), + case (.type(let type0Argument), .type(let type1Argument)) = (arguments.0.argument, arguments.1.argument) else { newNode = nil break @@ -75,7 +76,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = nil break } - guard case .type(let typeArgument) = genericArgumentList.firstAndOnly else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly.argument else { newNode = nil break } @@ -138,7 +139,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { switch expression.baseName.text { case "Array": - guard case .type(let typeArgument) = genericArgumentList.firstAndOnly else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly.argument else { newNode = nil break } @@ -150,7 +151,8 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = ExprSyntax(arrayTypeExpr) case "Dictionary": - guard case (.type(let type0Argument), .type(let type1Argument)) = exactlyTwoChildren(of: genericArgumentList) + guard let arguments = exactlyTwoChildren(of: genericArgumentList), + case (.type(let type0Argument), .type(let type1Argument)) = (arguments.0.argument, arguments.1.argument) else { newNode = nil break @@ -165,7 +167,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = ExprSyntax(dictTypeExpr) case "Optional": - guard case .type(let typeArgument) = genericArgumentList.firstAndOnly else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly.argument else { newNode = nil break } From e6aa9ec987dc088746fcbcaa3ebd56764c32c2c9 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 4 Nov 2024 12:24:07 -0800 Subject: [PATCH 285/332] Update UseShorthandTypeNames.swift --- Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift index 35ff84507..28d048fac 100644 --- a/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift +++ b/Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift @@ -47,7 +47,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { switch node.name.text { case "Array": - guard case .type(let typeArgument) = genericArgumentList.firstAndOnly.argument else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly?.argument else { newNode = nil break } @@ -76,7 +76,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = nil break } - guard case .type(let typeArgument) = genericArgumentList.firstAndOnly.argument else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly?.argument else { newNode = nil break } @@ -139,7 +139,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { switch expression.baseName.text { case "Array": - guard case .type(let typeArgument) = genericArgumentList.firstAndOnly.argument else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly?.argument else { newNode = nil break } @@ -167,7 +167,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule { newNode = ExprSyntax(dictTypeExpr) case "Optional": - guard case .type(let typeArgument) = genericArgumentList.firstAndOnly.argument else { + guard case .type(let typeArgument) = genericArgumentList.firstAndOnly?.argument else { newNode = nil break } From 211884fde1c0eea428795bd5477aebb00744744b Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 30 Oct 2024 18:21:41 -0700 Subject: [PATCH 286/332] Fix tests when building swift-format using Swift 6.0.2 When traversing the file URL with Foundation from Swift 6.0.2, you get the following components - `["/", "C:", "test.swift"]` - `["/", "C:"]` - `[]` The component count never reaches 1. Foundation from Swift 6.1 goes - `["/", "C:", "test.swift"]` - `["/", "C:"]` - `["/"]` Cover both cases by checking for `<= 1` instead of `== 1` --- Sources/SwiftFormat/API/Configuration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 7cc0d3855..2586024a3 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -478,7 +478,7 @@ fileprivate extension URL { #if os(Windows) // FIXME: We should call into Windows' native check to check if this path is a root once https://github.com/swiftlang/swift-foundation/issues/976 is fixed. // https://github.com/swiftlang/swift-format/issues/844 - return self.pathComponents.count == 1 + return self.pathComponents.count <= 1 #else // On Linux, we may end up with an string for the path due to https://github.com/swiftlang/swift-foundation/issues/980 // TODO: Remove the check for "" once https://github.com/swiftlang/swift-foundation/issues/980 is fixed. From 5e4caa81d0dcd1c60a9c44bb3e08b02ef11ee546 Mon Sep 17 00:00:00 2001 From: danthorpe Date: Wed, 10 Apr 2024 14:05:01 +0100 Subject: [PATCH 287/332] feat: add pre-commit hooks --- .pre-commit-hooks.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .pre-commit-hooks.yaml diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 000000000..f7fd11fa5 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,6 @@ +- id: swift-format + name: swift-format + entry: swift-format format --in-place --recursive --parallel + language: swift + types: [swift] + require_serial: true From fee42c92b1c1502f80e7494ae3af9cf26f708d23 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Fri, 8 Nov 2024 09:47:49 -0500 Subject: [PATCH 288/332] Add `--enable-experimental-feature` to enable those features in the parser. Also add a couple small tests for value generics to exercise the capability in tests. Fixes #875. --- .../SwiftFormat/API/SwiftFormatError.swift | 22 ++++++++- Sources/SwiftFormat/API/SwiftFormatter.swift | 6 +++ Sources/SwiftFormat/API/SwiftLinter.swift | 6 +++ Sources/SwiftFormat/Core/Parsing.swift | 24 ++++++++-- Sources/_SwiftFormatTestSupport/Parsing.swift | 37 +++++++++++++++ .../Frontend/FormatFrontend.swift | 13 ++---- .../swift-format/Frontend/LintFrontend.swift | 16 ++----- .../Subcommands/LintFormatOptions.swift | 9 ++++ .../PrettyPrint/PrettyPrintTestCase.swift | 14 +++++- .../PrettyPrint/ValueGenericsTests.swift | 46 +++++++++++++++++++ .../Rules/LintOrFormatRuleTestCase.swift | 14 ++++-- 11 files changed, 176 insertions(+), 31 deletions(-) create mode 100644 Sources/_SwiftFormatTestSupport/Parsing.swift create mode 100644 Tests/SwiftFormatTests/PrettyPrint/ValueGenericsTests.swift diff --git a/Sources/SwiftFormat/API/SwiftFormatError.swift b/Sources/SwiftFormat/API/SwiftFormatError.swift index 2e3a890cc..6e4183162 100644 --- a/Sources/SwiftFormat/API/SwiftFormatError.swift +++ b/Sources/SwiftFormat/API/SwiftFormatError.swift @@ -10,10 +10,11 @@ // //===----------------------------------------------------------------------===// +import Foundation import SwiftSyntax /// Errors that can be thrown by the `SwiftFormatter` and `SwiftLinter` APIs. -public enum SwiftFormatError: Error { +public enum SwiftFormatError: LocalizedError { /// The requested file was not readable or it did not exist. case fileNotReadable @@ -23,4 +24,23 @@ public enum SwiftFormatError: Error { /// The file contains invalid or unrecognized Swift syntax and cannot be handled safely. case fileContainsInvalidSyntax + + /// The requested experimental feature name was not recognized by the parser. + case unrecognizedExperimentalFeature(String) + + public var errorDescription: String? { + switch self { + case .fileNotReadable: + return "file is not readable or does not exist" + case .isDirectory: + return "requested path is a directory, not a file" + case .fileContainsInvalidSyntax: + return "file contains invalid Swift syntax" + case .unrecognizedExperimentalFeature(let name): + return "experimental feature '\(name)' was not recognized by the Swift parser" + } + } } + +extension SwiftFormatError: Equatable {} +extension SwiftFormatError: Hashable {} diff --git a/Sources/SwiftFormat/API/SwiftFormatter.swift b/Sources/SwiftFormat/API/SwiftFormatter.swift index 0daeb22c6..ddd0bbe2c 100644 --- a/Sources/SwiftFormat/API/SwiftFormatter.swift +++ b/Sources/SwiftFormat/API/SwiftFormatter.swift @@ -89,6 +89,10 @@ public final class SwiftFormatter { /// which is associated with any diagnostics emitted during formatting. If this is nil, a /// dummy value will be used. /// - selection: The ranges to format + /// - experimentalFeatures: The set of experimental features that should be enabled in the + /// parser. These names must be from the set of parser-recognized experimental language + /// features in `SwiftParser`'s `Parser.ExperimentalFeatures` enum, which match the spelling + /// defined in the compiler's `Features.def` file. /// - outputStream: A value conforming to `TextOutputStream` to which the formatted output will /// be written. /// - parsingDiagnosticHandler: An optional callback that will be notified if there are any @@ -98,6 +102,7 @@ public final class SwiftFormatter { source: String, assumingFileURL url: URL?, selection: Selection, + experimentalFeatures: Set = [], to outputStream: inout Output, parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil ) throws { @@ -110,6 +115,7 @@ public final class SwiftFormatter { source: source, operatorTable: .standardOperators, assumingFileURL: url, + experimentalFeatures: experimentalFeatures, parsingDiagnosticHandler: parsingDiagnosticHandler ) try format( diff --git a/Sources/SwiftFormat/API/SwiftLinter.swift b/Sources/SwiftFormat/API/SwiftLinter.swift index 25cfa2af1..cfc4639f6 100644 --- a/Sources/SwiftFormat/API/SwiftLinter.swift +++ b/Sources/SwiftFormat/API/SwiftLinter.swift @@ -81,12 +81,17 @@ public final class SwiftLinter { /// - Parameters: /// - source: The Swift source code to be linted. /// - url: A file URL denoting the filename/path that should be assumed for this source code. + /// - experimentalFeatures: The set of experimental features that should be enabled in the + /// parser. These names must be from the set of parser-recognized experimental language + /// features in `SwiftParser`'s `Parser.ExperimentalFeatures` enum, which match the spelling + /// defined in the compiler's `Features.def` file. /// - parsingDiagnosticHandler: An optional callback that will be notified if there are any /// errors when parsing the source code. /// - Throws: If an unrecoverable error occurs when formatting the code. public func lint( source: String, assumingFileURL url: URL, + experimentalFeatures: Set = [], parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil ) throws { // If the file or input string is completely empty, do nothing. This prevents even a trailing @@ -98,6 +103,7 @@ public final class SwiftLinter { source: source, operatorTable: .standardOperators, assumingFileURL: url, + experimentalFeatures: experimentalFeatures, parsingDiagnosticHandler: parsingDiagnosticHandler ) try lint( diff --git a/Sources/SwiftFormat/Core/Parsing.swift b/Sources/SwiftFormat/Core/Parsing.swift index da57a017f..09b20ef0a 100644 --- a/Sources/SwiftFormat/Core/Parsing.swift +++ b/Sources/SwiftFormat/Core/Parsing.swift @@ -13,7 +13,7 @@ import Foundation import SwiftDiagnostics import SwiftOperators -import SwiftParser +@_spi(ExperimentalLanguageFeatures) import SwiftParser import SwiftParserDiagnostics import SwiftSyntax @@ -25,10 +25,14 @@ import SwiftSyntax /// /// - Parameters: /// - source: The Swift source code to be formatted. +/// - operatorTable: The operator table to use for sequence folding. /// - url: A file URL denoting the filename/path that should be assumed for this syntax tree, /// which is associated with any diagnostics emitted during formatting. If this is nil, a /// dummy value will be used. -/// - operatorTable: The operator table to use for sequence folding. +/// - experimentalFeatures: The set of experimental features that should be enabled in the parser. +/// These names must be from the set of parser-recognized experimental language features in +/// `SwiftParser`'s `Parser.ExperimentalFeatures` enum, which match the spelling defined in the +/// compiler's `Features.def` file. /// - parsingDiagnosticHandler: An optional callback that will be notified if there are any /// errors when parsing the source code. /// - Throws: If an unrecoverable error occurs when formatting the code. @@ -36,11 +40,21 @@ func parseAndEmitDiagnostics( source: String, operatorTable: OperatorTable, assumingFileURL url: URL?, + experimentalFeatures: Set, parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil ) throws -> SourceFileSyntax { - let sourceFile = - operatorTable.foldAll(Parser.parse(source: source)) { _ in }.as(SourceFileSyntax.self)! - + var experimentalFeaturesSet: Parser.ExperimentalFeatures = [] + for featureName in experimentalFeatures { + guard let featureValue = Parser.ExperimentalFeatures(name: featureName) else { + throw SwiftFormatError.unrecognizedExperimentalFeature(featureName) + } + experimentalFeaturesSet.formUnion(featureValue) + } + var source = source + let sourceFile = source.withUTF8 { sourceBytes in + operatorTable.foldAll(Parser.parse(source: sourceBytes, experimentalFeatures: experimentalFeaturesSet)) { _ in } + .as(SourceFileSyntax.self)! + } let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: sourceFile) var hasErrors = false if let parsingDiagnosticHandler = parsingDiagnosticHandler { diff --git a/Sources/_SwiftFormatTestSupport/Parsing.swift b/Sources/_SwiftFormatTestSupport/Parsing.swift new file mode 100644 index 000000000..71f1a64d4 --- /dev/null +++ b/Sources/_SwiftFormatTestSupport/Parsing.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +@_spi(ExperimentalLanguageFeatures) import SwiftParser +import SwiftSyntax +import XCTest + +extension Parser { + /// Parses the given source string and returns the corresponding `SourceFileSyntax` node. + /// + /// - Parameters: + /// - source: The source text to parse. + /// - experimentalFeatures: The set of experimental features that should be enabled in the + /// parser. + @_spi(Testing) + public static func parse( + source: String, + experimentalFeatures: Parser.ExperimentalFeatures + ) -> SourceFileSyntax { + var source = source + return source.withUTF8 { sourceBytes in + parse( + source: sourceBytes, + experimentalFeatures: experimentalFeatures + ) + } + } +} diff --git a/Sources/swift-format/Frontend/FormatFrontend.swift b/Sources/swift-format/Frontend/FormatFrontend.swift index 850086d61..23d127719 100644 --- a/Sources/swift-format/Frontend/FormatFrontend.swift +++ b/Sources/swift-format/Frontend/FormatFrontend.swift @@ -56,6 +56,7 @@ class FormatFrontend: Frontend { source: source, assumingFileURL: url, selection: fileToProcess.selection, + experimentalFeatures: Set(lintFormatOptions.experimentalFeatures), to: &buffer, parsingDiagnosticHandler: diagnosticHandler ) @@ -69,15 +70,11 @@ class FormatFrontend: Frontend { source: source, assumingFileURL: url, selection: fileToProcess.selection, + experimentalFeatures: Set(lintFormatOptions.experimentalFeatures), to: &stdoutStream, parsingDiagnosticHandler: diagnosticHandler ) } - } catch SwiftFormatError.fileNotReadable { - diagnosticsEngine.emitError( - "Unable to format \(url.relativePath): file is not readable or does not exist." - ) - return } catch SwiftFormatError.fileContainsInvalidSyntax { guard !lintFormatOptions.ignoreUnparsableFiles else { guard !inPlace else { @@ -87,10 +84,10 @@ class FormatFrontend: Frontend { stdoutStream.write(source) return } - // Otherwise, relevant diagnostics about the problematic nodes have been emitted. - return + // Otherwise, relevant diagnostics about the problematic nodes have already been emitted; we + // don't need to print anything else. } catch { - diagnosticsEngine.emitError("Unable to format \(url.relativePath): \(error)") + diagnosticsEngine.emitError("Unable to format \(url.relativePath): \(error.localizedDescription).") } } } diff --git a/Sources/swift-format/Frontend/LintFrontend.swift b/Sources/swift-format/Frontend/LintFrontend.swift index f7c4ee52c..c231266a7 100644 --- a/Sources/swift-format/Frontend/LintFrontend.swift +++ b/Sources/swift-format/Frontend/LintFrontend.swift @@ -35,7 +35,8 @@ class LintFrontend: Frontend { do { try linter.lint( source: source, - assumingFileURL: url + assumingFileURL: url, + experimentalFeatures: Set(lintFormatOptions.experimentalFeatures) ) { (diagnostic, location) in guard !self.lintFormatOptions.ignoreUnparsableFiles else { // No diagnostics should be emitted in this mode. @@ -43,22 +44,15 @@ class LintFrontend: Frontend { } self.diagnosticsEngine.consumeParserDiagnostic(diagnostic, location) } - - } catch SwiftFormatError.fileNotReadable { - diagnosticsEngine.emitError( - "Unable to lint \(url.relativePath): file is not readable or does not exist." - ) - return } catch SwiftFormatError.fileContainsInvalidSyntax { guard !lintFormatOptions.ignoreUnparsableFiles else { // The caller wants to silently ignore this error. return } - // Otherwise, relevant diagnostics about the problematic nodes have been emitted. - return + // Otherwise, relevant diagnostics about the problematic nodes have already been emitted; we + // don't need to print anything else. } catch { - diagnosticsEngine.emitError("Unable to lint \(url.relativePath): \(error)") - return + diagnosticsEngine.emitError("Unable to lint \(url.relativePath): \(error.localizedDescription).") } } } diff --git a/Sources/swift-format/Subcommands/LintFormatOptions.swift b/Sources/swift-format/Subcommands/LintFormatOptions.swift index bd15dc4e7..520eaf970 100644 --- a/Sources/swift-format/Subcommands/LintFormatOptions.swift +++ b/Sources/swift-format/Subcommands/LintFormatOptions.swift @@ -98,6 +98,15 @@ struct LintFormatOptions: ParsableArguments { ) var followSymlinks: Bool = false + @Option( + name: .customLong("enable-experimental-feature"), + help: """ + The name of an experimental swift-syntax parser feature that should be enabled by \ + swift-format. Multiple features can be enabled by specifying this flag multiple times. + """ + ) + var experimentalFeatures: [String] = [] + /// The list of paths to Swift source files that should be formatted or linted. @Argument(help: "Zero or more input filenames.") var paths: [String] = [] diff --git a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift index abe0400b7..375e5fd7e 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/PrettyPrintTestCase.swift @@ -1,7 +1,7 @@ import SwiftFormat @_spi(Rules) @_spi(Testing) import SwiftFormat import SwiftOperators -import SwiftParser +@_spi(ExperimentalLanguageFeatures) import SwiftParser import SwiftSyntax import XCTest @_spi(Testing) import _SwiftFormatTestSupport @@ -18,6 +18,8 @@ class PrettyPrintTestCase: DiagnosingTestCase { /// changes that insert or remove non-whitespace characters (like trailing commas). /// - findings: A list of `FindingSpec` values that describe the findings that are expected to /// be emitted. These are currently only checked if `whitespaceOnly` is true. + /// - experimentalFeatures: The set of experimental features that should be enabled in the + /// parser. /// - file: The file in which failure occurred. Defaults to the file name of the test case in /// which this function was called. /// - line: The line number on which failure occurred. Defaults to the line number on which this @@ -29,6 +31,7 @@ class PrettyPrintTestCase: DiagnosingTestCase { configuration: Configuration = Configuration.forTesting, whitespaceOnly: Bool = false, findings: [FindingSpec] = [], + experimentalFeatures: Parser.ExperimentalFeatures = [], file: StaticString = #file, line: UInt = #line ) { @@ -44,6 +47,7 @@ class PrettyPrintTestCase: DiagnosingTestCase { configuration: configuration, selection: markedInput.selection, whitespaceOnly: whitespaceOnly, + experimentalFeatures: experimentalFeatures, findingConsumer: { emittedFindings.append($0) } ) assertStringsEqualWithDiff( @@ -76,6 +80,7 @@ class PrettyPrintTestCase: DiagnosingTestCase { configuration: configuration, selection: markedInput.selection, whitespaceOnly: whitespaceOnly, + experimentalFeatures: experimentalFeatures, findingConsumer: { _ in } // Ignore findings during the idempotence check. ) assertStringsEqualWithDiff( @@ -95,6 +100,8 @@ class PrettyPrintTestCase: DiagnosingTestCase { /// - configuration: The formatter configuration. /// - whitespaceOnly: If true, the pretty printer should only apply whitespace changes and omit /// changes that insert or remove non-whitespace characters (like trailing commas). + /// - experimentalFeatures: The set of experimental features that should be enabled in the + /// parser. /// - findingConsumer: A function called for each finding that is emitted by the pretty printer. /// - Returns: The pretty-printed text, or nil if an error occurred and a test failure was logged. private func prettyPrintedSource( @@ -102,11 +109,14 @@ class PrettyPrintTestCase: DiagnosingTestCase { configuration: Configuration, selection: Selection, whitespaceOnly: Bool, + experimentalFeatures: Parser.ExperimentalFeatures = [], findingConsumer: @escaping (Finding) -> Void ) -> (String, Context) { // Ignore folding errors for unrecognized operators so that we fallback to a reasonable default. let sourceFileSyntax = - OperatorTable.standardOperators.foldAll(Parser.parse(source: source)) { _ in } + OperatorTable.standardOperators.foldAll( + Parser.parse(source: source, experimentalFeatures: experimentalFeatures) + ) { _ in } .as(SourceFileSyntax.self)! let context = makeContext( sourceFileSyntax: sourceFileSyntax, diff --git a/Tests/SwiftFormatTests/PrettyPrint/ValueGenericsTests.swift b/Tests/SwiftFormatTests/PrettyPrint/ValueGenericsTests.swift new file mode 100644 index 000000000..54dec83ab --- /dev/null +++ b/Tests/SwiftFormatTests/PrettyPrint/ValueGenericsTests.swift @@ -0,0 +1,46 @@ +@_spi(ExperimentalLanguageFeatures) import SwiftParser + +final class ValueGenericsTests: PrettyPrintTestCase { + func testValueGenericDeclaration() { + let input = "struct Foo { static let bar = n }" + let expected = """ + struct Foo< + let n: Int + > { + static let bar = n + } + + """ + assertPrettyPrintEqual( + input: input, + expected: expected, + linelength: 20, + experimentalFeatures: [.valueGenerics] + ) + } + + func testValueGenericTypeUsage() { + let input = + """ + let v1: Vector<100, Int> + let v2 = Vector<100, Int>() + """ + let expected = """ + let v1: + Vector< + 100, Int + > + let v2 = + Vector< + 100, Int + >() + + """ + assertPrettyPrintEqual( + input: input, + expected: expected, + linelength: 15, + experimentalFeatures: [.valueGenerics] + ) + } +} diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index 9ae0050e0..dc69fbef6 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -1,7 +1,7 @@ import SwiftFormat @_spi(Rules) @_spi(Testing) import SwiftFormat import SwiftOperators -import SwiftParser +@_spi(ExperimentalLanguageFeatures) import SwiftParser import SwiftSyntax import XCTest @_spi(Testing) import _SwiftFormatTestSupport @@ -16,18 +16,21 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { /// where findings are expected to be emitted. /// - findings: A list of `FindingSpec` values that describe the findings that are expected to /// be emitted. + /// - experimentalFeatures: The set of experimental features that should be enabled in the + /// parser. /// - file: The file the test resides in (defaults to the current caller's file). /// - line: The line the test resides in (defaults to the current caller's line). final func assertLint( _ type: LintRule.Type, _ markedSource: String, findings: [FindingSpec] = [], + experimentalFeatures: Parser.ExperimentalFeatures = [], file: StaticString = #file, line: UInt = #line ) { let markedText = MarkedText(textWithMarkers: markedSource) let unmarkedSource = markedText.textWithoutMarkers - let tree = Parser.parse(source: unmarkedSource) + let tree = Parser.parse(source: unmarkedSource, experimentalFeatures: experimentalFeatures) let sourceFileSyntax = try! OperatorTable.standardOperators.foldAll(tree).as(SourceFileSyntax.self)! @@ -80,6 +83,8 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { /// - findings: A list of `FindingSpec` values that describe the findings that are expected to /// be emitted. /// - configuration: The configuration to use when formatting (or nil to use the default). + /// - experimentalFeatures: The set of experimental features that should be enabled in the + /// parser. /// - file: The file the test resides in (defaults to the current caller's file) /// - line: The line the test resides in (defaults to the current caller's line) final func assertFormatting( @@ -88,12 +93,13 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { expected: String, findings: [FindingSpec] = [], configuration: Configuration? = nil, + experimentalFeatures: Parser.ExperimentalFeatures = [], file: StaticString = #file, line: UInt = #line ) { let markedInput = MarkedText(textWithMarkers: input) let originalSource: String = markedInput.textWithoutMarkers - let tree = Parser.parse(source: originalSource) + let tree = Parser.parse(source: originalSource, experimentalFeatures: experimentalFeatures) let sourceFileSyntax = try! OperatorTable.standardOperators.foldAll(tree).as(SourceFileSyntax.self)! @@ -134,7 +140,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { printTokenStream: false, whitespaceOnly: false ).prettyPrint() - let prettyPrintedTree = Parser.parse(source: prettyPrintedSource) + let prettyPrintedTree = Parser.parse(source: prettyPrintedSource, experimentalFeatures: experimentalFeatures) XCTAssertEqual( whitespaceInsensitiveText(of: actual), whitespaceInsensitiveText(of: prettyPrintedTree), From 8c68ec3e98376cf89ea4a1126e993d6f6e7c351e Mon Sep 17 00:00:00 2001 From: TTOzzi Date: Sat, 16 Nov 2024 08:00:27 +0900 Subject: [PATCH 289/332] Add indentBlankLines configuration --- Documentation/Configuration.md | 5 + .../API/Configuration+Default.swift | 1 + Sources/SwiftFormat/API/Configuration.swift | 14 + .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 6 +- .../PrettyPrint/PrettyPrintBuffer.swift | 15 +- .../PrettyPrint/TokenStreamCreator.swift | 7 + .../Configuration+Testing.swift | 1 + .../PrettyPrint/IndentBlankLinesTests.swift | 294 ++++++++++++++++++ 8 files changed, 339 insertions(+), 4 deletions(-) create mode 100644 Tests/SwiftFormatTests/PrettyPrint/IndentBlankLinesTests.swift diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index d31f98a02..14072d84f 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -94,6 +94,11 @@ top-level keys and values: * `multiElementCollectionTrailingCommas` _(boolean)_: Determines whether multi-element collection literals should have trailing commas. Defaults to `true`. + +* `indentBlankLines` _(boolean)_: Determines whether blank lines should be modified + to match the current indentation. When this setting is true, blank lines will be modified + to match the indentation level, adding indentation whether or not there is existing whitespace. + When false (the default), all whitespace in blank lines will be completely removed. > TODO: Add support for enabling/disabling specific syntax transformations in > the pipeline. diff --git a/Sources/SwiftFormat/API/Configuration+Default.swift b/Sources/SwiftFormat/API/Configuration+Default.swift index d18164f39..1af06a121 100644 --- a/Sources/SwiftFormat/API/Configuration+Default.swift +++ b/Sources/SwiftFormat/API/Configuration+Default.swift @@ -41,5 +41,6 @@ extension Configuration { self.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() self.multiElementCollectionTrailingCommas = true self.reflowMultilineStringLiterals = .never + self.indentBlankLines = false } } diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 2586024a3..20b491a07 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -46,6 +46,7 @@ public struct Configuration: Codable, Equatable { case noAssignmentInExpressions case multiElementCollectionTrailingCommas case reflowMultilineStringLiterals + case indentBlankLines } /// A dictionary containing the default enabled/disabled states of rules, keyed by the rules' @@ -260,6 +261,13 @@ public struct Configuration: Codable, Equatable { public var reflowMultilineStringLiterals: MultilineStringReflowBehavior + /// Determines whether to add indentation whitespace to blank lines or remove it entirely. + /// + /// If true, blank lines will be modified to match the current indentation level: + /// if they contain whitespace, the existing whitespace will be adjusted, and if they are empty, spaces will be added to match the indentation. + /// If false (the default), the whitespace in blank lines will be removed entirely. + public var indentBlankLines: Bool + /// Creates a new `Configuration` by loading it from a configuration file. public init(contentsOf url: URL) throws { let data = try Data(contentsOf: url) @@ -368,6 +376,12 @@ public struct Configuration: Codable, Equatable { self.reflowMultilineStringLiterals = try container.decodeIfPresent(MultilineStringReflowBehavior.self, forKey: .reflowMultilineStringLiterals) ?? defaults.reflowMultilineStringLiterals + self.indentBlankLines = + try container.decodeIfPresent( + Bool.self, + forKey: .indentBlankLines + ) + ?? defaults.indentBlankLines // If the `rules` key is not present at all, default it to the built-in set // so that the behavior is the same as if the configuration had been diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index f9e9246e5..607364ee8 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -441,7 +441,7 @@ public class PrettyPrinter { outputBuffer.enqueueSpaces(size) outputBuffer.write("\\") } - outputBuffer.writeNewlines(newline) + outputBuffer.writeNewlines(newline, shouldIndentBlankLines: configuration.indentBlankLines) lastBreak = true } else { if outputBuffer.isAtStartOfLine { @@ -458,6 +458,10 @@ public class PrettyPrinter { // Print out the number of spaces according to the size, and adjust spaceRemaining. case .space(let size, _): + if configuration.indentBlankLines, outputBuffer.isAtStartOfLine { + // An empty string write is needed to add line-leading indentation that matches the current indentation on a line that contains only whitespaces. + outputBuffer.write("") + } outputBuffer.enqueueSpaces(size) // Print any indentation required, followed by the text content of the syntax token. diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift index 6c3402a00..af9d02f10 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift @@ -70,8 +70,11 @@ struct PrettyPrintBuffer { /// subtract the previously written newlines during the second call so that we end up with the /// correct number overall. /// - /// - Parameter newlines: The number and type of newlines to write. - mutating func writeNewlines(_ newlines: NewlineBehavior) { + /// - Parameters: + /// - newlines: The number and type of newlines to write. + /// - shouldIndentBlankLines: A Boolean value indicating whether to insert spaces + /// for blank lines based on the current indentation level. + mutating func writeNewlines(_ newlines: NewlineBehavior, shouldIndentBlankLines: Bool) { let numberToPrint: Int switch newlines { case .elective: @@ -86,7 +89,13 @@ struct PrettyPrintBuffer { } guard numberToPrint > 0 else { return } - writeRaw(String(repeating: "\n", count: numberToPrint)) + for number in 0..= 1 { + writeRaw(currentIndentation.indentation()) + } + writeRaw("\n") + } + lineNumber += numberToPrint isAtStartOfLine = true consecutiveNewlineCount += numberToPrint diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 3832d6874..03da7d4b4 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -3521,6 +3521,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { leadingIndent = nil case .newlines(let count), .carriageReturns(let count), .carriageReturnLineFeeds(let count): + if config.indentBlankLines, + let leadingIndent, leadingIndent.count > 0 + { + requiresNextNewline = true + } + leadingIndent = .spaces(0) guard !isStartOfFile else { break } @@ -3557,6 +3563,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { case .spaces(let n): guard leadingIndent == .spaces(0) else { break } leadingIndent = .spaces(n) + case .tabs(let n): guard leadingIndent == .spaces(0) else { break } leadingIndent = .tabs(n) diff --git a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift index 8d095767b..a3c593726 100644 --- a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift +++ b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift @@ -41,6 +41,7 @@ extension Configuration { config.spacesAroundRangeFormationOperators = false config.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() config.multiElementCollectionTrailingCommas = true + config.indentBlankLines = false return config } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/IndentBlankLinesTests.swift b/Tests/SwiftFormatTests/PrettyPrint/IndentBlankLinesTests.swift new file mode 100644 index 000000000..31f8a1190 --- /dev/null +++ b/Tests/SwiftFormatTests/PrettyPrint/IndentBlankLinesTests.swift @@ -0,0 +1,294 @@ +import SwiftFormat + +final class IndentBlankLinesTests: PrettyPrintTestCase { + func testIndentBlankLinesEnabled() { + let input = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + """ + + let expected = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + + """ + var config = Configuration.forTesting + config.indentBlankLines = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } + + func testIndentBlankLinesDisabled() { + let input = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + """ + + let expected = + """ + class A { + func foo() -> Int { + return 1 + } + + func bar() -> Int { + return 2 + } + } + + """ + var config = Configuration.forTesting + config.indentBlankLines = false + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } + + func testLineWithMoreWhitespacesThanIndentation() { + let input = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020}\u{0020}\u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + """ + + let expected = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + + """ + var config = Configuration.forTesting + config.indentBlankLines = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } + + func testLineWithFewerWhitespacesThanIndentation() { + let input = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020} + func bar() -> Int { + return 2 + } + } + """ + + let expected = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + + """ + var config = Configuration.forTesting + config.indentBlankLines = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } + + func testLineWithoutWhitespace() { + let input = + """ + class A { + func foo() -> Int { + return 1 + } + + func bar() -> Int { + return 2 + } + } + """ + + let expected = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + + """ + var config = Configuration.forTesting + config.indentBlankLines = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } + + func testConsecutiveLinesWithMoreWhitespacesThanIndentation() { + let input = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020}\u{0020}\u{0020}\u{0020} + \u{0020}\u{0020}\u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + """ + + let expected = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + + """ + var config = Configuration.forTesting + config.indentBlankLines = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } + + func testConsecutiveLinesWithFewerWhitespacesThanIndentation() { + let input = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020} + + func bar() -> Int { + return 2 + } + } + """ + + let expected = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + + """ + var config = Configuration.forTesting + config.indentBlankLines = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } + + func testConsecutiveLinesWithoutWhitespace() { + let input = + """ + class A { + func foo() -> Int { + return 1 + } + + + func bar() -> Int { + return 2 + } + } + """ + + let expected = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + + """ + var config = Configuration.forTesting + config.indentBlankLines = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } + + func testExpressionsWithUnnecessaryWhitespaces() { + let input = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + """ + + let expected = + """ + class A { + func foo() -> Int { + return 1 + } + \u{0020}\u{0020} + func bar() -> Int { + return 2 + } + } + + """ + var config = Configuration.forTesting + config.indentBlankLines = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } +} From 2cd032cdac94c41bdae8175ea623e3c8250ce47a Mon Sep 17 00:00:00 2001 From: Bernd Kolb Date: Sat, 23 Nov 2024 20:18:34 +0100 Subject: [PATCH 290/332] PrettyPrinter reports wrong line LineNumbersTests The reason for the wrong line number were multiline comments. In to accomodate for this, we now check the string while writing for new lines and increment the line count accordingly. Issue: #882 --- .../PrettyPrint/PrettyPrintBuffer.swift | 15 +++- .../PrettyPrint/LineNumbersTests.swift | 84 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 Tests/SwiftFormatTests/PrettyPrint/LineNumbersTests.swift diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift index af9d02f10..4b02d372e 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift @@ -118,7 +118,20 @@ struct PrettyPrintBuffer { writeRaw(text) consecutiveNewlineCount = 0 pendingSpaces = 0 - column += text.count + + // In case of comments, we may get a multi-line string. + // To account for that case, we need to correct the lineNumber count. + // The new column is only the position within the last line. + let lines = text.split(separator: "\n") + lineNumber += lines.count - 1 + if lines.count > 1 { + // in case we have inserted new lines, we need to reset the column + column = lines.last?.count ?? 0 + } else { + // in case it is an end of line comment or a single line comment, + // we just add to the current column + column += lines.last?.count ?? 0 + } } /// Request that the given number of spaces be printed out before the next text token. diff --git a/Tests/SwiftFormatTests/PrettyPrint/LineNumbersTests.swift b/Tests/SwiftFormatTests/PrettyPrint/LineNumbersTests.swift new file mode 100644 index 000000000..1bb58f7ab --- /dev/null +++ b/Tests/SwiftFormatTests/PrettyPrint/LineNumbersTests.swift @@ -0,0 +1,84 @@ +import SwiftFormat +import _SwiftFormatTestSupport + +final class LineNumbersTests: PrettyPrintTestCase { + func testLineNumbers() { + let input = + """ + final class A { + @Test func b() throws { + doSomethingInAFunctionWithAVeryLongName() 1️⃣// Here we have a very long comment that should not be here because it is far too long + } + } + """ + + let expected = + """ + final class A { + @Test func b() throws { + doSomethingInAFunctionWithAVeryLongName() // Here we have a very long comment that should not be here because it is far too long + } + } + + """ + + assertPrettyPrintEqual( + input: input, + expected: expected, + linelength: 120, + whitespaceOnly: true, + findings: [ + FindingSpec("1️⃣", message: "move end-of-line comment that exceeds the line length") + ] + ) + } + + func testLineNumbersWithComments() { + let input = + """ + // Copyright (C) 2024 My Coorp. All rights reserved. + // + // This document is the property of My Coorp. + // It is considered confidential and proprietary. + // + // This document may not be reproduced or transmitted in any form, + // in whole or in part, without the express written permission of + // My Coorp. + + final class A { + @Test func b() throws { + doSomethingInAFunctionWithAVeryLongName() 1️⃣// Here we have a very long comment that should not be here because it is far too long + } + } + """ + + let expected = + """ + // Copyright (C) 2024 My Coorp. All rights reserved. + // + // This document is the property of My Coorp. + // It is considered confidential and proprietary. + // + // This document may not be reproduced or transmitted in any form, + // in whole or in part, without the express written permission of + // My Coorp. + + final class A { + @Test func b() throws { + doSomethingInAFunctionWithAVeryLongName() // Here we have a very long comment that should not be here because it is far too long + } + } + + """ + + assertPrettyPrintEqual( + input: input, + expected: expected, + linelength: 120, + whitespaceOnly: true, + findings: [ + FindingSpec("1️⃣", message: "move end-of-line comment that exceeds the line length") + ] + ) + } +} From 832d1bcbb5d080626ba864e62d3a43888b479ead Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 4 Dec 2024 10:21:54 -0800 Subject: [PATCH 291/332] [6.1] Update related dependencies to release/6.1 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 228b13f5f..9c61904d0 100644 --- a/Package.swift +++ b/Package.swift @@ -198,7 +198,7 @@ var dependencies: [Package.Dependency] { return [ .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2"), .package(url: "https://github.com/apple/swift-markdown.git", from: "0.2.0"), - .package(url: "https://github.com/swiftlang/swift-syntax.git", branch: "main"), + .package(url: "https://github.com/swiftlang/swift-syntax.git", branch: "release/6.1"), ] } } From 8fec655e4b91689d895533fe0433e9fb29102bc6 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 4 Dec 2024 10:35:07 -0800 Subject: [PATCH 292/332] Use dockerless Windows jobs Dockerless Windows is 5-10 minutes faster than Docker on Windows --- .github/workflows/pull_request.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d49113a87..5a9f75434 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -8,6 +8,8 @@ jobs: tests: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + with: + enable_windows_docker: false # Dockerless Windows is 5-10 minutes faster than Docker on Windows soundness: name: Soundness uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main From 23709e812cf252cd99157433a32a363bc3899c7c Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 4 Dec 2024 16:13:08 -0800 Subject: [PATCH 293/332] Fix issue in publish_release pipeline testing swift-format in debug configuration --- .github/workflows/publish_release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 03c4f04f5..a360c3f97 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -116,8 +116,8 @@ jobs: # fatal: empty ident name (for <>) not allowed cmd /c "type $env:TEMP\patch.diff | git am || (exit /b 1)" # We require that releases of swift-format build without warnings - linux_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' }} - windows_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' }} + linux_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' || '' }} + windows_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' || '' }} create_tag: name: Create Tag runs-on: ubuntu-latest From 4db71dbfb9f24ce83c02b15c426b6f39405739eb Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 4 Dec 2024 16:14:11 -0800 Subject: [PATCH 294/332] Run Windows tests dockerless --- .github/workflows/publish_release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index a360c3f97..5c1985c7c 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -99,6 +99,7 @@ jobs: matrix: release: [true, false] with: + enable_windows_docker: false # Dockerless Windows is 5-10 minutes faster than Docker on Windows linux_pre_build_command: | git config --global --add safe.directory "$(realpath .)" git config --local user.name 'swift-ci' From 6c8eabe0ebc4bb7b2b9d96c0c55c8c744d6f0394 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 24 Oct 2024 06:59:26 -0700 Subject: [PATCH 295/332] Run Windows tests before tagging a release The GitHub workflows enabled Windows in the testing matrix. We needed to port the pre-build commands that apply the release commits to Windows to make the Windows checks pass. --- .github/workflows/create-release-commits.sh | 28 ------ .github/workflows/publish_release.yml | 99 +++++++++++++++++---- 2 files changed, 81 insertions(+), 46 deletions(-) delete mode 100755 .github/workflows/create-release-commits.sh diff --git a/.github/workflows/create-release-commits.sh b/.github/workflows/create-release-commits.sh deleted file mode 100755 index 81fa8eba9..000000000 --- a/.github/workflows/create-release-commits.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -SWIFT_SYNTAX_TAG="$1" -SWIFT_FORMAT_VERSION="$2" - -if [[ -z "$SWIFT_SYNTAX_TAG" || -z "$SWIFT_FORMAT_VERSION" ]]; then - echo "Update the Package manifest to reference a specific version of swift-syntax and embed the given version in the swift-format --version command" - echo "Usage create-release-commits.sh " - echo " SWIFT_SYNTAX_TAG: The tag of swift-syntax to depend on" - echo " SWIFT_FORMAT_VERSION: The version of swift-format that is about to be released" - exit 1 -fi - -# Without this, we can't perform git operations in GitHub actions. -git config --global --add safe.directory "$(realpath .)" - -git config --local user.name 'swift-ci' -git config --local user.email 'swift-ci@users.noreply.github.com' - -sed -E -i "s#branch: \"(main|release/[0-9]+\.[0-9]+)\"#from: \"$SWIFT_SYNTAX_TAG\"#" Package.swift -git add Package.swift -git commit -m "Change swift-syntax dependency to $SWIFT_SYNTAX_TAG" - -sed -E -i "s#print\(\".*\"\)#print\(\"$SWIFT_FORMAT_VERSION\"\)#" Sources/swift-format/PrintVersion.swift -git add Sources/swift-format/PrintVersion.swift -git commit -m "Change version to $SWIFT_FORMAT_VERSION" diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 2528ebf6f..2081696ec 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -34,12 +34,12 @@ jobs: echo "${{ github.triggering_actor }} is not allowed to create a release" exit 1 fi - define_tags: - name: Determine dependent swift-syntax version and prerelease date + create_release_commits: + name: Create release commits runs-on: ubuntu-latest outputs: - swift_syntax_tag: ${{ steps.swift_syntax_tag.outputs.swift_syntax_tag }} swift_format_version: ${{ steps.swift_format_version.outputs.swift_format_version }} + release_commit_patch: ${{ steps.create_release_commits.outputs.release_commit_patch }} steps: - name: Determine swift-syntax tag to depend on id: swift_syntax_tag @@ -65,37 +65,100 @@ jobs: fi echo "Using swift-format version: $SWIFT_FORMAT_VERSION" echo "swift_format_version=$SWIFT_FORMAT_VERSION" >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create release commits + id: create_release_commits + run: | + # Without this, we can't perform git operations in GitHub actions. + git config --global --add safe.directory "$(realpath .)" + git config --local user.name 'swift-ci' + git config --local user.email 'swift-ci@users.noreply.github.com' + + BASE_COMMIT=$(git rev-parse HEAD) + + sed -E -i "s#branch: \"(main|release/[0-9]+\.[0-9]+)\"#from: \"${{ steps.swift_syntax_tag.outputs.swift_syntax_tag }}\"#" Package.swift + git add Package.swift + git commit -m "Change swift-syntax dependency to ${{ steps.swift_syntax_tag.outputs.swift_syntax_tag }}" + + sed -E -i "s#print\(\".*\"\)#print\(\"${{ steps.swift_format_version.outputs.swift_format_version }}\"\)#" Sources/swift-format/PrintVersion.swift + git add Sources/swift-format/PrintVersion.swift + git commit -m "Change version to ${{ steps.swift_format_version.outputs.swift_format_version }}" + + { + echo 'release_commit_patch<> "$GITHUB_OUTPUT" test_debug: name: Test in Debug configuration - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main - needs: define_tags + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@windows-pre-build + needs: create_release_commits with: - pre_build_command: bash .github/workflows/create-release-commits.sh '${{ needs.define_tags.outputs.swift_syntax_tag }}' '${{ needs.define_tags.outputs.swift_format_version }}' + linux_pre_build_command: | + git config --global --add safe.directory "$(realpath .)" + git config --local user.name 'swift-ci' + git config --local user.email 'swift-ci@users.noreply.github.com' + git am << EOF + ${{ needs.create_release_commits.outputs.release_commit_patch }} + EOF + windows_pre_build_command: | + git config --local user.name "swift-ci" + git config --local user.email "swift-ci@users.noreply.github.com" + echo @" + ${{ needs.create_release_commits.outputs.release_commit_patch }} + "@ > $env:TEMP\patch.diff + # For some reason `git am` fails in Powershell with the following error. Executing it in cmd works... + # fatal: empty ident name (for <>) not allowed + cmd /c "type $env:TEMP\patch.diff | git am || (exit /b 1)" # We require that releases of swift-format build without warnings - build_command: swift test -Xswiftc -warnings-as-errors + linux_build_command: swift test -Xswiftc -warnings-as-errors + windows_build_command: swift test -Xswiftc -warnings-as-errors test_release: name: Test in Release configuration - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main - needs: define_tags + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@windows-pre-build + needs: create_release_commits with: - pre_build_command: bash .github/workflows/create-release-commits.sh '${{ needs.define_tags.outputs.swift_syntax_tag }}' '${{ needs.define_tags.outputs.swift_format_version }}' + linux_pre_build_command: | + git config --global --add safe.directory "$(realpath .)" + git config --local user.name 'swift-ci' + git config --local user.email 'swift-ci@users.noreply.github.com' + git am << EOF + ${{ needs.create_release_commits.outputs.release_commit_patch }} + EOF + windows_pre_build_command: | + git config --local user.name "swift-ci" + git config --local user.email "swift-ci@users.noreply.github.com" + echo @" + ${{ needs.create_release_commits.outputs.release_commit_patch }} + "@ > $env:TEMP\patch.diff + # For some reason `git am` fails in Powershell with the following error. Executing it in cmd works... + # fatal: empty ident name (for <>) not allowed + cmd /c "type $env:TEMP\patch.diff | git am || (exit /b 1)" # We require that releases of swift-format build without warnings - build_command: swift test -c release -Xswiftc -warnings-as-errors + linux_build_command: swift test -Xswiftc -warnings-as-errors + windows_build_command: swift test -Xswiftc -warnings-as-errors create_tag: name: Create Tag runs-on: ubuntu-latest - needs: [check_triggering_actor, test_debug, test_release, define_tags] + needs: [check_triggering_actor, test_debug, create_release_commits] permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Create release commits - run: bash .github/workflows/create-release-commits.sh '${{ needs.define_tags.outputs.swift_syntax_tag }}' '${{ needs.define_tags.outputs.swift_format_version }}' + - name: Apply release commits + run: | + git config --global --add safe.directory "$(realpath .)" + git config --local user.name 'swift-ci' + git config --local user.email 'swift-ci@users.noreply.github.com' + git am << EOF + ${{ needs.create_release_commits.outputs.release_commit_patch }} + EOF - name: Tag release run: | - git tag "${{ needs.define_tags.outputs.swift_format_version }}" - git push origin "${{ needs.define_tags.outputs.swift_format_version }}" + git tag "${{ needs.create_release_commits.outputs.swift_format_version }}" + git push origin "${{ needs.create_release_commits.outputs.swift_format_version }}" - name: Create release env: GH_TOKEN: ${{ github.token }} @@ -104,6 +167,6 @@ jobs: # Only create a release automatically for prereleases. For real releases, release notes should be crafted by hand. exit fi - gh release create "${{ needs.define_tags.outputs.swift_format_version }}" \ - --title "${{ needs.define_tags.outputs.swift_format_version }}" \ + gh release create "${{ needs.create_release_commits.outputs.swift_format_version }}" \ + --title "${{ needs.create_release_commits.outputs.swift_format_version }}" \ --prerelease From 989d0cc602b92e026eb4f182dd4104b1e3f10810 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 24 Oct 2024 08:42:44 -0700 Subject: [PATCH 296/332] Use matrix to run debug and release --- .github/workflows/publish_release.yml | 40 +++++++-------------------- 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 2081696ec..03c4f04f5 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -90,10 +90,14 @@ jobs: git format-patch "$BASE_COMMIT"..HEAD --stdout echo EOF } >> "$GITHUB_OUTPUT" - test_debug: - name: Test in Debug configuration - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@windows-pre-build + test: + name: Test in ${{ matrix.release && 'Release' || 'Debug' }} configuration + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main needs: create_release_commits + strategy: + fail-fast: false + matrix: + release: [true, false] with: linux_pre_build_command: | git config --global --add safe.directory "$(realpath .)" @@ -112,36 +116,12 @@ jobs: # fatal: empty ident name (for <>) not allowed cmd /c "type $env:TEMP\patch.diff | git am || (exit /b 1)" # We require that releases of swift-format build without warnings - linux_build_command: swift test -Xswiftc -warnings-as-errors - windows_build_command: swift test -Xswiftc -warnings-as-errors - test_release: - name: Test in Release configuration - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@windows-pre-build - needs: create_release_commits - with: - linux_pre_build_command: | - git config --global --add safe.directory "$(realpath .)" - git config --local user.name 'swift-ci' - git config --local user.email 'swift-ci@users.noreply.github.com' - git am << EOF - ${{ needs.create_release_commits.outputs.release_commit_patch }} - EOF - windows_pre_build_command: | - git config --local user.name "swift-ci" - git config --local user.email "swift-ci@users.noreply.github.com" - echo @" - ${{ needs.create_release_commits.outputs.release_commit_patch }} - "@ > $env:TEMP\patch.diff - # For some reason `git am` fails in Powershell with the following error. Executing it in cmd works... - # fatal: empty ident name (for <>) not allowed - cmd /c "type $env:TEMP\patch.diff | git am || (exit /b 1)" - # We require that releases of swift-format build without warnings - linux_build_command: swift test -Xswiftc -warnings-as-errors - windows_build_command: swift test -Xswiftc -warnings-as-errors + linux_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' }} + windows_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' }} create_tag: name: Create Tag runs-on: ubuntu-latest - needs: [check_triggering_actor, test_debug, create_release_commits] + needs: [check_triggering_actor, test, create_release_commits] permissions: contents: write steps: From 5fc28a652c1f4a2a03da67634e5621360e12d494 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 4 Dec 2024 16:13:08 -0800 Subject: [PATCH 297/332] Fix issue in publish_release pipeline testing swift-format in debug configuration --- .github/workflows/publish_release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 03c4f04f5..a360c3f97 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -116,8 +116,8 @@ jobs: # fatal: empty ident name (for <>) not allowed cmd /c "type $env:TEMP\patch.diff | git am || (exit /b 1)" # We require that releases of swift-format build without warnings - linux_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' }} - windows_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' }} + linux_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' || '' }} + windows_build_command: swift test -Xswiftc -warnings-as-errors ${{ matrix.release && '-c release' || '' }} create_tag: name: Create Tag runs-on: ubuntu-latest From 8b7970313916626ca143af8f33fbe14456639fcb Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 4 Dec 2024 16:14:11 -0800 Subject: [PATCH 298/332] Run Windows tests dockerless --- .github/workflows/publish_release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index a360c3f97..5c1985c7c 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -99,6 +99,7 @@ jobs: matrix: release: [true, false] with: + enable_windows_docker: false # Dockerless Windows is 5-10 minutes faster than Docker on Windows linux_pre_build_command: | git config --global --add safe.directory "$(realpath .)" git config --local user.name 'swift-ci' From 003208bf50d87144fc31d6f03fbb833fe790252f Mon Sep 17 00:00:00 2001 From: Josh Wisenbaker Date: Thu, 5 Dec 2024 08:04:54 -0800 Subject: [PATCH 299/332] Fix support for `FileIterator` when working directory is `/` Added an additional check that tests to see if the working directory is root. https://github.com/swiftlang/swift-format/issues/862 --- Sources/SwiftFormat/API/Configuration.swift | 14 ---- Sources/SwiftFormat/CMakeLists.txt | 3 +- .../SwiftFormat/Utilities/FileIterator.swift | 28 ++++--- .../SwiftFormat/Utilities/URL+isRoot.swift | 32 ++++++++ .../Utilities/FileIteratorTests.swift | 81 ++++++++++++++++--- 5 files changed, 121 insertions(+), 37 deletions(-) create mode 100644 Sources/SwiftFormat/Utilities/URL+isRoot.swift diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index 20b491a07..70ac916aa 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -486,17 +486,3 @@ public struct NoAssignmentInExpressionsConfiguration: Codable, Equatable { public init() {} } - -fileprivate extension URL { - var isRoot: Bool { - #if os(Windows) - // FIXME: We should call into Windows' native check to check if this path is a root once https://github.com/swiftlang/swift-foundation/issues/976 is fixed. - // https://github.com/swiftlang/swift-format/issues/844 - return self.pathComponents.count <= 1 - #else - // On Linux, we may end up with an string for the path due to https://github.com/swiftlang/swift-foundation/issues/980 - // TODO: Remove the check for "" once https://github.com/swiftlang/swift-foundation/issues/980 is fixed. - return self.path == "/" || self.path == "" - #endif - } -} diff --git a/Sources/SwiftFormat/CMakeLists.txt b/Sources/SwiftFormat/CMakeLists.txt index 62c2d4df8..46937f713 100644 --- a/Sources/SwiftFormat/CMakeLists.txt +++ b/Sources/SwiftFormat/CMakeLists.txt @@ -97,7 +97,8 @@ add_library(SwiftFormat Rules/UseTripleSlashForDocumentationComments.swift Rules/UseWhereClausesInForLoops.swift Rules/ValidateDocumentationComments.swift - Utilities/FileIterator.swift) + Utilities/FileIterator.swift + Utilities/URL+isRoot.swift) target_link_libraries(SwiftFormat PUBLIC SwiftMarkdown::Markdown SwiftSyntax::SwiftSyntax diff --git a/Sources/SwiftFormat/Utilities/FileIterator.swift b/Sources/SwiftFormat/Utilities/FileIterator.swift index 9d1a1d2cf..b0a8d2f06 100644 --- a/Sources/SwiftFormat/Utilities/FileIterator.swift +++ b/Sources/SwiftFormat/Utilities/FileIterator.swift @@ -12,6 +12,10 @@ import Foundation +#if os(Windows) +import WinSDK +#endif + /// Iterator for looping over lists of files and directories. Directories are automatically /// traversed recursively, and we check for files with a ".swift" extension. @_spi(Internal) @@ -32,7 +36,7 @@ public struct FileIterator: Sequence, IteratorProtocol { /// The current working directory of the process, which is used to relativize URLs of files found /// during iteration. - private let workingDirectory = URL(fileURLWithPath: ".") + private let workingDirectory: URL /// Keep track of the current directory we're recursing through. private var currentDirectory = URL(fileURLWithPath: "") @@ -46,8 +50,13 @@ public struct FileIterator: Sequence, IteratorProtocol { /// Create a new file iterator over the given list of file URLs. /// /// The given URLs may be files or directories. If they are directories, the iterator will recurse - /// into them. - public init(urls: [URL], followSymlinks: Bool) { + /// into them. Symlinks are never followed on Windows platforms as Foundation doesn't support it. + /// - Parameters: + /// - urls: `Array` of files or directories to iterate. + /// - followSymlinks: `Bool` to indicate if symbolic links should be followed when iterating. + /// - workingDirectory: `URL` that indicates the current working directory. Used for testing. + public init(urls: [URL], followSymlinks: Bool, workingDirectory: URL = URL(fileURLWithPath: ".")) { + self.workingDirectory = workingDirectory self.urls = urls self.urlIterator = self.urls.makeIterator() self.followSymlinks = followSymlinks @@ -144,12 +153,13 @@ public struct FileIterator: Sequence, IteratorProtocol { // if the user passes paths that are relative to the current working directory, they will // be displayed as relative paths. Otherwise, they will still be displayed as absolute // paths. - let relativePath = - path.hasPrefix(workingDirectory.path) - ? String(path.dropFirst(workingDirectory.path.count + 1)) - : path - output = - URL(fileURLWithPath: relativePath, isDirectory: false, relativeTo: workingDirectory) + let relativePath: String + if !workingDirectory.isRoot, path.hasPrefix(workingDirectory.path) { + relativePath = String(path.dropFirst(workingDirectory.path.count).drop(while: { $0 == "/" || $0 == #"\"# })) + } else { + relativePath = path + } + output = URL(fileURLWithPath: relativePath, isDirectory: false, relativeTo: workingDirectory) default: break diff --git a/Sources/SwiftFormat/Utilities/URL+isRoot.swift b/Sources/SwiftFormat/Utilities/URL+isRoot.swift new file mode 100644 index 000000000..1ad521c7f --- /dev/null +++ b/Sources/SwiftFormat/Utilities/URL+isRoot.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation + +extension URL { + @_spi(Testing) public var isRoot: Bool { + #if os(Windows) + // FIXME: We should call into Windows' native check to check if this path is a root once https://github.com/swiftlang/swift-foundation/issues/976 is fixed. + // https://github.com/swiftlang/swift-format/issues/844 + var pathComponents = self.pathComponents + if pathComponents.first == "/" { + // Canonicalize `/C:/` to `C:/`. + pathComponents = Array(pathComponents.dropFirst()) + } + return pathComponents.count <= 1 + #else + // On Linux, we may end up with an string for the path due to https://github.com/swiftlang/swift-foundation/issues/980 + // TODO: Remove the check for "" once https://github.com/swiftlang/swift-foundation/issues/980 is fixed. + return self.path == "/" || self.path == "" + #endif + } +} diff --git a/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift index ba482063d..4146ef51a 100644 --- a/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift +++ b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift @@ -1,6 +1,31 @@ -@_spi(Internal) import SwiftFormat +@_spi(Internal) @_spi(Testing) import SwiftFormat import XCTest +extension URL { + /// Assuming this is a file URL, resolves all symlinks in the path. + /// + /// - Note: We need this because `URL.resolvingSymlinksInPath()` not only resolves symlinks but also standardizes the + /// path by stripping away `private` prefixes. Since sourcekitd is not performing this standardization, using + /// `resolvingSymlinksInPath` can lead to slightly mismatched URLs between the sourcekit-lsp response and the test + /// assertion. + fileprivate var realpath: URL { + #if canImport(Darwin) + return self.path.withCString { path in + guard let realpath = Darwin.realpath(path, nil) else { + return self + } + let result = URL(fileURLWithPath: String(cString: realpath)) + free(realpath) + return result + } + #else + // Non-Darwin platforms don't have the `/private` stripping issue, so we can just use `self.resolvingSymlinksInPath` + // here. + return self.resolvingSymlinksInPath() + #endif + } +} + final class FileIteratorTests: XCTestCase { private var tmpdir: URL! @@ -10,7 +35,7 @@ final class FileIteratorTests: XCTestCase { in: .userDomainMask, appropriateFor: FileManager.default.temporaryDirectory, create: true - ) + ).realpath // Create a simple file tree used by the tests below. try touch("project/real1.swift") @@ -31,8 +56,8 @@ final class FileIteratorTests: XCTestCase { #endif let seen = allFilesSeen(iteratingOver: [tmpdir], followSymlinks: false) XCTAssertEqual(seen.count, 2) - XCTAssertTrue(seen.contains { $0.hasSuffix("project/real1.swift") }) - XCTAssertTrue(seen.contains { $0.hasSuffix("project/real2.swift") }) + XCTAssertTrue(seen.contains { $0.path.hasSuffix("project/real1.swift") }) + XCTAssertTrue(seen.contains { $0.path.hasSuffix("project/real2.swift") }) } func testFollowSymlinks() throws { @@ -41,10 +66,10 @@ final class FileIteratorTests: XCTestCase { #endif let seen = allFilesSeen(iteratingOver: [tmpdir], followSymlinks: true) XCTAssertEqual(seen.count, 3) - XCTAssertTrue(seen.contains { $0.hasSuffix("project/real1.swift") }) - XCTAssertTrue(seen.contains { $0.hasSuffix("project/real2.swift") }) + XCTAssertTrue(seen.contains { $0.path.hasSuffix("project/real1.swift") }) + XCTAssertTrue(seen.contains { $0.path.hasSuffix("project/real2.swift") }) // Hidden but found through the visible symlink project/link.swift - XCTAssertTrue(seen.contains { $0.hasSuffix("project/.hidden.swift") }) + XCTAssertTrue(seen.contains { $0.path.hasSuffix("project/.hidden.swift") }) } func testTraversesHiddenFilesIfExplicitlySpecified() throws { @@ -56,8 +81,8 @@ final class FileIteratorTests: XCTestCase { followSymlinks: false ) XCTAssertEqual(seen.count, 2) - XCTAssertTrue(seen.contains { $0.hasSuffix("project/.build/generated.swift") }) - XCTAssertTrue(seen.contains { $0.hasSuffix("project/.hidden.swift") }) + XCTAssertTrue(seen.contains { $0.path.hasSuffix("project/.build/generated.swift") }) + XCTAssertTrue(seen.contains { $0.path.hasSuffix("project/.hidden.swift") }) } func testDoesNotFollowSymlinksIfFollowSymlinksIsFalseEvenIfExplicitlySpecified() { @@ -71,6 +96,32 @@ final class FileIteratorTests: XCTestCase { ) XCTAssertTrue(seen.isEmpty) } + + func testDoesNotTrimFirstCharacterOfPathIfRunningInRoot() throws { + // Find the root of tmpdir. On Unix systems, this is always `/`. On Windows it is the drive. + var root = tmpdir! + while !root.isRoot { + root.deleteLastPathComponent() + } + var rootPath = root.path + #if os(Windows) && compiler(<6.1) + if rootPath.hasPrefix("/") { + // Canonicalize /C: to C: + rootPath = String(rootPath.dropFirst()) + } + #endif + // Make sure that we don't drop the beginning of the path if we are running in root. + // https://github.com/swiftlang/swift-format/issues/862 + let seen = allFilesSeen(iteratingOver: [tmpdir], followSymlinks: false, workingDirectory: root).map(\.relativePath) + XCTAssertTrue(seen.allSatisfy { $0.hasPrefix(rootPath) }, "\(seen) does not contain root directory '\(rootPath)'") + } + + func testShowsRelativePaths() throws { + // Make sure that we still show the relative path if using them. + // https://github.com/swiftlang/swift-format/issues/862 + let seen = allFilesSeen(iteratingOver: [tmpdir], followSymlinks: false, workingDirectory: tmpdir) + XCTAssertEqual(Set(seen.map(\.relativePath)), ["project/real1.swift", "project/real2.swift"]) + } } extension FileIteratorTests { @@ -111,11 +162,15 @@ extension FileIteratorTests { } /// Computes the list of all files seen by using `FileIterator` to iterate over the given URLs. - private func allFilesSeen(iteratingOver urls: [URL], followSymlinks: Bool) -> [String] { - let iterator = FileIterator(urls: urls, followSymlinks: followSymlinks) - var seen: [String] = [] + private func allFilesSeen( + iteratingOver urls: [URL], + followSymlinks: Bool, + workingDirectory: URL = URL(fileURLWithPath: ".") + ) -> [URL] { + let iterator = FileIterator(urls: urls, followSymlinks: followSymlinks, workingDirectory: workingDirectory) + var seen: [URL] = [] for next in iterator { - seen.append(next.path) + seen.append(next) } return seen } From 8358e380ff3d93f0ca314c00fc7911171e86ca1a Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 5 Dec 2024 10:15:02 -0800 Subject: [PATCH 300/332] Add an api-breakages file to allow API breakages --- .github/workflows/pull_request.yml | 1 + api-breakages.txt | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 api-breakages.txt diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 5a9f75434..b29107802 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -16,3 +16,4 @@ jobs: with: license_header_check_enabled: false license_header_check_project_name: "Swift.org" + api_breakage_check_allowlist_path: "api-breakages.txt" diff --git a/api-breakages.txt b/api-breakages.txt new file mode 100644 index 000000000..987ca864c --- /dev/null +++ b/api-breakages.txt @@ -0,0 +1,4 @@ +6.2 +--- + +API breakage: constructor FileIterator.init(urls:followSymlinks:) has been removed From f0770713b310293d60b4c00170eda51fd8e80677 Mon Sep 17 00:00:00 2001 From: Josh Wisenbaker Date: Fri, 6 Dec 2024 15:00:30 -0500 Subject: [PATCH 301/332] PathCchIsRoot PathCchIsRoot PathCchIsRoot Updated isRoot extension. Now that https://github.com/swiftlang/swift-foundation/issues/976 and https://github.com/swiftlang/swift-foundation/issues/980 are fixed we can clean this code up a bit by removing the empty path check on Linux and by using the native `PathCchIsRoot` on Windows. Existing code is retained for toolchains <6.1 --- .../SwiftFormat/Utilities/URL+isRoot.swift | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftFormat/Utilities/URL+isRoot.swift b/Sources/SwiftFormat/Utilities/URL+isRoot.swift index 1ad521c7f..6a8522889 100644 --- a/Sources/SwiftFormat/Utilities/URL+isRoot.swift +++ b/Sources/SwiftFormat/Utilities/URL+isRoot.swift @@ -12,10 +12,32 @@ import Foundation +#if os(Windows) +import WinSDK +#endif + extension URL { + /// Returns a `Bool` to indicate if the given `URL` leads to the root of a filesystem. + /// A non-filesystem type `URL` will always return false. @_spi(Testing) public var isRoot: Bool { + guard isFileURL else { return false } + + #if compiler(>=6.1) + #if os(Windows) + let filePath = self.withUnsafeFileSystemRepresentation { pointer in + guard let pointer else { + return "" + } + return String(cString: pointer) + } + return filePath.withCString(encodedAs: UTF16.self, PathCchIsRoot) + #else // os(Windows) + return self.path == "/" + #endif // os(Windows) + #else // compiler(>=6.1) + #if os(Windows) - // FIXME: We should call into Windows' native check to check if this path is a root once https://github.com/swiftlang/swift-foundation/issues/976 is fixed. + // This is needed as the fixes from #844 aren't in the Swift 6.0 toolchain. // https://github.com/swiftlang/swift-format/issues/844 var pathComponents = self.pathComponents if pathComponents.first == "/" { @@ -23,10 +45,11 @@ extension URL { pathComponents = Array(pathComponents.dropFirst()) } return pathComponents.count <= 1 - #else + #else // os(Windows) // On Linux, we may end up with an string for the path due to https://github.com/swiftlang/swift-foundation/issues/980 - // TODO: Remove the check for "" once https://github.com/swiftlang/swift-foundation/issues/980 is fixed. + // This is needed as the fixes from #980 aren't in the Swift 6.0 toolchain. return self.path == "/" || self.path == "" - #endif + #endif // os(Windows) + #endif // compiler(>=6.1) } } From a652b9dbc5929f9ec10e6bdd73ca84f7243f1804 Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Tue, 10 Dec 2024 21:39:51 -0800 Subject: [PATCH 302/332] Fix comment length computation Resolved an issue where "end of line" comments matching the configured line length were incorrectly flagged as exceeding it. --- Sources/SwiftFormat/PrettyPrint/Comment.swift | 2 +- .../PrettyPrint/CommentTests.swift | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/PrettyPrint/Comment.swift b/Sources/SwiftFormat/PrettyPrint/Comment.swift index 3dff9480f..43616a5b4 100644 --- a/Sources/SwiftFormat/PrettyPrint/Comment.swift +++ b/Sources/SwiftFormat/PrettyPrint/Comment.swift @@ -67,9 +67,9 @@ struct Comment { switch kind { case .line, .docLine: + self.length = text.count self.text = [text] self.text[0].removeFirst(kind.prefixLength) - self.length = self.text.reduce(0, { $0 + $1.count + kind.prefixLength + 1 }) case .block, .docBlock: var fulltext: String = text diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift index 99b2259e2..b32342748 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift @@ -1009,6 +1009,29 @@ final class CommentTests: PrettyPrintTestCase { ) } + // Tests that "end of line" comments are flagged only when they exceed the configured line length. + func testDiagnoseMoveEndOfLineCommentAroundBoundary() { + assertPrettyPrintEqual( + input: """ + x // 789 + x // 7890 + x 1️⃣// 78901 + + """, + expected: """ + x // 789 + x // 7890 + x // 78901 + + """, + linelength: 10, + whitespaceOnly: true, + findings: [ + FindingSpec("1️⃣", message: "move end-of-line comment that exceeds the line length") + ] + ) + } + func testLineWithDocLineComment() { // none of these should be merged if/when there is comment formatting let input = From 6566d61233e9065d6407ff17d2e028bab09f8d5e Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 11 Dec 2024 16:51:24 -0800 Subject: [PATCH 303/332] Fix build warning in tests --- Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift index 4146ef51a..6c15e6f41 100644 --- a/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift +++ b/Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift @@ -103,12 +103,14 @@ final class FileIteratorTests: XCTestCase { while !root.isRoot { root.deleteLastPathComponent() } - var rootPath = root.path #if os(Windows) && compiler(<6.1) + var rootPath = root.path if rootPath.hasPrefix("/") { // Canonicalize /C: to C: rootPath = String(rootPath.dropFirst()) } + #else + let rootPath = root.path #endif // Make sure that we don't drop the beginning of the path if we are running in root. // https://github.com/swiftlang/swift-format/issues/862 From 6cb2c76b250fd8391a1c40d693c1c0d02b20aac6 Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Thu, 12 Dec 2024 17:09:39 -0800 Subject: [PATCH 304/332] Document default config values Updates Configuration.md with a more structured config description and documents the default value for each config. --- Documentation/Configuration.md | 260 +++++++++++++++++++++------------ 1 file changed, 169 insertions(+), 91 deletions(-) diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 14072d84f..42f044fb1 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -8,97 +8,175 @@ used as a command line tool or as an API. A `swift-format` configuration file is a JSON file with the following top-level keys and values: -* `version` _(number)_: The version of the configuration file. For now, this - should always be `1`. - -* `lineLength` _(number)_: The maximum allowed length of a line, in - characters. - -* `indentation` _(object)_: The kind and amount of whitespace that should be - added when indenting one level. The object value of this property should - have **exactly one of the following properties:** - - * `spaces` _(number)_: One level of indentation is the given number of - spaces. - * `tabs` _(number)_: One level of indentation is the given number of - tabs. - -* `tabWidth` _(number)_: The number of spaces that should be considered - equivalent to one tab character. This is used during line length - calculations when tabs are used for indentation. - -* `maximumBlankLines` _(number)_: The maximum number of consecutive blank - lines that are allowed to be present in a source file. Any number larger - than this will be collapsed down to the maximum. - -* `spacesBeforeEndOfLineComments` _(number)_: The number of spaces between - the last token on a non-empty line and a line comment starting with `//`. - -* `respectsExistingLineBreaks` _(boolean)_: Indicates whether or not existing - line breaks in the source code should be honored (if they are valid - according to the style guidelines being enforced). If this settings is - `false`, then the formatter will be more "opinionated" by only inserting - line breaks where absolutely necessary and removing any others, effectively - canonicalizing the output. - -* `lineBreakBeforeControlFlowKeywords` _(boolean)_: Determines the - line-breaking behavior for control flow keywords that follow a closing - brace, like `else` and `catch`. If true, a line break will be added before - the keyword, forcing it onto its own line. If false (the default), the - keyword will be placed after the closing brace (separated by a space). - -* `lineBreakBeforeEachArgument` _(boolean)_: Determines the line-breaking - behavior for generic arguments and function arguments when a declaration is - wrapped onto multiple lines. If true, a line break will be added before each - argument, forcing the entire argument list to be laid out vertically. - If false (the default), arguments will be laid out horizontally first, with - line breaks only being fired when the line length would be exceeded. - -* `lineBreakBeforeEachGenericRequirement` _(boolean)_: Determines the - line-breaking behavior for generic requirements when the requirements list - is wrapped onto multiple lines. If true, a line break will be added before each - requirement, forcing the entire requirements list to be laid out vertically. If false - (the default), requirements will be laid out horizontally first, with line breaks - only being fired when the line length would be exceeded. - -* `lineBreakBetweenDeclarationAttributes` _(boolean)_: Determines the - line-breaking behavior for adjacent attributes on declarations. - If true, a line break will be added between each attribute, forcing the - attribute list to be laid out vertically. If false (the default), - attributes will be laid out horizontally first, with line breaks only - being fired when the line length would be exceeded. - -* `prioritizeKeepingFunctionOutputTogether` _(boolean)_: Determines if - function-like declaration outputs should be prioritized to be together with the - function signature right (closing) parenthesis. If false (the default), function - output (i.e. throws, return type) is not prioritized to be together with the - signature's right parenthesis, and when the line length would be exceeded, - a line break will be fired after the function signature first, indenting the - declaration output one additional level. If true, A line break will be fired - further up in the function's declaration (e.g. generic parameters, - parameters) before breaking on the function's output. - -* `indentConditionalCompilationBlocks` _(boolean)_: Determines if - conditional compilation blocks are indented. If this setting is `false` the body - of `#if`, `#elseif`, and `#else` is not indented. Defaults to `true`. - -* `lineBreakAroundMultilineExpressionChainComponents` _(boolean)_: Determines whether - line breaks should be forced before and after multiline components of dot-chained - expressions, such as function calls and subscripts chained together through member - access (i.e. "." expressions). When any component is multiline and this option is - true, a line break is forced before the "." of the component and after the component's - closing delimiter (i.e. right paren, right bracket, right brace, etc.). - -* `spacesAroundRangeFormationOperators` _(boolean)_: Determines whether whitespace should be forced - before and after the range formation operators `...` and `..<`. - -* `multiElementCollectionTrailingCommas` _(boolean)_: Determines whether multi-element collection literals should have trailing commas. - Defaults to `true`. - -* `indentBlankLines` _(boolean)_: Determines whether blank lines should be modified - to match the current indentation. When this setting is true, blank lines will be modified - to match the indentation level, adding indentation whether or not there is existing whitespace. - When false (the default), all whitespace in blank lines will be completely removed. +### `version` +**type:** number + +**description:** The version of the configuration file. For now, this should always be `1`. + +**default:** `1` + +--- + +### `lineLength` +**type:** number + +**description:** The maximum allowed length of a line, in characters. + +**default:** `100` + +--- + +### `indentation` +**type:** object + +**description:** The kind and amount of whitespace that should be added when indenting one level. The object value of this property should have exactly one of the following properties: + +- `spaces` _(number)_: One level of indentation is the given number of spaces. +- `tabs` _(number)_: One level of indentation is the given number of tabs. + +**default:** `{ "spaces": 2 }` + +--- + +### `tabWidth` +**type:** number + +**description:** The number of spaces that should be considered equivalent to one tab character. This is used during line length calculations when tabs are used for indentation. + +**default:** `8` + +--- + +### `maximumBlankLines` +**type:** number + +**description:** The maximum number of consecutive blank lines that are allowed to be present in a source file. Any number larger than this will be collapsed down to the maximum. + +**default:** `1` + +--- + +### `spacesBeforeEndOfLineComments` +**type:** number + +**description:** The number of spaces between the last token on a non-empty line and a line comment starting with `//`. + +**default:** `2` + +--- + +### `respectsExistingLineBreaks` +**type:** boolean + +**description:** Indicates whether or not existing line breaks in the source code should be honored (if they are valid according to the style guidelines being enforced). If this settings is `false`, then the formatter will be more "opinionated" by only inserting line breaks where absolutely necessary and removing any others, effectively canonicalizing the output. + +**default:** `true` + +--- + +### `lineBreakBeforeControlFlowKeywords` +**type:** boolean + +**description:** Determines the line-breaking behavior for control flow keywords that follow a closing brace, like `else` and `catch`. If true, a line break will be added before the keyword, forcing it onto its own line. If `false`, the keyword will be placed after the closing brace (separated by a space). + +**default:** `false` + +--- + +### `lineBreakBeforeEachArgument` +**type:** boolean + +**description:** Determines the line-breaking behavior for generic arguments and function arguments when a declaration is wrapped onto multiple lines. If true, a line break will be added before each argument, forcing the entire argument list to be laid out vertically. If `false`, arguments will be laid out horizontally first, with line breaks only being fired when the line length would be exceeded. + +**default:** `false` + +--- + +### `lineBreakBeforeEachGenericRequirement` +**type:** boolean + +**description:** Determines the line-breaking behavior for generic requirements when the requirements list is wrapped onto multiple lines. If true, a line break will be added before each requirement, forcing the entire requirements list to be laid out vertically. If `false`, requirements will be laid out horizontally first, with line breaks only being fired when the line length would be exceeded. + +**default:** `false` + +--- + +### `lineBreakBetweenDeclarationAttributes` +**type:** boolean + +**description:** Determines the line-breaking behavior for adjacent attributes on declarations. If true, a line break will be added between each attribute, forcing the attribute list to be laid out vertically. If `false`, attributes will be laid out horizontally first, with line breaks only being fired when the line length would be exceeded. + +**default:** `false` + +--- + +### `prioritizeKeepingFunctionOutputTogether` +**type:** boolean + +**description:** Determines if function-like declaration outputs should be prioritized to be together with thefunction signature right (closing) parenthesis. If `false`, function output (i.e. throws, return type) is not prioritized to be together with the signature's right parenthesis, and when the line length would be exceeded,a line break will be fired after the function signature first, indenting the declaration output one additional level. If true, A line break will be fired further up in the function's declaration (e.g. generic parameters, parameters) before breaking on the function's output. + +**default:** `false` + +--- + +### `indentConditionalCompilationBlocks` +**type:** boolean + +**description:** Determines if conditional compilation blocks are indented. If this setting is `false` the body of `#if`, `#elseif`, and `#else` is not indented. + +**default:** `true` + +--- + +### `lineBreakAroundMultilineExpressionChainComponents` +**type:** boolean + +**description:** Determines whether line breaks should be forced before and after multiline components of dot-chained expressions, such as function calls and subscripts chained together through member access (i.e. "." expressions). When any component is multiline and this option is true, a line break is forced before the "." of the component and after the component's closing delimiter (i.e. right paren, right bracket, right brace, etc.). + +**default:** `false` + +--- + +## FIXME: fileScopedDeclarationPrivacy + +--- + +### FIXME: indentSwitchCaseLabels + +--- + +### `spacesAroundRangeFormationOperators` +**type:** boolean + +**description:** Determines whether whitespace should be forced before and after the range formation operators `...` and `..<`. + +**default:** `false` + +--- + +### FIXME: noAssignmentInExpressions + +--- + +### `multiElementCollectionTrailingCommas` +**type:** boolean + +**description:** Determines whether multi-element collection literals should have trailing commas. + +**default:** `true` + +--- + +### FIXME: reflowMultilineStringLiterals + +--- + +### `indentBlankLines` +**type:** boolean + +**description:** Determines whether blank lines should be modified to match the current indentation. When this setting is true, blank lines will be modified whitespace. If `false`, all whitespace in blank lines will be completely removed. + +**default:** `false` > TODO: Add support for enabling/disabling specific syntax transformations in > the pipeline. From f384f62094b49f4420db0a0b78466f957949f6ab Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Fri, 13 Dec 2024 12:04:17 +0100 Subject: [PATCH 305/332] Add Swift package index --- .spi.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .spi.yml diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 000000000..bde5eb41c --- /dev/null +++ b/.spi.yml @@ -0,0 +1,11 @@ +# This is manifest file for the Swift Package Index for it to +# auto-generate and host DocC documentation. +# For reference see https://swiftpackageindex.com/swiftpackageindex/spimanifest/documentation/spimanifest/commonusecases#Host-DocC-documentation-in-the-Swift-Package-Index. + +version: 1 +builder: + configs: + - documentation_targets: + # First item in the list is the "landing" (default) target + - SwiftFormat + custom_documentation_parameters: [--experimental-skip-synthesized-symbols] From dba874b4af371ea1e9030bfd040d2854cb45e7a3 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Sat, 14 Dec 2024 11:28:40 +0100 Subject: [PATCH 306/332] Fix documentation --- Sources/SwiftFormat/API/SwiftFormatter.swift | 4 ++-- Sources/SwiftFormat/API/SwiftLinter.swift | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftFormat/API/SwiftFormatter.swift b/Sources/SwiftFormat/API/SwiftFormatter.swift index ddd0bbe2c..6e06015cf 100644 --- a/Sources/SwiftFormat/API/SwiftFormatter.swift +++ b/Sources/SwiftFormat/API/SwiftFormatter.swift @@ -45,7 +45,7 @@ public final class SwiftFormatter { /// This form of the `format` function automatically folds expressions using the default operator /// set defined in Swift. If you need more control over this—for example, to provide the correct /// precedence relationships for custom operators—you must parse and fold the syntax tree - /// manually and then call ``format(syntax:assumingFileURL:to:)``. + /// manually and then call ``format(syntax:source:operatorTable:assumingFileURL:selection:to:)``. /// /// - Parameters: /// - url: The URL of the file containing the code to format. @@ -81,7 +81,7 @@ public final class SwiftFormatter { /// This form of the `format` function automatically folds expressions using the default operator /// set defined in Swift. If you need more control over this—for example, to provide the correct /// precedence relationships for custom operators—you must parse and fold the syntax tree - /// manually and then call ``format(syntax:assumingFileURL:to:)``. + /// manually and then call ``format(syntax:source:operatorTable:assumingFileURL:selection:to:)``. /// /// - Parameters: /// - source: The Swift source code to be formatted. diff --git a/Sources/SwiftFormat/API/SwiftLinter.swift b/Sources/SwiftFormat/API/SwiftLinter.swift index cfc4639f6..d70f30673 100644 --- a/Sources/SwiftFormat/API/SwiftLinter.swift +++ b/Sources/SwiftFormat/API/SwiftLinter.swift @@ -45,7 +45,7 @@ public final class SwiftLinter { /// This form of the `lint` function automatically folds expressions using the default operator /// set defined in Swift. If you need more control over this—for example, to provide the correct /// precedence relationships for custom operators—you must parse and fold the syntax tree - /// manually and then call ``lint(syntax:assumingFileURL:)``. + /// manually and then call ``lint(syntax:source:operatorTable:assumingFileURL:)``. /// /// - Parameters: /// - url: The URL of the file containing the code to format. @@ -76,7 +76,7 @@ public final class SwiftLinter { /// This form of the `lint` function automatically folds expressions using the default operator /// set defined in Swift. If you need more control over this—for example, to provide the correct /// precedence relationships for custom operators—you must parse and fold the syntax tree - /// manually and then call ``lint(syntax:assumingFileURL:)``. + /// manually and then call ``lint(syntax:source:operatorTable:assumingFileURL:)``. /// /// - Parameters: /// - source: The Swift source code to be linted. @@ -124,6 +124,7 @@ public final class SwiftLinter { /// /// - Parameters: /// - syntax: The Swift syntax tree to be converted to be linted. + /// - source: The Swift source code to be linted. /// - operatorTable: The table that defines the operators and their precedence relationships. /// This must be the same operator table that was used to fold the expressions in the `syntax` /// argument. From 72123e5dad1d89db7e8196c37fd066da54d6ede3 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Mon, 16 Dec 2024 10:58:02 +0100 Subject: [PATCH 307/332] Bump to Swift 5.8 for tool version --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 228b13f5f..74ab7b2c7 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.6 +// swift-tools-version:5.8 //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project From 87467ee3a573c2b13e5b0078b93cb29af7bcc545 Mon Sep 17 00:00:00 2001 From: Josh Wisenbaker Date: Fri, 13 Dec 2024 16:32:35 -0500 Subject: [PATCH 308/332] PrettyPrinterPerformance PrettyPrinterPerformance PrettyPrinterPerformance PrettyPrinterPerformance Optimized the PrettyPrinter for #894 Worked to get the perfomance to be closer to where we were before the changes in #883. This code should be only about 1.5% slower rather than 7% slower. Using a lazy filter as `count(where:_)` isn't avaliable < Swift 6.0. One forward loop and using the UTF8 view makes this faster than the original code pre-#883. --- .../PrettyPrint/PrettyPrintBuffer.swift | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift index 4b02d372e..0752cd916 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift @@ -122,16 +122,17 @@ struct PrettyPrintBuffer { // In case of comments, we may get a multi-line string. // To account for that case, we need to correct the lineNumber count. // The new column is only the position within the last line. - let lines = text.split(separator: "\n") - lineNumber += lines.count - 1 - if lines.count > 1 { - // in case we have inserted new lines, we need to reset the column - column = lines.last?.count ?? 0 - } else { - // in case it is an end of line comment or a single line comment, - // we just add to the current column - column += lines.last?.count ?? 0 + var lastLength = 0 + // We are only interested in "\n" we can use the UTF8 view and skip the grapheme clustering. + for element in text.utf8 { + if element == 10 { + lineNumber += 1 + lastLength = 0 + } else { + lastLength += 1 + } } + column += lastLength } /// Request that the given number of spaces be printed out before the next text token. From bb5b4c9b5aeaee2effc728aa496cfa2bc4da54ab Mon Sep 17 00:00:00 2001 From: Josh Wisenbaker Date: Thu, 19 Dec 2024 09:00:45 -0500 Subject: [PATCH 309/332] Update Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift Co-authored-by: Alex Hoppen --- Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift index 0752cd916..724f346b5 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift @@ -125,7 +125,7 @@ struct PrettyPrintBuffer { var lastLength = 0 // We are only interested in "\n" we can use the UTF8 view and skip the grapheme clustering. for element in text.utf8 { - if element == 10 { + if element == UInt8(ascii: "\n") { lineNumber += 1 lastLength = 0 } else { From 8073ddcb6de0b4397f094f10d7113eeb831a4732 Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Fri, 20 Dec 2024 12:30:55 -0800 Subject: [PATCH 310/332] Revert "PrettyPrinterPerformance Optimized the PrettyPrinter for #894" --- .../PrettyPrint/PrettyPrintBuffer.swift | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift index 724f346b5..4b02d372e 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift @@ -122,17 +122,16 @@ struct PrettyPrintBuffer { // In case of comments, we may get a multi-line string. // To account for that case, we need to correct the lineNumber count. // The new column is only the position within the last line. - var lastLength = 0 - // We are only interested in "\n" we can use the UTF8 view and skip the grapheme clustering. - for element in text.utf8 { - if element == UInt8(ascii: "\n") { - lineNumber += 1 - lastLength = 0 - } else { - lastLength += 1 - } + let lines = text.split(separator: "\n") + lineNumber += lines.count - 1 + if lines.count > 1 { + // in case we have inserted new lines, we need to reset the column + column = lines.last?.count ?? 0 + } else { + // in case it is an end of line comment or a single line comment, + // we just add to the current column + column += lines.last?.count ?? 0 } - column += lastLength } /// Request that the given number of spaces be printed out before the next text token. From 3d1e03b9d48426f5aa50f442e84a580a34c11716 Mon Sep 17 00:00:00 2001 From: TTOzzi Date: Sat, 4 Jan 2025 22:34:44 +0900 Subject: [PATCH 311/332] Fix NoEmptyLinesOpeningClosingBraces to respect consecutive newlines when a function starts or ends with a comment --- .../NoEmptyLineOpeningClosingBraces.swift | 43 +++++++++++--- ...oEmptyLinesOpeningClosingBracesTests.swift | 56 +++++++++++++++++++ 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift b/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift index b01d173bf..5158dbb95 100644 --- a/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift +++ b/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift @@ -69,7 +69,9 @@ public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule { } func rewritten(_ token: TokenSyntax) -> TokenSyntax { - let (trimmedLeadingTrivia, count) = token.leadingTrivia.trimmingSuperfluousNewlines() + let (trimmedLeadingTrivia, count) = token.leadingTrivia.trimmingSuperfluousNewlines( + fromClosingBrace: token.tokenKind == .rightBrace + ) if trimmedLeadingTrivia.sourceLength != token.leadingTriviaLength { diagnose(.removeEmptyLinesBefore(count), on: token, anchor: .start) return token.with(\.leadingTrivia, trimmedLeadingTrivia) @@ -83,7 +85,7 @@ public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule { if let first = collection.first, first.leadingTrivia.containsNewlines, let index = collection.index(of: first) { - let (trimmedLeadingTrivia, count) = first.leadingTrivia.trimmingSuperfluousNewlines() + let (trimmedLeadingTrivia, count) = first.leadingTrivia.trimmingSuperfluousNewlines(fromClosingBrace: false) if trimmedLeadingTrivia.sourceLength != first.leadingTriviaLength { diagnose(.removeEmptyLinesAfter(count), on: first, anchor: .leadingTrivia(0)) var first = first @@ -96,24 +98,49 @@ public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule { } extension Trivia { - func trimmingSuperfluousNewlines() -> (Trivia, Int) { + func trimmingSuperfluousNewlines(fromClosingBrace: Bool) -> (Trivia, Int) { var trimmmed = 0 + var pendingNewlineCount = 0 let pieces = self.indices.reduce([TriviaPiece]()) { (partialResult, index) in let piece = self[index] // Collapse consecutive newlines into a single one if case .newlines(let count) = piece { - if let last = partialResult.last, last.isNewline { - trimmmed += count - return partialResult + if fromClosingBrace { + if index == self.count - 1 { + // For the last index(newline right before the closing brace), collapse into a single newline + trimmmed += count - 1 + return partialResult + [.newlines(1)] + } else { + pendingNewlineCount += count + return partialResult + } } else { - trimmmed += count - 1 - return partialResult + [.newlines(1)] + if let last = partialResult.last, last.isNewline { + trimmmed += count + return partialResult + } else if index == 0 { + // For leading trivia not associated with a closing brace, collapse the first newline into a single one + trimmmed += count - 1 + return partialResult + [.newlines(1)] + } else { + return partialResult + [piece] + } } } // Remove spaces/tabs surrounded by newlines if piece.isSpaceOrTab, index > 0, index < self.count - 1, self[index - 1].isNewline, self[index + 1].isNewline { return partialResult } + // Handle pending newlines if there are any + if pendingNewlineCount > 0 { + if index < self.count - 1 { + let newlines = TriviaPiece.newlines(pendingNewlineCount) + pendingNewlineCount = 0 + return partialResult + [newlines] + [piece] + } else { + return partialResult + [.newlines(1)] + [piece] + } + } // Retain other trivia pieces return partialResult + [piece] } diff --git a/Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift b/Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift index 30f9471fc..bb555739a 100644 --- a/Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift +++ b/Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift @@ -136,4 +136,60 @@ final class NoEmptyLinesOpeningClosingBracesTests: LintOrFormatRuleTestCase { ] ) } + + func testNoEmptyLinesOpeningClosingBracesInFunctionBeginningAndEndingWithComment() { + assertFormatting( + NoEmptyLinesOpeningClosingBraces.self, + input: """ + func myFunc() { + // Some comment here + + // Do a thing + var x = doAThing() + + // Do a thing + + var y = doAThing() + + // Some other comment here + } + """, + expected: """ + func myFunc() { + // Some comment here + + // Do a thing + var x = doAThing() + + // Do a thing + + var y = doAThing() + + // Some other comment here + } + """ + ) + } + + func testNoEmptyLinesOpeningClosingBracesInFunctionWithEmptyLinesOnly() { + assertFormatting( + NoEmptyLinesOpeningClosingBraces.self, + input: """ + func myFunc() { + + + + + + 1️⃣} + """, + expected: """ + func myFunc() { + } + """, + findings: [ + FindingSpec("1️⃣", message: "remove empty lines before '}'") + ] + ) + } } From 3ca204bb18c4a20bcb4402c22ddb8c6241c906f3 Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Thu, 9 Jan 2025 20:32:55 -0800 Subject: [PATCH 312/332] Optimize pretty printing performance swift-format#883 fixed outputting incorrect line numbers, but introduced a performance regression. swift-format#901 improved this back to around the original, but had to be reverted as it introduced a an issue due to counting codepoints rather than characters. Introduce a similar optimization again, but only for the first portion of the string (prior to the last newline). Fixes swift-format#894 again. --- .../PrettyPrint/PrettyPrintBuffer.swift | 25 ++++++++------ .../PrettyPrint/LineNumbersTests.swift | 34 +++++++++++++++++++ 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift index 4b02d372e..9ea726206 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift @@ -119,18 +119,21 @@ struct PrettyPrintBuffer { consecutiveNewlineCount = 0 pendingSpaces = 0 - // In case of comments, we may get a multi-line string. - // To account for that case, we need to correct the lineNumber count. - // The new column is only the position within the last line. - let lines = text.split(separator: "\n") - lineNumber += lines.count - 1 - if lines.count > 1 { - // in case we have inserted new lines, we need to reset the column - column = lines.last?.count ?? 0 + // In case of comments, we may get a multi-line string. To account for that case, we need to correct the + // `lineNumber` count. The new `column` is the position within the last line. + + var lastNewlineIndex: String.Index? = nil + for i in text.utf8.indices { + if text.utf8[i] == UInt8(ascii: "\n") { + lastNewlineIndex = i + lineNumber += 1 + } + } + + if let lastNewlineIndex { + column = text.distance(from: text.utf8.index(after: lastNewlineIndex), to: text.endIndex) } else { - // in case it is an end of line comment or a single line comment, - // we just add to the current column - column += lines.last?.count ?? 0 + column += text.count } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/LineNumbersTests.swift b/Tests/SwiftFormatTests/PrettyPrint/LineNumbersTests.swift index 1bb58f7ab..c377e7cad 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/LineNumbersTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/LineNumbersTests.swift @@ -81,4 +81,38 @@ final class LineNumbersTests: PrettyPrintTestCase { ] ) } + + func testCharacterVsCodepoint() { + let input = + """ + let fo = 1 // 🤥 + + """ + + assertPrettyPrintEqual( + input: input, + expected: input, + linelength: 16, + whitespaceOnly: true, + findings: [] + ) + } + + func testCharacterVsCodepointMultiline() { + let input = + #""" + /// This is a multiline + /// comment that is in 🤥 + /// fact perfectly sized + + """# + + assertPrettyPrintEqual( + input: input, + expected: input, + linelength: 25, + whitespaceOnly: true, + findings: [] + ) + } } From 83b5f010d250e3b4d3009562ed55414766201514 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 13 Jan 2025 10:06:44 -0800 Subject: [PATCH 313/332] [WhitespaceLinter] Use hand crafted "is whitespace" function * `UnicodeScalar(_:)` on arbitrary UTF8 code point was wrong. It only works correctly if the code point is < 0x80 * `UnicodeScalar.Properties.isWhitespace` is slow. Profiling 'lint' shows it's taking 13.6 of the entire time * Whitespaces in Unicode "Basic Latin" block are well defined, there's no need to consult `UnicodeScalar.Properties` --- .../SwiftFormat/PrettyPrint/WhitespaceLinter.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift index c5c5e2ae8..30f733952 100644 --- a/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift +++ b/Sources/SwiftFormat/PrettyPrint/WhitespaceLinter.swift @@ -339,9 +339,16 @@ public class WhitespaceLinter { startingAt offset: Int, in data: [UTF8.CodeUnit] ) -> ArraySlice { + func isWhitespace(_ char: UTF8.CodeUnit) -> Bool { + switch char { + case UInt8(ascii: " "), UInt8(ascii: "\n"), UInt8(ascii: "\t"), UInt8(ascii: "\r"), /*VT*/ 0x0B, /*FF*/ 0x0C: + return true + default: + return false + } + } guard - let whitespaceEnd = - data[offset...].firstIndex(where: { !UnicodeScalar($0).properties.isWhitespace }) + let whitespaceEnd = data[offset...].firstIndex(where: { !isWhitespace($0) }) else { return data[offset.. Date: Sun, 12 Jan 2025 06:27:05 +0900 Subject: [PATCH 314/332] Gradually deprecate running swift-format without input paths; require '-' for stdin in the future --- Sources/swift-format/Frontend/Frontend.swift | 18 +++++++++++++++++- .../Subcommands/LintFormatOptions.swift | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index a3ea18a4f..b0e262a94 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -97,7 +97,23 @@ class Frontend { /// Runs the linter or formatter over the inputs. final func run() { - if lintFormatOptions.paths.isEmpty { + if lintFormatOptions.paths == ["-"] { + processStandardInput() + } else if lintFormatOptions.paths.isEmpty { + diagnosticsEngine.emitWarning( + """ + Running swift-format without input paths is deprecated and will be removed in the future. + + Please update your invocation to do either of the following: + + - Pass `-` to read from stdin (e.g., `cat MyFile.swift | swift-format -`). + - Pass one or more paths to Swift source files or directories containing + Swift source files. When passing directories, make sure to include the + `--recursive` flag. + + For more information, use the `--help` option. + """ + ) processStandardInput() } else { processURLs( diff --git a/Sources/swift-format/Subcommands/LintFormatOptions.swift b/Sources/swift-format/Subcommands/LintFormatOptions.swift index 520eaf970..737de42fc 100644 --- a/Sources/swift-format/Subcommands/LintFormatOptions.swift +++ b/Sources/swift-format/Subcommands/LintFormatOptions.swift @@ -108,7 +108,7 @@ struct LintFormatOptions: ParsableArguments { var experimentalFeatures: [String] = [] /// The list of paths to Swift source files that should be formatted or linted. - @Argument(help: "Zero or more input filenames.") + @Argument(help: "Zero or more input filenames. Use `-` for stdin.") var paths: [String] = [] @Flag(help: .hidden) var debugDisablePrettyPrint: Bool = false From 4bba23b83cb6a1cbe191197907d220a3409c50fc Mon Sep 17 00:00:00 2001 From: Dmytro Mishchenko Date: Thu, 30 Jan 2025 12:54:58 +0100 Subject: [PATCH 315/332] Document missing configuration Documented missing config values in `Configuration.md`. Added the same description as inside inline docs. Documented values: * `fileScopedDeclarationPrivacy` * `indentSwitchCaseLabels` * `noAssignmentInExpressions` * `reflowMultilineStringLiterals` --- Documentation/Configuration.md | 88 ++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 42f044fb1..f84fb1c60 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -137,11 +137,40 @@ top-level keys and values: --- -## FIXME: fileScopedDeclarationPrivacy +## `fileScopedDeclarationPrivacy` +**type:** object + +**description:** Declarations at file scope with effective private access should be consistently declared as either `fileprivate` or `private`, determined by configuration. + +- `accessLevel` _(string)_: The formal access level to use when encountering a file-scoped declaration with effective private access. Allowed values are `private` and `fileprivate`. + +**default:** `{ "accessLevel" : "private" }` --- -### FIXME: indentSwitchCaseLabels +### `indentSwitchCaseLabels` +**type:** boolean + +**description:** Determines if `case` statements should be indented compared to the containing `switch` block. + +When `false`, the correct form is: +```swift +switch someValue { +case someCase: + someStatement +... +} +``` +When `true`, the correct form is: +```swift +switch someValue { + case someCase: + someStatement + ... +} +``` + +**default:** `false` --- @@ -154,7 +183,14 @@ top-level keys and values: --- -### FIXME: noAssignmentInExpressions +### `noAssignmentInExpressions` +**type:** object + +**description:** Assignment expressions must be their own statements. Assignment should not be used in an expression context that expects a `Void` value. For example, assigning a variable within a `return` statement existing a `Void` function is prohibited. + +- `allowedFunctions` _(strings array)_: A list of function names where assignments are allowed to be embedded in expressions that are passed as parameters to that function. + +**default:** `{ "allowedFunctions" : ["XCTAssertNoThrow"] }` --- @@ -167,7 +203,51 @@ top-level keys and values: --- -### FIXME: reflowMultilineStringLiterals +### `reflowMultilineStringLiterals` +**type:** `string` + +**description:** Determines how multiline string literals should reflow when formatted. + +- `never`: Never reflow multiline string literals. +- `onlyLinesOverLength`: Reflow lines in string literal that exceed the maximum line length. +For example with a line length of 10: +```swift +""" +an escape\ + line break +a hard line break +""" +``` +will be formatted as: +```swift +""" +an esacpe\ + line break +a hard \ +line break +""" +``` +- `always`: Always reflow multiline string literals, this will ignore existing escaped newlines in the literal and reflow each line. Hard linebreaks are still respected. +For example, with a line length of 10: +```swift +""" +one \ +word \ +a line. +this is too long. +""" +``` +will be formatted as: +```swift +""" +one word \ +a line. +this is \ +too long. +""" +``` + +**default:** `"never"` --- From 3bd45820b50322f25b6a273dbbcf996bd2808f4a Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 5 Mar 2025 10:31:36 -0800 Subject: [PATCH 316/332] Run Windows tests inside a Docker container Looks like GitHub updated Visual Studio on their Windows runners and the new SDK causes compilation issues. Run in a Docker container so we fully control the version of Visual Studio installed. --- .github/workflows/publish_release.yml | 1 - .github/workflows/pull_request.yml | 2 -- 2 files changed, 3 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 5c1985c7c..a360c3f97 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -99,7 +99,6 @@ jobs: matrix: release: [true, false] with: - enable_windows_docker: false # Dockerless Windows is 5-10 minutes faster than Docker on Windows linux_pre_build_command: | git config --global --add safe.directory "$(realpath .)" git config --local user.name 'swift-ci' diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b29107802..87e2293aa 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -8,8 +8,6 @@ jobs: tests: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main - with: - enable_windows_docker: false # Dockerless Windows is 5-10 minutes faster than Docker on Windows soundness: name: Soundness uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main From d749be649f824b824c6c6bf6a9f6379df9e97b89 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Tue, 14 Feb 2023 10:35:28 +0000 Subject: [PATCH 317/332] Revert "[5.8] Update for SwiftSyntax if/switch expression work" --- Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift | 6 +++--- Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 03da7d4b4..519da82bd 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -575,7 +575,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: IfExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: IfStmtSyntax) -> SyntaxVisitorContinueKind { // There may be a consistent breaking group around this node, see `CodeBlockItemSyntax`. This // group is necessary so that breaks around and inside of the conditions aren't forced to break // when the if-stmt spans multiple lines. @@ -612,7 +612,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { tokenAfterElse.hasPrecedingLineComment { after(node.elseKeyword, tokens: .break(.same, size: 1)) - } else if let elseBody = node.elseBody, elseBody.is(IfExprSyntax.self) { + } else if let elseBody = node.elseBody, elseBody.is(IfStmtSyntax.self) { after(node.elseKeyword, tokens: .space) } } @@ -783,7 +783,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return .visitChildren } - override func visit(_ node: SwitchExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: SwitchStmtSyntax) -> SyntaxVisitorContinueKind { before(node.switchKeyword, tokens: .open) after(node.switchKeyword, tokens: .space) before(node.leftBrace, tokens: .break(.reset)) diff --git a/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift index 6671879e4..caa09db7d 100644 --- a/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift +++ b/Sources/SwiftFormat/Rules/UseWhereClausesInForLoops.swift @@ -72,6 +72,13 @@ public final class UseWhereClausesInForLoops: SyntaxFormatRule { default: return forInStmt } + diagnose(.useWhereInsteadOfIf, on: ifStmt) + return updateWithWhereCondition( + node: forInStmt, + condition: condition, + statements: ifStmt.body.statements + ) + case .guardStmt(let guardStmt) where guardStmt.conditions.count == 1 && guardStmt.body.statements.count == 1 From aa18686bbd519e07352598d3ce28e0b80c59d11d Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Fri, 7 Apr 2023 11:00:35 -0700 Subject: [PATCH 318/332] Update package dependencies for 508.0.0. --- Package.swift | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Package.swift b/Package.swift index f960544aa..38c5bd13e 100644 --- a/Package.swift +++ b/Package.swift @@ -145,6 +145,7 @@ let package = Package( targets: targets ) +<<<<<<< HEAD func swiftSyntaxDependencies(_ names: [String]) -> [Target.Dependency] { if buildDynamicSwiftSyntaxLibrary { return [.product(name: "_SwiftSyntaxDynamic", package: "swift-syntax")] @@ -211,4 +212,30 @@ var swiftformatLinkSettings: [LinkerSetting] { } else { return [] } +======= +// MARK: Dependencies + +if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { + // Building standalone. + package.dependencies += [ + .package( + url: "https://github.com/apple/swift-argument-parser.git", + Version("1.0.1")..>>>>>> f0ad11e (Update package dependencies for 508.0.0.) } From 8d3a55f44e3cf47b28b63f8b4207b07031e688c2 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 21 Apr 2023 15:27:12 -0700 Subject: [PATCH 319/332] Relax version requirement for swift-argument-parser This will make swift-format more tolerant with regard to which swift-argument-parser version it needs, resulting in fewer version conflicts for packages that depend on swift-format. --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 38c5bd13e..8749713c2 100644 --- a/Package.swift +++ b/Package.swift @@ -220,7 +220,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { package.dependencies += [ .package( url: "https://github.com/apple/swift-argument-parser.git", - Version("1.0.1").. Date: Wed, 23 Aug 2023 16:04:59 +0530 Subject: [PATCH 320/332] Remove test targets and test util --- Package.swift | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/Package.swift b/Package.swift index 8749713c2..f960544aa 100644 --- a/Package.swift +++ b/Package.swift @@ -145,7 +145,6 @@ let package = Package( targets: targets ) -<<<<<<< HEAD func swiftSyntaxDependencies(_ names: [String]) -> [Target.Dependency] { if buildDynamicSwiftSyntaxLibrary { return [.product(name: "_SwiftSyntaxDynamic", package: "swift-syntax")] @@ -212,30 +211,4 @@ var swiftformatLinkSettings: [LinkerSetting] { } else { return [] } -======= -// MARK: Dependencies - -if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { - // Building standalone. - package.dependencies += [ - .package( - url: "https://github.com/apple/swift-argument-parser.git", - from: "1.0.1" - ), - .package( - url: "https://github.com/apple/swift-syntax.git", - from: "508.0.0" - ), - .package( - url: "https://github.com/apple/swift-tools-support-core.git", - from: "0.5.0" - ), - ] -} else { - package.dependencies += [ - .package(path: "../swift-argument-parser"), - .package(path: "../swift-syntax"), - .package(path: "../swift-tools-support-core"), - ] ->>>>>>> f0ad11e (Update package dependencies for 508.0.0.) } From 78844c32bd82d6ea4603816a4dcc4534b2b896ee Mon Sep 17 00:00:00 2001 From: deepak-deepsource Date: Wed, 23 Aug 2023 16:06:09 +0530 Subject: [PATCH 321/332] Add Dockerfile --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..be2505b1a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,2 @@ +FROM swift:5.8.1-focal as swift-format-builder +RUN swift build -c release -Xswiftc -static-executable From 72835ccbc9be1283af1956dcfe596b00ba90a0b2 Mon Sep 17 00:00:00 2001 From: deepak-deepsource Date: Wed, 23 Aug 2023 16:12:46 +0530 Subject: [PATCH 322/332] Add GH to build and push image --- .github/workflows/build_and_push.yaml | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/build_and_push.yaml diff --git a/.github/workflows/build_and_push.yaml b/.github/workflows/build_and_push.yaml new file mode 100644 index 000000000..678e5912a --- /dev/null +++ b/.github/workflows/build_and_push.yaml @@ -0,0 +1,40 @@ +name: Build and push docker image +on: + push: + tags: + - v*-static-binary + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build_and_push_image: + name: "${{ github.head_ref }}" + runs-on: [self-hosted, charizard] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Authenticate to Google Cloud + uses: 'google-github-actions/auth@v1' + with: + credentials_json: '${{ secrets.DS_GHA_SA_DEV }}' + + - name: Set up Cloud SDK + uses: 'google-github-actions/setup-gcloud@v1' + + - name: Configure docker for gcr + run: gcloud auth configure-docker + + - name: Build dev image + uses: docker/build-push-action@v4 + with: + context: . + tags: us.gcr.io/deepsource-dev/swift-format-static:dev + push: true + pull: true + no-cache: true From 8d6fde25fadaca8167029bf47b9d1975178abf2f Mon Sep 17 00:00:00 2001 From: deepak-deepsource Date: Wed, 23 Aug 2023 16:17:56 +0530 Subject: [PATCH 323/332] For test -- undo this change --- .github/workflows/build_and_push.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build_and_push.yaml b/.github/workflows/build_and_push.yaml index 678e5912a..eef5ca7dc 100644 --- a/.github/workflows/build_and_push.yaml +++ b/.github/workflows/build_and_push.yaml @@ -1,8 +1,6 @@ name: Build and push docker image on: push: - tags: - - v*-static-binary concurrency: group: ${{ github.workflow }}-${{ github.ref }} From 709004b1a5ded6bd6832e306b2e9cd4eb1e56aff Mon Sep 17 00:00:00 2001 From: deepak-deepsource Date: Wed, 23 Aug 2023 16:28:55 +0530 Subject: [PATCH 324/332] Copy file for docker build --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index be2505b1a..cfe8eeccd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,2 +1,4 @@ FROM swift:5.8.1-focal as swift-format-builder +ADD . /toolbox +WORKDIR /toolbox RUN swift build -c release -Xswiftc -static-executable From e9004bc6386be72506e503d96d490c525b6384b6 Mon Sep 17 00:00:00 2001 From: deepak-deepsource Date: Thu, 24 Aug 2023 15:59:59 +0530 Subject: [PATCH 325/332] Add GCR build script and remove workflow --- .github/workflows/build_and_push.yaml | 38 --------------------------- cloudbuild_depl.yaml | 17 ++++++++++++ cloudbuild_depl_dev.yaml | 13 +++++++++ 3 files changed, 30 insertions(+), 38 deletions(-) delete mode 100644 .github/workflows/build_and_push.yaml create mode 100644 cloudbuild_depl.yaml create mode 100644 cloudbuild_depl_dev.yaml diff --git a/.github/workflows/build_and_push.yaml b/.github/workflows/build_and_push.yaml deleted file mode 100644 index eef5ca7dc..000000000 --- a/.github/workflows/build_and_push.yaml +++ /dev/null @@ -1,38 +0,0 @@ -name: Build and push docker image -on: - push: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build_and_push_image: - name: "${{ github.head_ref }}" - runs-on: [self-hosted, charizard] - - steps: - - uses: actions/checkout@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Authenticate to Google Cloud - uses: 'google-github-actions/auth@v1' - with: - credentials_json: '${{ secrets.DS_GHA_SA_DEV }}' - - - name: Set up Cloud SDK - uses: 'google-github-actions/setup-gcloud@v1' - - - name: Configure docker for gcr - run: gcloud auth configure-docker - - - name: Build dev image - uses: docker/build-push-action@v4 - with: - context: . - tags: us.gcr.io/deepsource-dev/swift-format-static:dev - push: true - pull: true - no-cache: true diff --git a/cloudbuild_depl.yaml b/cloudbuild_depl.yaml new file mode 100644 index 000000000..2e1f48d45 --- /dev/null +++ b/cloudbuild_depl.yaml @@ -0,0 +1,17 @@ +# Build +timeout: 20m0s + +steps: + - name: "gcr.io/cloud-builders/docker:20.10.14" + args: + - build + - -t + - us.gcr.io/deepsource-production/swift-format:$TAG_NAME + - -t + - us.gcr.io/deepsource-production/swift-format:latest + - . + +images: ['us.gcr.io/deepsource-production/swift-format:$TAG_NAME', 'us.gcr.io/deepsource-production/swift-format:latest'] + +options: + machineType: 'E2_HIGHCPU_32' diff --git a/cloudbuild_depl_dev.yaml b/cloudbuild_depl_dev.yaml new file mode 100644 index 000000000..8e2c576a0 --- /dev/null +++ b/cloudbuild_depl_dev.yaml @@ -0,0 +1,13 @@ +# Build +timeout: 40m0s +steps: + - name: "gcr.io/cloud-builders/docker" + args: + - build + - --tag=us.gcr.io/deepsource-dev/swift-format:dev + - . + +images: ['us.gcr.io/deepsource-dev/swift-format:dev'] + +options: + machineType: 'E2_HIGHCPU_32' From 1c74422ccd2d093a4c864aeeee2dfe16ef5d2069 Mon Sep 17 00:00:00 2001 From: deepak-deepsource Date: Thu, 24 Aug 2023 16:06:52 +0530 Subject: [PATCH 326/332] Add deepsource config --- .deepsource.toml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .deepsource.toml diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 000000000..a47d8d0f1 --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,7 @@ +version = 1 + +[[analyzers]] +name = "swift" + +[[analyzers]] +name = "secrets" \ No newline at end of file From bf917c22d2a5b129ab34a00f773c161ed75889ae Mon Sep 17 00:00:00 2001 From: Deepak Raj H R <104898724+deepak-deepsource@users.noreply.github.com> Date: Thu, 24 Aug 2023 16:09:15 +0530 Subject: [PATCH 327/332] Apply suggestions from code review Signed-off-by: Deepak Raj H R <104898724+deepak-deepsource@users.noreply.github.com> --- .deepsource.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.deepsource.toml b/.deepsource.toml index a47d8d0f1..b77045aeb 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -1,7 +1,5 @@ version = 1 -[[analyzers]] -name = "swift" [[analyzers]] name = "secrets" \ No newline at end of file From 7ceb398cafe7f6f7fb286562163df873fce269be Mon Sep 17 00:00:00 2001 From: Deepak Raj H R <104898724+deepak-deepsource@users.noreply.github.com> Date: Thu, 24 Aug 2023 16:23:03 +0530 Subject: [PATCH 328/332] Apply suggestions from code review Signed-off-by: Deepak Raj H R <104898724+deepak-deepsource@users.noreply.github.com> --- cloudbuild_depl.yaml | 2 +- cloudbuild_depl_dev.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudbuild_depl.yaml b/cloudbuild_depl.yaml index 2e1f48d45..b72f8011f 100644 --- a/cloudbuild_depl.yaml +++ b/cloudbuild_depl.yaml @@ -14,4 +14,4 @@ steps: images: ['us.gcr.io/deepsource-production/swift-format:$TAG_NAME', 'us.gcr.io/deepsource-production/swift-format:latest'] options: - machineType: 'E2_HIGHCPU_32' + machineType: 'E2_HIGHCPU_8' diff --git a/cloudbuild_depl_dev.yaml b/cloudbuild_depl_dev.yaml index 8e2c576a0..4d9704cfb 100644 --- a/cloudbuild_depl_dev.yaml +++ b/cloudbuild_depl_dev.yaml @@ -10,4 +10,4 @@ steps: images: ['us.gcr.io/deepsource-dev/swift-format:dev'] options: - machineType: 'E2_HIGHCPU_32' + machineType: 'E2_HIGHCPU_8' From b90827cb1051d0d5379f0c37a37d9963bd7ca334 Mon Sep 17 00:00:00 2001 From: Deepak Raj H R <104898724+deepak-deepsource@users.noreply.github.com> Date: Fri, 25 Aug 2023 07:21:56 +0530 Subject: [PATCH 329/332] Add makefile for tag creation Signed-off-by: Deepak Raj H R <104898724+deepak-deepsource@users.noreply.github.com> --- Makefile | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..0cb4a5537 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +major: + @git pull --tags; \ + IFS='.' read -ra tag <<< "$$(git tag | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ + bump=$$(($${tag[0]} + 1)); \ + ver=v$$bump.0.0; \ + git tag $$ver; \ + echo "Made tag $$ver"; \ + echo "Do this to push it: git push origin $$ver" + +minor: + @git pull --tags; \ + IFS='.' read -ra tag <<< "$$(git tag | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ + bump=$$(($${tag[1]} + 1)); \ + ver=v$${tag[0]}.$$bump.0; \ + git tag $$ver; \ + echo "Made tag $$ver"; \ + echo "Do this to push it: git push origin $$ver" + +patch: + @git pull --tags; \ + IFS='.' read -ra tag <<< "$$(git tag | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ + bump=$$(($${tag[2]} + 1)); \ + ver=v$${tag[0]}.$${tag[1]}.$$bump; \ + git tag $$ver; \ + echo "Made tag $$ver"; \ + echo "Do this to push it: git push origin $$ver" + +enterprise: + @git pull --tags; \ + latest_tag=v"$$(git tag | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ + enterprise_tag="$$latest_tag"-enterprise; \ + git tag $$enterprise_tag $$latest_tag; \ + echo "Made tag $$enterprise_tag"; \ + echo "Do this to push it: git push origin $$enterprise_tag" + +release_major: major + +release_minor: minor + +release_patch: patch From a6293afdac54a83b6e0bd01eb42138782f5e03f2 Mon Sep 17 00:00:00 2001 From: deepak-deepsource Date: Fri, 25 Aug 2023 07:27:47 +0530 Subject: [PATCH 330/332] Make sure only DeepSource tags are used --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 0cb4a5537..2a373a64f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ major: @git pull --tags; \ - IFS='.' read -ra tag <<< "$$(git tag | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ + IFS='.' read -ra tag <<< "$$(git tag | grep '^v' | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ bump=$$(($${tag[0]} + 1)); \ ver=v$$bump.0.0; \ git tag $$ver; \ @@ -9,7 +9,7 @@ major: minor: @git pull --tags; \ - IFS='.' read -ra tag <<< "$$(git tag | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ + IFS='.' read -ra tag <<< "$$(git tag | grep '^v' | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ bump=$$(($${tag[1]} + 1)); \ ver=v$${tag[0]}.$$bump.0; \ git tag $$ver; \ @@ -18,7 +18,7 @@ minor: patch: @git pull --tags; \ - IFS='.' read -ra tag <<< "$$(git tag | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ + IFS='.' read -ra tag <<< "$$(git tag | grep '^v' | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ bump=$$(($${tag[2]} + 1)); \ ver=v$${tag[0]}.$${tag[1]}.$$bump; \ git tag $$ver; \ @@ -27,7 +27,7 @@ patch: enterprise: @git pull --tags; \ - latest_tag=v"$$(git tag | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ + latest_tag=v"$$(git tag | grep '^v' | sed 's/v//gi' | sort -t "." -k1,1nr -k2,2nr -k3,3nr | head -1)"; \ enterprise_tag="$$latest_tag"-enterprise; \ git tag $$enterprise_tag $$latest_tag; \ echo "Made tag $$enterprise_tag"; \ From 24fc8e93127e2d0ee01d4f8c91efb818b7103436 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:39:40 +0530 Subject: [PATCH 331/332] Use newer ubuntu Signed-off-by: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cfe8eeccd..e56e6d65c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM swift:5.8.1-focal as swift-format-builder +FROM swift:5.8.1-jammy as swift-format-builder ADD . /toolbox WORKDIR /toolbox RUN swift build -c release -Xswiftc -static-executable From d4d602ecc2b79eb33183a2367d32283396382e8e Mon Sep 17 00:00:00 2001 From: Anto Christopher Date: Wed, 4 Oct 2023 19:31:03 +0530 Subject: [PATCH 332/332] chore: replace -static-executable with --static-swift-stdlib --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e56e6d65c..2d6f784d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ FROM swift:5.8.1-jammy as swift-format-builder ADD . /toolbox WORKDIR /toolbox -RUN swift build -c release -Xswiftc -static-executable +RUN swift build -c release --static-swift-stdlib