From af6f7ab18aafb4e99f91c5516a36cb6c4e24e913 Mon Sep 17 00:00:00 2001 From: TTOzzi Date: Sat, 8 Mar 2025 01:36:49 +0900 Subject: [PATCH] =?UTF-8?q?Bump=20the=20deployment=20target=20to=20macOS?= =?UTF-8?q?=2013=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump the deployment target to macOS 13 - Relax regex restrictions to allow rules containing _ or other special characters - Drop NSRegularExpression and migrate to Swift Regex --- Package.swift | 4 +- Sources/SwiftFormat/Core/RuleMask.swift | 35 ++++----- .../DiagnosingTestCase.swift | 71 ++++++++----------- .../SwiftFormatTests/Core/RuleMaskTests.swift | 19 +++++ 4 files changed, 71 insertions(+), 58 deletions(-) diff --git a/Package.swift b/Package.swift index 74ab7b2c7..7b01e298d 100644 --- a/Package.swift +++ b/Package.swift @@ -137,8 +137,8 @@ if buildOnlyTests { let package = Package( name: "swift-format", platforms: [ - .macOS("12.0"), - .iOS("13.0"), + .macOS("13.0"), + .iOS("16.0"), ], products: products, dependencies: dependencies, diff --git a/Sources/SwiftFormat/Core/RuleMask.swift b/Sources/SwiftFormat/Core/RuleMask.swift index 0c7feaa5a..3ec0b7cdb 100644 --- a/Sources/SwiftFormat/Core/RuleMask.swift +++ b/Sources/SwiftFormat/Core/RuleMask.swift @@ -97,6 +97,8 @@ extension SourceRange { /// Represents the kind of ignore directive encountered in the source. enum IgnoreDirective: CustomStringConvertible { + typealias RegexExpression = Regex<(Substring, ruleNames: Substring?)> + /// A node-level directive that disables rules for the following node and its children. case node /// A file-level directive that disables rules for the entire file. @@ -111,10 +113,14 @@ enum IgnoreDirective: CustomStringConvertible { } } - /// Regex pattern to match an ignore comment. This pattern supports 0 or more comma delimited rule - /// names. The rule name(s), when present, are in capture group #3. - fileprivate var pattern: String { - return #"^\s*\/\/\s*"# + description + #"((:\s+(([A-z0-9]+[,\s]*)+))?$|\s+$)"# + /// Regex pattern to match an ignore directive comment. + /// - Captures rule names when `:` is present. + /// + /// Note: We are using a string-based regex instead of a regex literal (`#/regex/#`) + /// because Windows did not have full support for regex literals until Swift 5.10. + fileprivate func makeRegex() -> RegexExpression { + let pattern = #"^\s*\/\/\s*"# + description + #"(?:\s*:\s*(?.+))?$"# + return try! Regex(pattern) } } @@ -140,10 +146,10 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor { private let sourceLocationConverter: SourceLocationConverter /// Cached regex object for ignoring rules at the node. - private let ignoreRegex: NSRegularExpression + private let ignoreRegex: IgnoreDirective.RegexExpression /// Cached regex object for ignoring rules at the file. - private let ignoreFileRegex: NSRegularExpression + private let ignoreFileRegex: IgnoreDirective.RegexExpression /// Stores the source ranges in which all rules are ignored. var allRulesIgnoredRanges: [SourceRange] = [] @@ -152,8 +158,8 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor { var ruleMap: [String: [SourceRange]] = [:] init(sourceLocationConverter: SourceLocationConverter) { - ignoreRegex = try! NSRegularExpression(pattern: IgnoreDirective.node.pattern, options: []) - ignoreFileRegex = try! NSRegularExpression(pattern: IgnoreDirective.file.pattern, options: []) + ignoreRegex = IgnoreDirective.node.makeRegex() + ignoreFileRegex = IgnoreDirective.file.makeRegex() self.sourceLocationConverter = sourceLocationConverter super.init(viewMode: .sourceAccurate) @@ -202,7 +208,7 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor { private func appendRuleStatus( from token: TokenSyntax, of sourceRange: SourceRange, - using regex: NSRegularExpression + using regex: IgnoreDirective.RegexExpression ) -> SyntaxVisitorContinueKind { let isFirstInFile = token.previousToken(viewMode: .sourceAccurate) == nil let comments = loneLineComments(in: token.leadingTrivia, isFirstToken: isFirstInFile) @@ -227,18 +233,15 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor { /// match, its contents (e.g. list of rule names) are returned. private func ruleStatusDirectiveMatch( in text: String, - using regex: NSRegularExpression + using regex: IgnoreDirective.RegexExpression ) -> RuleStatusDirectiveMatch? { - let textRange = NSRange(text.startIndex.. 0 } return .subset(ruleNames: rules) diff --git a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift index cb4e07267..c5038fe0c 100644 --- a/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift +++ b/Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift @@ -213,54 +213,45 @@ open class DiagnosingTestCase: XCTestCase { file: StaticString = #file, line: UInt = #line ) { - // Use `CollectionDifference` on supported platforms to get `diff`-like line-based output. On - // older platforms, fall back to simple string comparison. - if #available(macOS 10.15, *) { - let actualLines = actual.components(separatedBy: .newlines) - let expectedLines = expected.components(separatedBy: .newlines) + let actualLines = actual.components(separatedBy: .newlines) + let expectedLines = expected.components(separatedBy: .newlines) - let difference = actualLines.difference(from: expectedLines) - if difference.isEmpty { return } + let difference = actualLines.difference(from: expectedLines) + if difference.isEmpty { return } - var result = "" + var result = "" - var insertions = [Int: String]() - var removals = [Int: String]() + var insertions = [Int: String]() + var removals = [Int: String]() - for change in difference { - switch change { - case .insert(let offset, let element, _): - insertions[offset] = element - case .remove(let offset, let element, _): - removals[offset] = element - } + for change in difference { + switch change { + case .insert(let offset, let element, _): + insertions[offset] = element + case .remove(let offset, let element, _): + removals[offset] = element } + } - var expectedLine = 0 - var actualLine = 0 + var expectedLine = 0 + var actualLine = 0 - while expectedLine < expectedLines.count || actualLine < actualLines.count { - if let removal = removals[expectedLine] { - result += "-\(removal)\n" - expectedLine += 1 - } else if let insertion = insertions[actualLine] { - result += "+\(insertion)\n" - actualLine += 1 - } else { - result += " \(expectedLines[expectedLine])\n" - expectedLine += 1 - actualLine += 1 - } + while expectedLine < expectedLines.count || actualLine < actualLines.count { + if let removal = removals[expectedLine] { + result += "-\(removal)\n" + expectedLine += 1 + } else if let insertion = insertions[actualLine] { + result += "+\(insertion)\n" + actualLine += 1 + } else { + result += " \(expectedLines[expectedLine])\n" + expectedLine += 1 + actualLine += 1 } - - let failureMessage = "Actual output (+) differed from expected output (-):\n\(result)" - let fullMessage = message.isEmpty ? failureMessage : "\(message) - \(failureMessage)" - XCTFail(fullMessage, file: file, line: line) - } else { - // Fall back to simple string comparison on platforms that don't support CollectionDifference. - let failureMessage = "Actual output differed from expected output:" - let fullMessage = message.isEmpty ? failureMessage : "\(message) - \(failureMessage)" - XCTAssertEqual(actual, expected, fullMessage, file: file, line: line) } + + let failureMessage = "Actual output (+) differed from expected output (-):\n\(result)" + let fullMessage = message.isEmpty ? failureMessage : "\(message) - \(failureMessage)" + XCTFail(fullMessage, file: file, line: line) } } diff --git a/Tests/SwiftFormatTests/Core/RuleMaskTests.swift b/Tests/SwiftFormatTests/Core/RuleMaskTests.swift index 35773fed0..bfaca5ae0 100644 --- a/Tests/SwiftFormatTests/Core/RuleMaskTests.swift +++ b/Tests/SwiftFormatTests/Core/RuleMaskTests.swift @@ -65,6 +65,25 @@ final class RuleMaskTests: XCTestCase { XCTAssertEqual(mask.ruleState("rule2", at: location(ofLine: 8)), .default) } + func testIgnoreComplexRuleNames() { + let text = + """ + // swift-format-ignore: ru_le, rule!, ru&le, rule?, rule[], rule(), rule; + let a = 123 + """ + + let mask = createMask(sourceText: text) + + XCTAssertEqual(mask.ruleState("ru_le", at: location(ofLine: 2)), .disabled) + XCTAssertEqual(mask.ruleState("rule!", at: location(ofLine: 2)), .disabled) + XCTAssertEqual(mask.ruleState("ru&le", at: location(ofLine: 2)), .disabled) + XCTAssertEqual(mask.ruleState("rule?", at: location(ofLine: 2)), .disabled) + XCTAssertEqual(mask.ruleState("rule[]", at: location(ofLine: 2)), .disabled) + XCTAssertEqual(mask.ruleState("rule()", at: location(ofLine: 2)), .disabled) + XCTAssertEqual(mask.ruleState("rule;", at: location(ofLine: 2)), .disabled) + XCTAssertEqual(mask.ruleState("default", at: location(ofLine: 2)), .default) + } + func testDuplicateNested() { let text = """