Skip to content

Commit b2b7f05

Browse files
committed
Add rule to insert blank lines in scope
1 parent 5001bf9 commit b2b7f05

4 files changed

Lines changed: 579 additions & 0 deletions

File tree

Sources/Rules.swift

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,120 @@ public struct _FormatRules {
10231023
}
10241024
}
10251025

1026+
public let insertBlankLinesAtScope = FormatRule(
1027+
help: "Insert blank line at start/end of scope class, struct, extension"
1028+
) { formatter in
1029+
1030+
var canInsertSpaceAtStart = false
1031+
var startToken: Token?
1032+
1033+
formatter.forEachToken { i, token in
1034+
switch token {
1035+
case .keyword("class"),
1036+
.keyword("struct"),
1037+
.keyword("extension"):
1038+
canInsertSpaceAtStart = true
1039+
1040+
case .keyword("func"),
1041+
.keyword("var"),
1042+
.keyword("protocol"):
1043+
canInsertSpaceAtStart = false
1044+
1045+
case .startOfScope("{"):
1046+
guard canInsertSpaceAtStart else { return }
1047+
startToken = token
1048+
1049+
guard let linebreakToken = formatter.token(at: i + 2),
1050+
!linebreakToken.isLinebreak
1051+
else { return }
1052+
1053+
guard let endOfScopeToken = formatter.token(at: i + 1),
1054+
endOfScopeToken != .endOfScope("{")
1055+
else { return }
1056+
1057+
formatter.insertLinebreak(at: i + 1)
1058+
1059+
case .endOfScope("}"):
1060+
guard let startToken = startToken,
1061+
token.isEndOfScope(startToken)
1062+
else { return }
1063+
1064+
guard let linebreakToken = formatter.token(at: i - 2),
1065+
!linebreakToken.isLinebreak
1066+
else { return }
1067+
1068+
guard let startOfScopeToken = formatter.token(at: i - 1),
1069+
startOfScopeToken != .startOfScope("{")
1070+
else { return }
1071+
1072+
formatter.insertLinebreak(at: i - 1)
1073+
1074+
default:
1075+
break
1076+
}
1077+
}
1078+
1079+
formatter.forEachToken { i, token in
1080+
switch token {
1081+
case .keyword("class"),
1082+
.keyword("struct"),
1083+
.keyword("extension"):
1084+
guard let startOfScopeIndex = formatter.index(of: .startOfScope("{"), after: i),
1085+
!(formatter.token(at: startOfScopeIndex + 2)?.isLinebreak ?? true)
1086+
else { return }
1087+
1088+
if let wrongToken = formatter.lastToken(before: startOfScopeIndex, where: {
1089+
$0 == .keyword("func") || $0 == .keyword("protocol")
1090+
}),
1091+
let wrongIndex = formatter.index(of: token, before: startOfScopeIndex)
1092+
{
1093+
let startOfScopeLine = formatter.originalLine(at: startOfScopeIndex)
1094+
let funcLine = formatter.originalLine(at: startOfScopeIndex)
1095+
if startOfScopeLine == funcLine { return }
1096+
}
1097+
1098+
formatter.insertLinebreak(at: startOfScopeIndex + 1)
1099+
1100+
case .endOfScope("}"):
1101+
guard let currentScopeToken = formatter.currentScope(at: i),
1102+
let startOfScopeIndex = formatter.index(of: currentScopeToken, before: i),
1103+
let token = formatter.lastToken(before: startOfScopeIndex, where: {
1104+
$0 == .keyword("class") ||
1105+
$0 == .keyword("struct") ||
1106+
$0 == .keyword("extension")
1107+
}),
1108+
let keywordIndex = formatter.index(of: token, before: startOfScopeIndex)
1109+
else { return }
1110+
1111+
let startOfScopeLine = formatter.originalLine(at: startOfScopeIndex)
1112+
let keywordLine = formatter.originalLine(at: keywordIndex)
1113+
1114+
if let wrongToken = formatter.lastToken(before: startOfScopeIndex, where: {
1115+
$0 == .keyword("func") || $0 == .keyword("protocol")
1116+
}),
1117+
let wrongIndex = formatter.index(of: token, before: startOfScopeIndex)
1118+
{
1119+
let funcLine = formatter.originalLine(at: wrongIndex)
1120+
if startOfScopeLine == funcLine { return }
1121+
}
1122+
1123+
guard startOfScopeLine == keywordLine else { return }
1124+
guard !(formatter.token(at: i)?.isLinebreak ?? true) else { return }
1125+
guard let prevEndScopeIndex = formatter.index(of: .endOfScope, before: i) else { return }
1126+
1127+
let isCloseBrace = formatter.token(at: prevEndScopeIndex) == .endOfScope("}")
1128+
let isCorrectEndOfScope = isCloseBrace || !(formatter.token(at: i - 3)?.isLinebreak ?? true)
1129+
1130+
guard isCorrectEndOfScope, prevEndScopeIndex == i - 2 else { return }
1131+
1132+
formatter.insertLinebreak(at: prevEndScopeIndex + 1)
1133+
1134+
default:
1135+
break
1136+
}
1137+
}
1138+
}
1139+
10261140
/// Adds a blank line around MARK: comments
10271141
public let blankLinesAroundMark = FormatRule(
10281142
help: "Insert blank line before and after `MARK:` comments.",

SwiftFormat.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
01F3DF8D1DB9FD3F00454944 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8B1DB9FD3F00454944 /* Options.swift */; };
7373
01F3DF8E1DB9FD3F00454944 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8B1DB9FD3F00454944 /* Options.swift */; };
7474
01F3DF901DBA003E00454944 /* InferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8F1DBA003E00454944 /* InferenceTests.swift */; };
75+
0693EDCA263E277300BAF167 /* RulesTests+BlanklineInScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0693EDC9263E277300BAF167 /* RulesTests+BlanklineInScope.swift */; };
7576
37D828AB24BF77DA0012FC0A /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; };
7677
37D828AC24BF77DA0012FC0A /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
7778
9028F7831DA4B435009FE5B4 /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; };
@@ -210,6 +211,7 @@
210211
01F17E841E258A4900DCD359 /* CommandLineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandLineTests.swift; sourceTree = "<group>"; };
211212
01F3DF8B1DB9FD3F00454944 /* Options.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Options.swift; sourceTree = "<group>"; };
212213
01F3DF8F1DBA003E00454944 /* InferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InferenceTests.swift; sourceTree = "<group>"; };
214+
0693EDC9263E277300BAF167 /* RulesTests+BlanklineInScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+BlanklineInScope.swift"; sourceTree = "<group>"; };
213215
37D828AA24BF77DA0012FC0A /* XcodeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XcodeKit.framework; path = Library/Frameworks/XcodeKit.framework; sourceTree = DEVELOPER_DIR; };
214216
90C4B6CA1DA4B04A009EB000 /* SwiftFormat for Xcode.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftFormat for Xcode.app"; sourceTree = BUILT_PRODUCTS_DIR; };
215217
90C4B6CC1DA4B04A009EB000 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -377,6 +379,7 @@
377379
018E82741D62E730008CA0F8 /* TokenizerTests.swift */,
378380
011A53E921FFAA3A00DD9268 /* VersionTests.swift */,
379381
018A47032200CB870012E41B /* XCTestManifests.swift */,
382+
0693EDC9263E277300BAF167 /* RulesTests+BlanklineInScope.swift */,
380383
);
381384
path = Tests;
382385
sourceTree = "<group>";
@@ -799,6 +802,7 @@
799802
01EF830F25616089003F6F2D /* RulesTests+Syntax.swift in Sources */,
800803
01BBD85E21DAA30700457380 /* GlobsTests.swift in Sources */,
801804
018A47042200CB880012E41B /* XCTestManifests.swift in Sources */,
805+
0693EDCA263E277300BAF167 /* RulesTests+BlanklineInScope.swift in Sources */,
802806
01B3987B1D763424009ADE61 /* FormatterTests.swift in Sources */,
803807
01C209BD2502D71F00E728A2 /* RulesTests+Redundancy.swift in Sources */,
804808
0142F06F1D72FE10007D66CC /* SwiftFormatTests.swift in Sources */,

0 commit comments

Comments
 (0)