Files
SwiftFormat/Sources/Rules/BlankLinesBetweenScopes.swift

134 lines
5.3 KiB
Swift

//
// BlankLinesBetweenScopes.swift
// SwiftFormat
//
// Created by Nick Lockwood on 9/7/16.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//
import Foundation
public extension FormatRule {
/// Adds a blank line immediately after a closing brace, unless followed by another closing brace
static let blankLinesBetweenScopes = FormatRule(
help: """
Insert blank line before class, struct, enum, extension, protocol or function
declarations.
""",
sharedOptions: ["linebreaks"]
) { formatter in
var spaceableScopeStack = [true]
var isSpaceableScopeType = false
formatter.forEachToken(onlyWhereEnabled: false) { i, token in
outer: switch token {
case .keyword("class"),
.keyword("actor"),
.keyword("struct"),
.keyword("extension"),
.keyword("enum"):
isSpaceableScopeType =
(formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .keyword("import"))
case .keyword("func"), .keyword("var"):
isSpaceableScopeType = false
case .startOfScope("{"):
spaceableScopeStack.append(isSpaceableScopeType)
isSpaceableScopeType = false
case .endOfScope("}"):
spaceableScopeStack.removeLast()
guard spaceableScopeStack.last == true,
let openingBraceIndex = formatter.index(of: .startOfScope("{"), before: i),
formatter.lastIndex(of: .linebreak, in: openingBraceIndex + 1 ..< i) != nil
else {
// Inline braces
break
}
var i = i
if let nextTokenIndex = formatter.index(of: .nonSpace, after: i, if: {
$0 == .startOfScope("(")
}), let closingParenIndex = formatter.index(of:
.endOfScope(")"), after: nextTokenIndex)
{
i = closingParenIndex
}
guard let nextTokenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i),
formatter.isEnabled, formatter.options.insertBlankLines,
let firstLinebreakIndex = formatter.index(of: .linebreak, in: i + 1 ..< nextTokenIndex),
formatter.index(of: .linebreak, in: firstLinebreakIndex + 1 ..< nextTokenIndex) == nil
else {
break
}
if var nextNonCommentIndex =
formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i)
{
while formatter.tokens[nextNonCommentIndex] == .startOfScope("#if"),
let nextIndex = formatter.index(
of: .nonSpaceOrCommentOrLinebreak,
after: formatter.endOfLine(at: nextNonCommentIndex)
)
{
nextNonCommentIndex = nextIndex
}
switch formatter.tokens[nextNonCommentIndex] {
case .error, .endOfScope,
.operator(".", _), .delimiter(","), .delimiter(":"),
.keyword("else"), .keyword("catch"), .keyword("#else"), .keyword("#elseif"):
break outer
case .keyword("while"):
if let previousBraceIndex = formatter.index(of: .startOfScope("{"), before: i),
formatter.last(.nonSpaceOrCommentOrLinebreak, before: previousBraceIndex)
== .keyword("repeat")
{
break outer
}
default:
if formatter.isLabel(at: nextNonCommentIndex), let colonIndex
= formatter.index(of: .delimiter(":"), after: nextNonCommentIndex),
formatter.next(.nonSpaceOrCommentOrLinebreak, after: colonIndex)
== .startOfScope("{")
{
break outer
}
}
}
switch formatter.tokens[nextTokenIndex] {
case .startOfScope("//"):
if case let .commentBody(body)? = formatter.next(.nonSpace, after: nextTokenIndex),
body.trimmingCharacters(in: .whitespaces).lowercased().hasPrefix("sourcery:")
{
break
}
formatter.insertLinebreak(at: firstLinebreakIndex)
default:
formatter.insertLinebreak(at: firstLinebreakIndex)
}
default:
break
}
}
} examples: {
"""
```diff
func foo() {
// foo
}
func bar() {
// bar
}
var baz: Bool
var quux: Int
func foo() {
// foo
}
+
func bar() {
// bar
}
+
var baz: Bool
var quux: Int
```
"""
}
}