1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import SwiftSyntax
/// Empty lines are forbidden after opening braces and before closing braces.
///
/// Lint: Empty lines after opening braces and before closing braces yield a lint error.
///
/// Format: Empty lines after opening braces and before closing braces will be removed.
@_spi(Rules)
public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule {
public override class var isOptIn: Bool { return true }
public override func visit(_ node: AccessorBlockSyntax) -> AccessorBlockSyntax {
var result = node
switch node.accessors {
case .accessors(let accessors):
result.accessors = .init(rewritten(accessors))
case .getter(let getter):
result.accessors = .init(rewritten(getter))
}
result.rightBrace = rewritten(node.rightBrace)
return result
}
public override func visit(_ node: CodeBlockSyntax) -> CodeBlockSyntax {
var result = node
result.statements = rewritten(node.statements)
result.rightBrace = rewritten(node.rightBrace)
return result
}
public override func visit(_ node: MemberBlockSyntax) -> MemberBlockSyntax {
var result = node
result.members = rewritten(node.members)
result.rightBrace = rewritten(node.rightBrace)
return result
}
public override func visit(_ node: ClosureExprSyntax) -> ExprSyntax {
var result = node
result.statements = rewritten(node.statements)
result.rightBrace = rewritten(node.rightBrace)
return ExprSyntax(result)
}
public override func visit(_ node: SwitchExprSyntax) -> ExprSyntax {
var result = node
result.cases = rewritten(node.cases)
result.rightBrace = rewritten(node.rightBrace)
return ExprSyntax(result)
}
public override func visit(_ node: PrecedenceGroupDeclSyntax) -> DeclSyntax {
var result = node
result.attributes = rewritten(node.attributes)
result.rightBrace = rewritten(node.rightBrace)
return DeclSyntax(result)
}
func rewritten(_ token: TokenSyntax) -> TokenSyntax {
let (trimmedLeadingTrivia, count) = token.leadingTrivia.trimmingSuperfluousNewlines(
fromClosingBrace: token.tokenKind == .rightBrace
)
if trimmedLeadingTrivia.sourceLength != token.leadingTriviaLength {
diagnose(.removeEmptyLinesBefore(count), on: token, anchor: .start)
return token.with(\.leadingTrivia, trimmedLeadingTrivia)
} else {
return token
}
}
func rewritten<C: SyntaxCollection>(_ collection: C) -> C {
var result = collection
if let first = collection.first, first.leadingTrivia.containsNewlines,
let index = collection.index(of: first)
{
let (trimmedLeadingTrivia, count) = first.leadingTrivia.trimmingSuperfluousNewlines(fromClosingBrace: false)
if trimmedLeadingTrivia.sourceLength != first.leadingTriviaLength {
diagnose(.removeEmptyLinesAfter(count), on: first, anchor: .leadingTrivia(0))
var first = first
first.leadingTrivia = trimmedLeadingTrivia
result[index] = first
}
}
return rewrite(result).as(C.self)!
}
}
extension Trivia {
func trimmingSuperfluousNewlines(fromClosingBrace: Bool) -> (Trivia, Int) {
var trimmmed = 0
var pendingNewlineCount = 0
let pieces = self.indices.reduce([TriviaPiece]()) { (partialResult, index) in
let piece = self[index]
// Collapse consecutive newlines into a single one
if case .newlines(let count) = piece {
if fromClosingBrace {
if index == self.count - 1 {
// For the last index(newline right before the closing brace), collapse into a single newline
trimmmed += count - 1
return partialResult + [.newlines(1)]
} else {
pendingNewlineCount += count
return partialResult
}
} else {
if let last = partialResult.last, last.isNewline {
trimmmed += count
return partialResult
} else if index == 0 {
// For leading trivia not associated with a closing brace, collapse the first newline into a single one
trimmmed += count - 1
return partialResult + [.newlines(1)]
} else {
return partialResult + [piece]
}
}
}
// Remove spaces/tabs surrounded by newlines
if piece.isSpaceOrTab, index > 0, index < self.count - 1, self[index - 1].isNewline, self[index + 1].isNewline {
return partialResult
}
// Handle pending newlines if there are any
if pendingNewlineCount > 0 {
if index < self.count - 1 {
let newlines = TriviaPiece.newlines(pendingNewlineCount)
pendingNewlineCount = 0
return partialResult + [newlines] + [piece]
} else {
return partialResult + [.newlines(1)] + [piece]
}
}
// Retain other trivia pieces
return partialResult + [piece]
}
return (Trivia(pieces: pieces), trimmmed)
}
}
extension Finding.Message {
fileprivate static func removeEmptyLinesAfter(_ count: Int) -> Finding.Message {
"remove empty \(count > 1 ? "lines" : "line") after '{'"
}
fileprivate static func removeEmptyLinesBefore(_ count: Int) -> Finding.Message {
"remove empty \(count > 1 ? "lines" : "line") before '}'"
}
}
|