diff --git a/CHANGELOG.md b/CHANGELOG.md index d9844f2b7..8d0d12833 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [0.51.14](https://github.com/nicklockwood/SwiftFormat/releases/tag/0.51.14) (2023-08-05) + +- Fixed regression in `unusedArguments` rule that caused used parameters to be marked unused +- Fixed some additional cases where regex literal was mistaken for `/` operator +- Vertical tab and form feed characters in source file no longer cause spurious errors + ## [0.51.13](https://github.com/nicklockwood/SwiftFormat/releases/tag/0.51.13) (2023-07-18) - Fixed bug where importing a type caused the `redundantSelf` rule to be silently disabled diff --git a/CommandLineTool/swiftformat b/CommandLineTool/swiftformat index 9834f51b9..73be20cd3 100755 Binary files a/CommandLineTool/swiftformat and b/CommandLineTool/swiftformat differ diff --git a/Sources/Formatter.swift b/Sources/Formatter.swift index 6c0426439..01adc287a 100644 --- a/Sources/Formatter.swift +++ b/Sources/Formatter.swift @@ -2,7 +2,7 @@ // Formatter.swift // SwiftFormat // -// Version 0.51.13 +// Version 0.51.14 // // Created by Nick Lockwood on 12/08/2016. // Copyright 2016 Nick Lockwood diff --git a/Sources/Rules.swift b/Sources/Rules.swift index b08f9534b..e21f8096d 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -4083,7 +4083,7 @@ public struct _FormatRules { wasDeclaration = false } let token = formatter.tokens[i] - outer: switch token { + switch token { case .keyword("guard"): isGuard = true case .keyword("let"), .keyword("var"), .keyword("func"), .keyword("for"): @@ -4103,14 +4103,8 @@ public struct _FormatRules { formatter.currentScope(at: i) == .startOfScope("[") { if isDeclaration { - switch formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) { - case .endOfScope(")")?, .operator("=", .infix)?, - .delimiter(",")? where !isConditional || formatter.currentScope(at: i) == .startOfScope("("): - tempLocals.insert(name) - break outer - default: - break - } + tempLocals.insert(name) + break } argNames.remove(at: index) associatedData.remove(at: index) diff --git a/Sources/SwiftFormat.swift b/Sources/SwiftFormat.swift index 11a94e5e2..52d3421d7 100644 --- a/Sources/SwiftFormat.swift +++ b/Sources/SwiftFormat.swift @@ -32,7 +32,7 @@ import Foundation /// The current SwiftFormat version -let swiftFormatVersion = "0.51.13" +let swiftFormatVersion = "0.51.14" public let version = swiftFormatVersion /// The standard SwiftFormat config file name diff --git a/Sources/Tokenizer.swift b/Sources/Tokenizer.swift index 11004c60e..95e14d5aa 100644 --- a/Sources/Tokenizer.swift +++ b/Sources/Tokenizer.swift @@ -2,7 +2,7 @@ // Tokenizer.swift // SwiftFormat // -// Version 0.51.13 +// Version 0.51.14 // // Created by Nick Lockwood on 11/08/2016. // Copyright 2016 Nick Lockwood @@ -551,10 +551,6 @@ extension UnicodeScalar { return false } } - - var isSpaceOrLinebreak: Bool { - isSpace || "\n\r\u{000B}\u{000C}".unicodeScalars.contains(self) - } } // Workaround for horribly slow String.UnicodeScalarView.Subsequence perf @@ -732,7 +728,7 @@ private extension UnicodeScalarView { } mutating func readToEndOfToken() -> String { - readCharacters { !$0.isSpaceOrLinebreak } ?? "" + readCharacters { !$0.isSpace && !"\n\r".unicodeScalars.contains($0) } ?? "" } } @@ -742,18 +738,13 @@ private extension UnicodeScalarView { } mutating func parseLineBreak() -> Token? { - switch first { - case "\r": - removeFirst() + if read("\r") { if read("\n") { return .linebreak("\r\n", 0) } return .linebreak("\r", 0) - case "\n", "\u{000B}", "\u{000C}": - return .linebreak(String(removeFirst()), 0) - default: - return nil } + return read("\n") ? .linebreak("\n", 0) : nil } mutating func parseDelimiter() -> Token? { @@ -1476,10 +1467,8 @@ public func tokenize(_ source: String) -> [Token] { return } guard let prevNonSpaceIndex = index(of: .nonSpaceOrCommentOrLinebreak, before: i) else { - if string == "/" { - tokens[i] = .startOfScope("/") - } else if tokens.count > i + 1 { - tokens[i] = .operator(string, .prefix) + if tokens.count > i + 1 { + tokens[i] = string == "/" ? .startOfScope("/") : .operator(string, .prefix) } return } @@ -1549,8 +1538,11 @@ public func tokenize(_ source: String) -> [Token] { guard let nextNonSpaceToken = index(of: .nonSpaceOrCommentOrLinebreak, after: i).map({ tokens[$0] }) else { + if prevToken.isLvalue { + type = .postfix + break + } if token == .operator("/", .none), - prevToken.isSpaceOrLinebreak || prevNonSpaceToken.isOperator(ofType: .infix) || ( prevNonSpaceToken.isUnwrapOperator && prevNonSpaceIndex > 0 && @@ -1562,9 +1554,6 @@ public func tokenize(_ source: String) -> [Token] { ].contains(prevNonSpaceToken) { tokens[i] = .startOfScope("/") - } else if prevToken.isLvalue { - type = .postfix - break } return } @@ -1875,12 +1864,6 @@ public func tokenize(_ source: String) -> [Token] { token = tokens[count - 1] switch token { case .startOfScope("/"): - if let next = characters.first, next.isSpaceOrLinebreak { - // Misidentified as regex - token = .operator("/", .none) - tokens[count - 1] = token - return - } scopeIndexStack.append(count - 1) let start = characters processStringBody(regex: true, hashCount: 0) diff --git a/SwiftFormat.podspec.json b/SwiftFormat.podspec.json index 4c148c5fa..b64058c71 100644 --- a/SwiftFormat.podspec.json +++ b/SwiftFormat.podspec.json @@ -1,6 +1,6 @@ { "name": "SwiftFormat", - "version": "0.51.13", + "version": "0.51.14", "license": { "type": "MIT", "file": "LICENSE.md" @@ -10,7 +10,7 @@ "authors": "Nick Lockwood", "source": { "git": "https://github.com/nicklockwood/SwiftFormat.git", - "tag": "0.51.13" + "tag": "0.51.14" }, "default_subspecs": "Core", "subspecs": [ diff --git a/SwiftFormat.xcodeproj/project.pbxproj b/SwiftFormat.xcodeproj/project.pbxproj index 2b3e8c80b..f47602619 100644 --- a/SwiftFormat.xcodeproj/project.pbxproj +++ b/SwiftFormat.xcodeproj/project.pbxproj @@ -1111,7 +1111,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 0.51.13; + MARKETING_VERSION = 0.51.14; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.SwiftFormat; @@ -1144,7 +1144,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 0.51.13; + MARKETING_VERSION = 0.51.14; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.SwiftFormat; @@ -1251,7 +1251,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 0.51.13; + MARKETING_VERSION = 0.51.14; PRODUCT_BUNDLE_IDENTIFIER = "com.charcoaldesign.SwiftFormat-for-Xcode"; PRODUCT_NAME = "SwiftFormat for Xcode"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1282,7 +1282,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 0.51.13; + MARKETING_VERSION = 0.51.14; PRODUCT_BUNDLE_IDENTIFIER = "com.charcoaldesign.SwiftFormat-for-Xcode"; PRODUCT_NAME = "SwiftFormat for Xcode"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1310,7 +1310,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 0.51.13; + MARKETING_VERSION = 0.51.14; PRODUCT_BUNDLE_IDENTIFIER = "com.charcoaldesign.SwiftFormat-for-Xcode.SourceEditorExtension"; PRODUCT_NAME = SwiftFormat; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1340,7 +1340,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 0.51.13; + MARKETING_VERSION = 0.51.14; PRODUCT_BUNDLE_IDENTIFIER = "com.charcoaldesign.SwiftFormat-for-Xcode.SourceEditorExtension"; PRODUCT_NAME = SwiftFormat; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Tests/RulesTests+Linebreaks.swift b/Tests/RulesTests+Linebreaks.swift index 04a181de7..30783f194 100644 --- a/Tests/RulesTests+Linebreaks.swift +++ b/Tests/RulesTests+Linebreaks.swift @@ -10,30 +10,52 @@ import XCTest @testable import SwiftFormat class LinebreakTests: RulesTests { - // MARK: - linebreaks + // MARK: - trailingSpace - func testCarriageReturn() { - let input = "foo\rbar" + // truncateBlankLines = true + + func testTrailingSpace() { + let input = "foo \nbar" let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) + testFormatting(for: input, output, rule: FormatRules.trailingSpace) } - func testCarriageReturnLinefeed() { - let input = "foo\r\nbar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) + func testTrailingSpaceAtEndOfFile() { + let input = "foo " + let output = "foo" + testFormatting(for: input, output, rule: FormatRules.trailingSpace) } - func testVerticalTab() { - let input = "foo\u{000B}bar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) + func testTrailingSpaceInMultilineComments() { + let input = "/* foo \n bar */" + let output = "/* foo\n bar */" + testFormatting(for: input, output, rule: FormatRules.trailingSpace) } - func testFormfeed() { - let input = "foo\u{000C}bar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) + func testTrailingSpaceInSingleLineComments() { + let input = "// foo \n// bar " + let output = "// foo\n// bar" + testFormatting(for: input, output, rule: FormatRules.trailingSpace) + } + + func testTruncateBlankLine() { + let input = "foo {\n // bar\n \n // baz\n}" + let output = "foo {\n // bar\n\n // baz\n}" + testFormatting(for: input, output, rule: FormatRules.trailingSpace) + } + + func testTrailingSpaceInArray() { + let input = "let foo = [\n 1,\n \n 2,\n]" + let output = "let foo = [\n 1,\n\n 2,\n]" + testFormatting(for: input, output, rule: FormatRules.trailingSpace, exclude: ["redundantSelf"]) + } + + // truncateBlankLines = false + + func testNoTruncateBlankLine() { + let input = "foo {\n // bar\n \n // baz\n}" + let options = FormatOptions(truncateBlankLines: false) + testFormatting(for: input, rule: FormatRules.trailingSpace, options: options) } // MARK: - consecutiveBlankLines diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index b4755a347..ea3c9b2c6 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -6482,34 +6482,6 @@ class RedundancyTests: RulesTests { testFormatting(for: input, output, rule: FormatRules.unusedArguments) } - func testClosureArgumentUsedInGuardNotRemoved() { - let input = """ - bar(for: quux) { _, _, foo in - guard - let baz = quux.baz, - foo.contains(where: { $0.baz == baz }) - else { - return - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testClosureArgumentUsedInIfNotRemoved() { - let input = """ - foo = { reservations, _ in - if let reservations, eligibleToShow( - reservations, - accountService: accountService - ) { - coordinator.startFlow() - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - // init func testParameterUsedInInit() { @@ -7095,23 +7067,6 @@ class RedundancyTests: RulesTests { testFormatting(for: input, output, rule: FormatRules.unusedArguments) } - func testFunctionArgumentUsedInGuardNotRemoved() { - let input = """ - func scrollViewDidEndDecelerating(_ visibleDayRange: DayRange) { - guard - store.state.request.isIdle, - let nextDayToLoad = store.state.request.nextCursor?.lowerBound, - visibleDayRange.upperBound.distance(to: nextDayToLoad) < 30 - else { - return - } - - store.handle(.loadNext) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - // functions (closure-only) func testNoMarkFunctionArgument() { diff --git a/Tests/RulesTests+Spacing.swift b/Tests/RulesTests+Spacing.swift index 25648992f..604a09a4b 100644 --- a/Tests/RulesTests+Spacing.swift +++ b/Tests/RulesTests+Spacing.swift @@ -1354,54 +1354,6 @@ class SpacingTests: RulesTests { testFormatting(for: input, rule: FormatRules.consecutiveSpaces) } - // MARK: - trailingSpace - - // truncateBlankLines = true - - func testTrailingSpace() { - let input = "foo \nbar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.trailingSpace) - } - - func testTrailingSpaceAtEndOfFile() { - let input = "foo " - let output = "foo" - testFormatting(for: input, output, rule: FormatRules.trailingSpace) - } - - func testTrailingSpaceInMultilineComments() { - let input = "/* foo \n bar */" - let output = "/* foo\n bar */" - testFormatting(for: input, output, rule: FormatRules.trailingSpace) - } - - func testTrailingSpaceInSingleLineComments() { - let input = "// foo \n// bar " - let output = "// foo\n// bar" - testFormatting(for: input, output, rule: FormatRules.trailingSpace) - } - - func testTruncateBlankLine() { - let input = "foo {\n // bar\n \n // baz\n}" - let output = "foo {\n // bar\n\n // baz\n}" - testFormatting(for: input, output, rule: FormatRules.trailingSpace) - } - - func testTrailingSpaceInArray() { - let input = "let foo = [\n 1,\n \n 2,\n]" - let output = "let foo = [\n 1,\n\n 2,\n]" - testFormatting(for: input, output, rule: FormatRules.trailingSpace, exclude: ["redundantSelf"]) - } - - // truncateBlankLines = false - - func testNoTruncateBlankLine() { - let input = "foo {\n // bar\n \n // baz\n}" - let options = FormatOptions(truncateBlankLines: false) - testFormatting(for: input, rule: FormatRules.trailingSpace, options: options) - } - // MARK: - emptyBraces func testLinebreaksRemovedInsideBraces() { diff --git a/Tests/TokenizerTests.swift b/Tests/TokenizerTests.swift index 88e2c11c4..bf35e7ccb 100644 --- a/Tests/TokenizerTests.swift +++ b/Tests/TokenizerTests.swift @@ -219,32 +219,6 @@ class TokenizerTests: XCTestCase { XCTAssertEqual(tokenize(input), output) } - // MARK: Linebreaks - - func testCarriageReturnLinefeed() { - let input = "\r\n" - let output: [Token] = [ - .linebreak("\r\n", 1), - ] - XCTAssertEqual(tokenize(input), output) - } - - func testVerticalTab() { - let input = "\u{000B}" - let output: [Token] = [ - .linebreak("\u{000B}", 1), - ] - XCTAssertEqual(tokenize(input), output) - } - - func testFormfeed() { - let input = "\u{000C}" - let output: [Token] = [ - .linebreak("\u{000C}", 1), - ] - XCTAssertEqual(tokenize(input), output) - } - // MARK: Strings func testEmptyString() { @@ -803,8 +777,8 @@ class TokenizerTests: XCTestCase { XCTAssertEqual(tokenize(input), output) } - func testSingleLineRegexLiteralWithEscapedClosingParen() { - let input = "let regex = /\\)/" + func testSingleLineRegexLiteralPrecededByTry() { + let input = "let regex = try /foo/" let output: [Token] = [ .keyword("let"), .space(" "), @@ -812,52 +786,8 @@ class TokenizerTests: XCTestCase { .space(" "), .operator("=", .infix), .space(" "), - .startOfScope("/"), - .stringBody("\\)"), - .endOfScope("/"), - ] - XCTAssertEqual(tokenize(input), output) - } - - func testSingleLineRegexLiteralWithEscapedClosingParenAtStartOfFile() { - let input = "/\\)/" - let output: [Token] = [ - .startOfScope("/"), - .stringBody("\\)"), - .endOfScope("/"), - ] - XCTAssertEqual(tokenize(input), output) - } - - func testSingleLineRegexLiteralWithEscapedClosingParenAtStartOfLine() { - let input = """ - let a = b - /\\)/ - """ - let output: [Token] = [ - .keyword("let"), - .space(" "), - .identifier("a"), - .space(" "), - .operator("=", .infix), - .space(" "), - .identifier("b"), - .linebreak("\n", 1), - .startOfScope("/"), - .stringBody("\\)"), - .endOfScope("/"), - ] - XCTAssertEqual(tokenize(input), output) - } - - func testSingleLineRegexLiteralPrecededByTry() { - let input = "let regex=try/foo/" - let output: [Token] = [ - .keyword("let"), - .space(" "), - .identifier("regex"), - .operator("=", .infix), .keyword("try"), + .space(" "), .startOfScope("/"), .stringBody("foo"), .endOfScope("/"), @@ -866,14 +796,17 @@ class TokenizerTests: XCTestCase { } func testSingleLineRegexLiteralPrecededByOptionalTry() { - let input = "let regex=try?/foo/" + let input = "let regex = try? /foo/" let output: [Token] = [ .keyword("let"), .space(" "), .identifier("regex"), + .space(" "), .operator("=", .infix), + .space(" "), .keyword("try"), .operator("?", .postfix), + .space(" "), .startOfScope("/"), .stringBody("foo"), .endOfScope("/"), @@ -972,26 +905,6 @@ class TokenizerTests: XCTestCase { XCTAssertEqual(tokenize(input), output) } - func testDivisionFollowedByCommentNotMistakenForRegexLiteral() { - let input = "foo = bar / 100 // baz" - let output: [Token] = [ - .identifier("foo"), - .space(" "), - .operator("=", .infix), - .space(" "), - .identifier("bar"), - .space(" "), - .operator("/", .infix), - .space(" "), - .number("100", .integer), - .space(" "), - .startOfScope("//"), - .space(" "), - .commentBody("baz"), - ] - XCTAssertEqual(tokenize(input), output) - } - func testPrefixPostfixSlashOperatorNotPermitted() { let input = "let x = /0; let y = 1/" let output: [Token] = [