Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[new rule] VerticalWhitespace rule #747

Merged
merged 2 commits into from
Aug 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
`NSRect` constants and constructors to existing rules.
[David Rönnqvist](https://github.com/d-ronnqvist)

* Added Vertical Whitespace Rule.
[J. Cheyo Jimenez](https://github.com/masters3d)
[#548](https://github.com/realm/SwiftLint/issues/548)

* Removed ConditionalBindingCascadeRule.
[J. Cheyo Jimenez](https://github.com/masters3d)
[#701](https://github.com/realm/SwiftLint/issues/701)
Expand Down
3 changes: 2 additions & 1 deletion Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,6 @@ public let masterRuleList = RuleList(rules:
TypeBodyLengthRule.self,
TypeNameRule.self,
ValidDocsRule.self,
VariableNameRule.self
VariableNameRule.self,
VerticalWhitespaceRule.self
)
1 change: 0 additions & 1 deletion Source/SwiftLintFramework/Rules/ClosingBraceRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ public struct ClosingBraceRule: CorrectableRule, ConfigurationProviderRule {
]
)


public func validateFile(file: File) -> [StyleViolation] {
return file.violatingClosingBraceRanges().map {
StyleViolation(ruleDescription: self.dynamicType.description,
Expand Down
168 changes: 168 additions & 0 deletions Source/SwiftLintFramework/Rules/VerticalWhitespaceRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//
// VerticalWhitespaceRule.swift
// SwiftLint
//
// Created by J. Cheyo Jimenez on 2015-05-16.
// Copyright (c) 2016 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

private let descriptionReason = "Limit vertical whitespace to a single empty line."

public struct VerticalWhitespaceRule: CorrectableRule,
ConfigurationProviderRule {

public var configuration = SeverityConfiguration(.Warning)

public init() {}

public static let description = RuleDescription(
identifier: "vertical_whitespace",
name: "Vertical Whitespace",
description: descriptionReason,
nonTriggeringExamples: [
"let abc = 0\n",
"let abc = 0\n\n",
"/* bcs \n\n\n\n*/",
"// bca \n\n",
],
triggeringExamples: [
"let aaaa = 0\n\n\n",
"struct AAAA {}\n\n\n\n",
"class BBBB {}\n\n\n",
],
corrections: [
"let b = 0\n\n\nclass AAA {}\n": "let b = 0\n\nclass AAA {}\n",
"let c = 0\n\n\nlet num = 1\n": "let c = 0\n\nlet num = 1\n",
"// bca \n\n\n": "// bca \n\n",
] // End of line autocorrections are handeled by Trailing Newline Rule.
)

public func validateFile(file: File) -> [StyleViolation] {

let linesSections = validate(file)
if linesSections.isEmpty { return [] }

var violations = [StyleViolation]()
for (eachLastLine, eachSectionCount) in linesSections {

// Skips violation for areas where the rule is disabled
let region = file.regions().filter {
$0.contains(Location(file: file.path, line: eachLastLine.index, character: 0))
}.first
if region?.isRuleDisabled(self) == true {
continue
}

let violation = StyleViolation(ruleDescription: self.dynamicType.description,
severity: configuration.severity,
location: Location(file: file.path,
line: eachLastLine.index ),
reason: descriptionReason
+ " Currently \(eachSectionCount + 1)." )
violations.append(violation)
}

return violations
}

func validate(file: File) -> [(lastLine: Line, linesToRemove: Int)] {

let filteredLines = file.lines.filter {
$0.content.stringByTrimmingCharactersInSet(.whitespaceCharacterSet()).isEmpty
}

if filteredLines.isEmpty { return [] }

var blankLinesSections = [[Line]]()
var lineSection = [Line]()

var previousIndex = 0
for index in 0..<filteredLines.count {
if filteredLines[previousIndex].index + 1 == filteredLines[index].index {
lineSection.append(filteredLines[index])
} else if !lineSection.isEmpty {
blankLinesSections.append(lineSection)
lineSection.removeAll()
}
previousIndex = index
}
if !lineSection.isEmpty {
blankLinesSections.append(lineSection)
}

// matching all accurrences of /* */
let matchMultilineComments = "/\\*(.|[\\r\\n])*?\\*/"
let comments = file.matchPattern(matchMultilineComments)

var result = [(lastLine: Line, linesToRemove: Int)]()
for eachSection in blankLinesSections {
guard let lastLine = eachSection.last else { continue }

// filtering out violations within a multiple comment block
let isSectionInComment = !comments.filter {
(eachRange, _ ) in eachRange.intersectsRange(lastLine.range)
}.isEmpty

if isSectionInComment {
continue // skipping the lines found in multiline comment
} else {
result.append((lastLine, eachSection.count))
}
}

return result

}

public func correctFile(file: File) -> [Correction] {
let linesSections = validate(file)
if linesSections.isEmpty { return [] }

var indexOfLinesToDelete = [Int]()

for eachLine in linesSections {
let start = eachLine.lastLine.index - eachLine.linesToRemove
indexOfLinesToDelete.appendContentsOf(start..<eachLine.lastLine.index)
}

var correctedLines = [String]()
var corrections = [Correction]()
let fileRegions = file.regions()

forLoopCounter: for currentLine in file.lines {

// Doesnt correct lines where rule is disabled
let region = fileRegions.filter {
$0.contains(Location(file: file.path, line: currentLine.index, character: 0))
}.first
if region?.isRuleDisabled(self) == true {
correctedLines.append(currentLine.content)
continue forLoopCounter
}

// by not incling lines in correctedLines, it removes them
if Set(indexOfLinesToDelete).contains(currentLine.index) {
let description = self.dynamicType.description
let location = Location(file: file.path, line: currentLine.index)

//reports every line that is being deleted
corrections.append(Correction(ruleDescription: description, location: location))
continue forLoopCounter
}

// all lines that pass get added to final output file
correctedLines.append(currentLine.content)
}
// converts lines back to file, add trailing line
if !corrections.isEmpty {
file.write(correctedLines.joinWithSeparator("\n") + "\n")
return corrections
}
return []

}

}
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
006ECFC41C44E99E00EF6364 /* LegacyConstantRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */; };
02FD8AEF1BFC18D60014BFFB /* ExtendedNSStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */; };
1EC163521D5992D900DD2928 /* VerticalWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */; };
094385041D5D4F7C009168CF /* PrivateOutletRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094385021D5D4F78009168CF /* PrivateOutletRule.swift */; };
1F11B3CF1C252F23002E8FA8 /* ClosingBraceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */; };
24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E17F701B1481FF008195BE /* File+Cache.swift */; };
Expand Down Expand Up @@ -174,6 +175,7 @@
/* Begin PBXFileReference section */
006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyConstantRule.swift; sourceTree = "<group>"; };
02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedNSStringTests.swift; sourceTree = "<group>"; };
1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalWhitespaceRule.swift; sourceTree = "<group>"; };
094385021D5D4F78009168CF /* PrivateOutletRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOutletRule.swift; sourceTree = "<group>"; };
1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosingBraceRule.swift; sourceTree = "<group>"; };
24E17F701B1481FF008195BE /* File+Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "File+Cache.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -615,6 +617,7 @@
E88DEA911B099B1F00A66CB0 /* TypeNameRule.swift */,
E81CDE701C00FEAA00B430F6 /* ValidDocsRule.swift */,
E88DEA931B099C0900A66CB0 /* VariableNameRule.swift */,
1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */,
);
path = Rules;
sourceTree = "<group>";
Expand Down Expand Up @@ -906,6 +909,7 @@
006ECFC41C44E99E00EF6364 /* LegacyConstantRule.swift in Sources */,
E88DEA731B0984C400A66CB0 /* String+SwiftLint.swift in Sources */,
E88198591BEA95F100333A11 /* LeadingWhitespaceRule.swift in Sources */,
1EC163521D5992D900DD2928 /* VerticalWhitespaceRule.swift in Sources */,
57ED827B1CF656E3002B3513 /* JUnitReporter.swift in Sources */,
24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */,
E80E018F1B92C1350078EB70 /* Region.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Tests/SwiftLintFramework/FunctionBodyLengthRuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class FunctionBodyLengthRuleTests: XCTestCase {
"whitespace: currently spans 41 lines")])

let longerFunctionBodyWithEmptyLines = funcWithBody(
"// swiftlint:disable vertical_whitespace\n" +
Repeat(count: 100, repeatedValue: "\n").joinWithSeparator("")
)
XCTAssertEqual(violations(longerFunctionBodyWithEmptyLines), [])
Expand Down
4 changes: 4 additions & 0 deletions Tests/SwiftLintFramework/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ class RulesTests: XCTestCase {
verifyRule(NestingRule.description)
}

func testVerticalWhitespace() {
verifyRule(VerticalWhitespaceRule.description)
}

func testOpeningBrace() {
verifyRule(OpeningBraceRule.description)
}
Expand Down