SwiftLint/Source/SwiftLintFramework/Rules/Style/OpeningBraceRule.swift

244 lines
9.4 KiB
Swift

import Foundation
import SourceKittenFramework
private let whitespaceAndNewlineCharacterSet = CharacterSet.whitespacesAndNewlines
private extension SwiftLintFile {
func violatingOpeningBraceRanges(allowMultilineFunc: Bool) -> [(range: NSRange, location: Int)] {
let excludingPattern: String
if allowMultilineFunc {
excludingPattern = #"(?:func[^\{\n]*\n[^\{\n]*\n[^\{]*|(?:\b(?:if|guard|while)\n[^\{]+?\s|\{\s*))\{"#
} else {
excludingPattern = #"(?:\b(?:if|guard|while)\n[^\{]+?\s|\{\s*)\{"#
}
return match(pattern: #"(?:[^( ]|[\s(][\s]+)\{"#,
excludingSyntaxKinds: SyntaxKind.commentAndStringKinds,
excludingPattern: excludingPattern).compactMap {
if isAnonymousClosure(range: $0) {
return nil
}
let braceRange = contents.bridge().range(of: "{", options: .literal, range: $0)
return ($0, braceRange.location)
}
}
func isAnonymousClosure(range: NSRange) -> Bool {
let contentsBridge = contents.bridge()
guard range.location != NSNotFound else {
return false
}
let closureCode = contentsBridge.substring(from: range.location)
guard let closingBracketPosition = closingBracket(closureCode) else {
return false
}
let lengthAfterClosingBracket = closureCode.count - closingBracketPosition - 1
if lengthAfterClosingBracket <= 0 {
return false
}
// First non-whitespace character should be "(" - otherwise it is not an anonymous closure
let afterBracketCode = closureCode.substring(from: closingBracketPosition + 1)
.trimmingCharacters(in: .whitespaces)
return afterBracketCode.first == "("
}
func closingBracket(_ closureCode: String) -> Int? {
var bracketCount = 0
for (index, letter) in closureCode.enumerated() {
if letter == "{" {
bracketCount += 1
} else if letter == "}" {
if bracketCount == 1 {
// The closing bracket found
return index
}
bracketCount -= 1
}
}
return nil
}
}
struct OpeningBraceRule: CorrectableRule, ConfigurationProviderRule {
var configuration = OpeningBraceConfiguration()
init() {}
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: [
Example("func abc() {\n}"),
Example("[].map() { $0 }"),
Example("[].map({ })"),
Example("if let a = b { }"),
Example("while a == b { }"),
Example("guard let a = b else { }"),
Example("if\n\tlet a = b,\n\tlet c = d\n\twhere a == c\n{ }"),
Example("while\n\tlet a = b,\n\tlet c = d\n\twhere a == c\n{ }"),
Example("guard\n\tlet a = b,\n\tlet c = d\n\twhere a == c else\n{ }"),
Example("struct Rule {}\n"),
Example("struct Parent {\n\tstruct Child {\n\t\tlet foo: Int\n\t}\n}\n"),
Example("""
func f(rect: CGRect) {
{
let centre = CGPoint(x: rect.midX, y: rect.midY)
print(centre)
}()
}
"""),
Example("""
func f(rect: CGRect) -> () -> Void {
{
let centre = CGPoint(x: rect.midX, y: rect.midY)
print(centre)
}
}
"""),
Example("""
func f() -> () -> Void {
{}
}
""")
],
triggeringExamples: [
Example("func abc()↓{\n}"),
Example("func abc()\n\t↓{ }"),
Example("func abc(a: A\n\tb: B)\n↓{"),
Example("[].map()↓{ $0 }"),
Example("[].map( ↓{ } )"),
Example("if let a = b↓{ }"),
Example("while a == b↓{ }"),
Example("guard let a = b else↓{ }"),
Example("if\n\tlet a = b,\n\tlet c = d\n\twhere a == c↓{ }"),
Example("while\n\tlet a = b,\n\tlet c = d\n\twhere a == c↓{ }"),
Example("guard\n\tlet a = b,\n\tlet c = d\n\twhere a == c else↓{ }"),
Example("struct Rule↓{}\n"),
Example("struct Rule\n↓{\n}\n"),
Example("struct Rule\n\n\t↓{\n}\n"),
Example("struct Parent {\n\tstruct Child\n\t↓{\n\t\tlet foo: Int\n\t}\n}\n"),
Example("""
// Get the current thread's TLS pointer. On first call for a given thread,
// creates and initializes a new one.
internal static func getPointer()
-> UnsafeMutablePointer<_ThreadLocalStorage>
{ // <- here
return _swift_stdlib_threadLocalStorageGet().assumingMemoryBound(
to: _ThreadLocalStorage.self)
}
"""),
Example("""
func run_Array_method1x(_ N: Int) {
let existentialArray = array!
for _ in 0 ..< N * 100 {
for elt in existentialArray {
if !elt.doIt() {
fatalError("expected true")
}
}
}
}
func run_Array_method2x(_ N: Int) {
}
"""),
Example("""
class TestFile {
func problemFunction() {
#if DEBUG
#endif
}
func openingBraceViolation()
↓{
print("Brackets")
}
}
""")
],
corrections: [
Example("struct Rule↓{}\n"): Example("struct Rule {}\n"),
Example("struct Rule\n↓{\n}\n"): Example("struct Rule {\n}\n"),
Example("struct Rule\n\n\t↓{\n}\n"): Example("struct Rule {\n}\n"),
Example("struct Parent {\n\tstruct Child\n\t↓{\n\t\tlet foo: Int\n\t}\n}\n"):
Example("struct Parent {\n\tstruct Child {\n\t\tlet foo: Int\n\t}\n}\n"),
Example("[].map()↓{ $0 }\n"): Example("[].map() { $0 }\n"),
Example("[].map( ↓{ })\n"): Example("[].map({ })\n"),
Example("if a == b↓{ }\n"): Example("if a == b { }\n"),
Example("if\n\tlet a = b,\n\tlet c = d↓{ }\n"): Example("if\n\tlet a = b,\n\tlet c = d { }\n")
]
)
func validate(file: SwiftLintFile) -> [StyleViolation] {
return file.violatingOpeningBraceRanges(allowMultilineFunc: configuration.allowMultilineFunc).map {
StyleViolation(ruleDescription: Self.description,
severity: configuration.severityConfiguration.severity,
location: Location(file: file, characterOffset: $0.location))
}
}
func correct(file: SwiftLintFile) -> [Correction] {
let violatingRanges = file.violatingOpeningBraceRanges(allowMultilineFunc: configuration.allowMultilineFunc)
.filter {
file.ruleEnabled(violatingRanges: [$0.range], for: self).isNotEmpty
}
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: 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) {
return contents
.replacingCharacters(in: indexRange, with: correctString)
} else {
return contents
}
}
}