mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
137 lines
5.4 KiB
Swift
137 lines
5.4 KiB
Swift
import Foundation
|
|
import SourceKittenFramework
|
|
|
|
private let whitespaceAndNewlineCharacterSet = CharacterSet.whitespacesAndNewlines
|
|
|
|
private extension File {
|
|
func violatingOpeningBraceRanges() -> [(range: NSRange, location: Int)] {
|
|
return match(pattern: "(?:[^( ]|[\\s(][\\s]+)\\{",
|
|
excludingSyntaxKinds: SyntaxKind.commentAndStringKinds,
|
|
excludingPattern: "(?:if|guard|while)\\n[^\\{]+?[\\s\\t\\n]\\{").map {
|
|
let branceRange = contents.bridge().range(of: "{", options: .literal, range: $0)
|
|
return ($0, branceRange.location)
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct OpeningBraceRule: CorrectableRule, ConfigurationProviderRule, AutomaticTestableRule {
|
|
|
|
public var configuration = SeverityConfiguration(.warning)
|
|
|
|
public init() {}
|
|
|
|
public static let description = RuleDescription(
|
|
identifier: "opening_brace",
|
|
name: "Opening Brace Spacing",
|
|
description: "Opening braces should be preceded by a single space and on the same line " +
|
|
"as the declaration.",
|
|
kind: .style,
|
|
nonTriggeringExamples: [
|
|
"func abc() {\n}",
|
|
"[].map() { $0 }",
|
|
"[].map({ })",
|
|
"if let a = b { }",
|
|
"while a == b { }",
|
|
"guard let a = b else { }",
|
|
"if\n\tlet a = b,\n\tlet c = d\n\twhere a == c\n{ }",
|
|
"while\n\tlet a = b,\n\tlet c = d\n\twhere a == c\n{ }",
|
|
"guard\n\tlet a = b,\n\tlet c = d\n\twhere a == c else\n{ }",
|
|
"struct Rule {}\n",
|
|
"struct Parent {\n\tstruct Child {\n\t\tlet foo: Int\n\t}\n}\n"
|
|
],
|
|
triggeringExamples: [
|
|
"func abc()↓{\n}",
|
|
"func abc()\n\t↓{ }",
|
|
"[].map()↓{ $0 }",
|
|
"[].map( ↓{ } )",
|
|
"if let a = b↓{ }",
|
|
"while a == b↓{ }",
|
|
"guard let a = b else↓{ }",
|
|
"if\n\tlet a = b,\n\tlet c = d\n\twhere a == c↓{ }",
|
|
"while\n\tlet a = b,\n\tlet c = d\n\twhere a == c↓{ }",
|
|
"guard\n\tlet a = b,\n\tlet c = d\n\twhere a == c else↓{ }",
|
|
"struct Rule↓{}\n",
|
|
"struct Rule\n↓{\n}\n",
|
|
"struct Rule\n\n\t↓{\n}\n",
|
|
"struct Parent {\n\tstruct Child\n\t↓{\n\t\tlet foo: Int\n\t}\n}\n"
|
|
],
|
|
corrections: [
|
|
"struct Rule↓{}\n": "struct Rule {}\n",
|
|
"struct Rule\n↓{\n}\n": "struct Rule {\n}\n",
|
|
"struct Rule\n\n\t↓{\n}\n": "struct Rule {\n}\n",
|
|
"struct Parent {\n\tstruct Child\n\t↓{\n\t\tlet foo: Int\n\t}\n}\n":
|
|
"struct Parent {\n\tstruct Child {\n\t\tlet foo: Int\n\t}\n}\n",
|
|
"[].map()↓{ $0 }\n": "[].map() { $0 }\n",
|
|
"[].map( ↓{ })\n": "[].map({ })\n",
|
|
"if a == b↓{ }\n": "if a == b { }\n",
|
|
"if\n\tlet a = b,\n\tlet c = d↓{ }\n": "if\n\tlet a = b,\n\tlet c = d { }\n"
|
|
]
|
|
)
|
|
|
|
public func validate(file: File) -> [StyleViolation] {
|
|
return file.violatingOpeningBraceRanges().map {
|
|
StyleViolation(ruleDescription: type(of: self).description,
|
|
severity: configuration.severity,
|
|
location: Location(file: file, characterOffset: $0.location))
|
|
}
|
|
}
|
|
|
|
public func correct(file: File) -> [Correction] {
|
|
let violatingRanges = file.violatingOpeningBraceRanges().filter {
|
|
!file.ruleEnabled(violatingRanges: [$0.range], for: self).isEmpty
|
|
}
|
|
var correctedContents = file.contents
|
|
var adjustedLocations = [Location]()
|
|
|
|
for (violatingRange, location) in violatingRanges.reversed() {
|
|
correctedContents = correct(contents: correctedContents, violatingRange: violatingRange)
|
|
adjustedLocations.insert(Location(file: file, characterOffset: location), at: 0)
|
|
}
|
|
|
|
file.write(correctedContents)
|
|
|
|
return adjustedLocations.map {
|
|
Correction(ruleDescription: type(of: self).description,
|
|
location: $0)
|
|
}
|
|
}
|
|
|
|
private func correct(contents: String,
|
|
violatingRange: NSRange) -> String {
|
|
guard let indexRange = contents.nsrangeToIndexRange(violatingRange) else {
|
|
return contents
|
|
}
|
|
|
|
let capturedString = String(contents[indexRange])
|
|
var adjustedRange = violatingRange
|
|
var correctString = " {"
|
|
|
|
// "struct Command{" has violating string = "d{", so ignore first "d"
|
|
if capturedString.count == 2 &&
|
|
capturedString.rangeOfCharacter(from: whitespaceAndNewlineCharacterSet) == nil {
|
|
adjustedRange = NSRange(
|
|
location: violatingRange.location + 1,
|
|
length: violatingRange.length - 1
|
|
)
|
|
}
|
|
|
|
// "[].map( { } )" has violating string = "( {",
|
|
// so ignore first "(" and use "{" as correction string instead
|
|
if capturedString.hasPrefix("(") {
|
|
adjustedRange = NSRange(
|
|
location: violatingRange.location + 1,
|
|
length: violatingRange.length - 1
|
|
)
|
|
correctString = "{"
|
|
}
|
|
|
|
if let indexRange = contents.nsrangeToIndexRange(adjustedRange) {
|
|
let correctedContents = contents
|
|
.replacingCharacters(in: indexRange, with: correctString)
|
|
return correctedContents
|
|
} else {
|
|
return contents
|
|
}
|
|
}
|
|
}
|