Skip to content

Commit af6f7ab

Browse files
committed
Bump the deployment target to macOS 13 🚀
- 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
1 parent b251238 commit af6f7ab

File tree

4 files changed

+71
-58
lines changed

4 files changed

+71
-58
lines changed

‎Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ if buildOnlyTests {
137137
let package = Package(
138138
name: "swift-format",
139139
platforms: [
140-
.macOS("12.0"),
141-
.iOS("13.0"),
140+
.macOS("13.0"),
141+
.iOS("16.0"),
142142
],
143143
products: products,
144144
dependencies: dependencies,

‎Sources/SwiftFormat/Core/RuleMask.swift

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ extension SourceRange {
9797

9898
/// Represents the kind of ignore directive encountered in the source.
9999
enum IgnoreDirective: CustomStringConvertible {
100+
typealias RegexExpression = Regex<(Substring, ruleNames: Substring?)>
101+
100102
/// A node-level directive that disables rules for the following node and its children.
101103
case node
102104
/// A file-level directive that disables rules for the entire file.
@@ -111,10 +113,14 @@ enum IgnoreDirective: CustomStringConvertible {
111113
}
112114
}
113115

114-
/// Regex pattern to match an ignore comment. This pattern supports 0 or more comma delimited rule
115-
/// names. The rule name(s), when present, are in capture group #3.
116-
fileprivate var pattern: String {
117-
return #"^\s*\/\/\s*"# + description + #"((:\s+(([A-z0-9]+[,\s]*)+))?$|\s+$)"#
116+
/// Regex pattern to match an ignore directive comment.
117+
/// - Captures rule names when `:` is present.
118+
///
119+
/// Note: We are using a string-based regex instead of a regex literal (`#/regex/#`)
120+
/// because Windows did not have full support for regex literals until Swift 5.10.
121+
fileprivate func makeRegex() -> RegexExpression {
122+
let pattern = #"^\s*\/\/\s*"# + description + #"(?:\s*:\s*(?<ruleNames>.+))?$"#
123+
return try! Regex(pattern)
118124
}
119125
}
120126

@@ -140,10 +146,10 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor {
140146
private let sourceLocationConverter: SourceLocationConverter
141147

142148
/// Cached regex object for ignoring rules at the node.
143-
private let ignoreRegex: NSRegularExpression
149+
private let ignoreRegex: IgnoreDirective.RegexExpression
144150

145151
/// Cached regex object for ignoring rules at the file.
146-
private let ignoreFileRegex: NSRegularExpression
152+
private let ignoreFileRegex: IgnoreDirective.RegexExpression
147153

148154
/// Stores the source ranges in which all rules are ignored.
149155
var allRulesIgnoredRanges: [SourceRange] = []
@@ -152,8 +158,8 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor {
152158
var ruleMap: [String: [SourceRange]] = [:]
153159

154160
init(sourceLocationConverter: SourceLocationConverter) {
155-
ignoreRegex = try! NSRegularExpression(pattern: IgnoreDirective.node.pattern, options: [])
156-
ignoreFileRegex = try! NSRegularExpression(pattern: IgnoreDirective.file.pattern, options: [])
161+
ignoreRegex = IgnoreDirective.node.makeRegex()
162+
ignoreFileRegex = IgnoreDirective.file.makeRegex()
157163

158164
self.sourceLocationConverter = sourceLocationConverter
159165
super.init(viewMode: .sourceAccurate)
@@ -202,7 +208,7 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor {
202208
private func appendRuleStatus(
203209
from token: TokenSyntax,
204210
of sourceRange: SourceRange,
205-
using regex: NSRegularExpression
211+
using regex: IgnoreDirective.RegexExpression
206212
) -> SyntaxVisitorContinueKind {
207213
let isFirstInFile = token.previousToken(viewMode: .sourceAccurate) == nil
208214
let comments = loneLineComments(in: token.leadingTrivia, isFirstToken: isFirstInFile)
@@ -227,18 +233,15 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor {
227233
/// match, its contents (e.g. list of rule names) are returned.
228234
private func ruleStatusDirectiveMatch(
229235
in text: String,
230-
using regex: NSRegularExpression
236+
using regex: IgnoreDirective.RegexExpression
231237
) -> RuleStatusDirectiveMatch? {
232-
let textRange = NSRange(text.startIndex..<text.endIndex, in: text)
233-
guard let match = regex.firstMatch(in: text, options: [], range: textRange) else {
238+
guard let match = text.firstMatch(of: regex) else {
234239
return nil
235240
}
236-
guard match.numberOfRanges == 5 else { return .all }
237-
let matchRange = match.range(at: 3)
238-
guard matchRange.location != NSNotFound, let ruleNamesRange = Range(matchRange, in: text) else {
241+
guard let matchedRuleNames = match.output.ruleNames else {
239242
return .all
240243
}
241-
let rules = text[ruleNamesRange].split(separator: ",")
244+
let rules = matchedRuleNames.split(separator: ",")
242245
.map { $0.trimmingCharacters(in: .whitespaces) }
243246
.filter { $0.count > 0 }
244247
return .subset(ruleNames: rules)

‎Sources/_SwiftFormatTestSupport/DiagnosingTestCase.swift

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -213,54 +213,45 @@ open class DiagnosingTestCase: XCTestCase {
213213
file: StaticString = #file,
214214
line: UInt = #line
215215
) {
216-
// Use `CollectionDifference` on supported platforms to get `diff`-like line-based output. On
217-
// older platforms, fall back to simple string comparison.
218-
if #available(macOS 10.15, *) {
219-
let actualLines = actual.components(separatedBy: .newlines)
220-
let expectedLines = expected.components(separatedBy: .newlines)
216+
let actualLines = actual.components(separatedBy: .newlines)
217+
let expectedLines = expected.components(separatedBy: .newlines)
221218

222-
let difference = actualLines.difference(from: expectedLines)
223-
if difference.isEmpty { return }
219+
let difference = actualLines.difference(from: expectedLines)
220+
if difference.isEmpty { return }
224221

225-
var result = ""
222+
var result = ""
226223

227-
var insertions = [Int: String]()
228-
var removals = [Int: String]()
224+
var insertions = [Int: String]()
225+
var removals = [Int: String]()
229226

230-
for change in difference {
231-
switch change {
232-
case .insert(let offset, let element, _):
233-
insertions[offset] = element
234-
case .remove(let offset, let element, _):
235-
removals[offset] = element
236-
}
227+
for change in difference {
228+
switch change {
229+
case .insert(let offset, let element, _):
230+
insertions[offset] = element
231+
case .remove(let offset, let element, _):
232+
removals[offset] = element
237233
}
234+
}
238235

239-
var expectedLine = 0
240-
var actualLine = 0
236+
var expectedLine = 0
237+
var actualLine = 0
241238

242-
while expectedLine < expectedLines.count || actualLine < actualLines.count {
243-
if let removal = removals[expectedLine] {
244-
result += "-\(removal)\n"
245-
expectedLine += 1
246-
} else if let insertion = insertions[actualLine] {
247-
result += "+\(insertion)\n"
248-
actualLine += 1
249-
} else {
250-
result += " \(expectedLines[expectedLine])\n"
251-
expectedLine += 1
252-
actualLine += 1
253-
}
239+
while expectedLine < expectedLines.count || actualLine < actualLines.count {
240+
if let removal = removals[expectedLine] {
241+
result += "-\(removal)\n"
242+
expectedLine += 1
243+
} else if let insertion = insertions[actualLine] {
244+
result += "+\(insertion)\n"
245+
actualLine += 1
246+
} else {
247+
result += " \(expectedLines[expectedLine])\n"
248+
expectedLine += 1
249+
actualLine += 1
254250
}
255-
256-
let failureMessage = "Actual output (+) differed from expected output (-):\n\(result)"
257-
let fullMessage = message.isEmpty ? failureMessage : "\(message) - \(failureMessage)"
258-
XCTFail(fullMessage, file: file, line: line)
259-
} else {
260-
// Fall back to simple string comparison on platforms that don't support CollectionDifference.
261-
let failureMessage = "Actual output differed from expected output:"
262-
let fullMessage = message.isEmpty ? failureMessage : "\(message) - \(failureMessage)"
263-
XCTAssertEqual(actual, expected, fullMessage, file: file, line: line)
264251
}
252+
253+
let failureMessage = "Actual output (+) differed from expected output (-):\n\(result)"
254+
let fullMessage = message.isEmpty ? failureMessage : "\(message) - \(failureMessage)"
255+
XCTFail(fullMessage, file: file, line: line)
265256
}
266257
}

‎Tests/SwiftFormatTests/Core/RuleMaskTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,25 @@ final class RuleMaskTests: XCTestCase {
6565
XCTAssertEqual(mask.ruleState("rule2", at: location(ofLine: 8)), .default)
6666
}
6767

68+
func testIgnoreComplexRuleNames() {
69+
let text =
70+
"""
71+
// swift-format-ignore: ru_le, rule!, ru&le, rule?, rule[], rule(), rule;
72+
let a = 123
73+
"""
74+
75+
let mask = createMask(sourceText: text)
76+
77+
XCTAssertEqual(mask.ruleState("ru_le", at: location(ofLine: 2)), .disabled)
78+
XCTAssertEqual(mask.ruleState("rule!", at: location(ofLine: 2)), .disabled)
79+
XCTAssertEqual(mask.ruleState("ru&le", at: location(ofLine: 2)), .disabled)
80+
XCTAssertEqual(mask.ruleState("rule?", at: location(ofLine: 2)), .disabled)
81+
XCTAssertEqual(mask.ruleState("rule[]", at: location(ofLine: 2)), .disabled)
82+
XCTAssertEqual(mask.ruleState("rule()", at: location(ofLine: 2)), .disabled)
83+
XCTAssertEqual(mask.ruleState("rule;", at: location(ofLine: 2)), .disabled)
84+
XCTAssertEqual(mask.ruleState("default", at: location(ofLine: 2)), .default)
85+
}
86+
6887
func testDuplicateNested() {
6988
let text =
7089
"""

0 commit comments

Comments
 (0)