Files
SwiftLint/Source/SwiftLintFramework/Rules/Style/OpeningBraceRule.swift
T
2018-08-05 18:54:05 -07:00

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
}
}
}