Skip to content

Commit

Permalink
Merge commit 'e65437cd7a7a1e97f68ddb62d572952d74fa3876' into swift3.0
Browse files Browse the repository at this point in the history
* commit 'e65437cd7a7a1e97f68ddb62d572952d74fa3876':
  Fixing #878
  `switch_case_on_newline` should ignore trailing comments

# Conflicts:
#	Source/SwiftLintFramework/Rules/SwitchCaseOnNewlineRule.swift
  • Loading branch information
norio-nomura committed Nov 30, 2016
2 parents 1d7fbe1 + e65437c commit 63a2b20
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 7 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@
[Marcelo Fabri](https://github.com/marcelofabri)
[#863](https://github.com/realm/SwiftLint/issues/863)

* `switch_case_on_newline` rule should ignore trailing comments.
[Marcelo Fabri](https://github.com/marcelofabri)
[#874](https://github.com/realm/SwiftLint/issues/874)

* `switch_case_on_newline` rule shouldn't trigger on enums.
[Marcelo Fabri](https://github.com/marcelofabri)
[#878](https://github.com/realm/SwiftLint/issues/878)


## 0.13.0: MakeYourClothesCleanAgain

Expand Down
82 changes: 75 additions & 7 deletions Source/SwiftLintFramework/Rules/SwitchCaseOnNewlineRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import Foundation
import SourceKittenFramework

public struct SwitchCaseOnNewlineRule: ConfigurationProviderRule, Rule, OptInRule {
public let configurationDescription = "N/A"
public var configuration = SeverityConfiguration(.Warning)

public init() { }
Expand All @@ -29,13 +28,20 @@ public struct SwitchCaseOnNewlineRule: ConfigurationProviderRule, Rule, OptInRul
"let x = [key: .default]",
"if case let .someEnum(value) = aFunction([key: 2]) {",
"guard case let .someEnum(value) = aFunction([key: 2]) {",
"for case let .someEnum(value) = aFunction([key: 2]) {"
"for case let .someEnum(value) = aFunction([key: 2]) {",
"case .myCase: // error from network",
"case let .myCase(value) where value > 10:\n return false",
"enum Environment {\n case development\n}",
"enum Environment {\n case development(url: URL)\n}",
"enum Environment {\n case development(url: URL) // staging\n}"
],
triggeringExamples: [
"case 1: return true",
"case let value: return true",
"default: return true",
"case \"a string\": return false"
"case \"a string\": return false",
"case .myCase: return false // error from network",
"case let .myCase(value) where value > 10: return false"
]
)

Expand All @@ -58,14 +64,19 @@ public struct SwitchCaseOnNewlineRule: ConfigurationProviderRule, Rule, OptInRul
}

let line = file.lines[lineNumber - 1]
let lineTokens = file.syntaxMap.tokensIn(line.byteRange).filter(tokenIsKeyword)
let allLineTokens = file.syntaxMap.tokensIn(line.byteRange)
let lineTokens = allLineTokens.filter(tokenIsKeyword)

guard let firstLineToken = lineTokens.first else {
return false
}

let firstTokenInLineString = contentForToken(token: firstLineToken, file: file)
return firstTokenInLineString == tokenString
guard firstTokenInLineString == tokenString else {
return false
}

return isViolation(lineTokens: allLineTokens, file: file, line: line)
}.map {
StyleViolation(ruleDescription: type(of: self).description,
severity: self.configuration.severity,
Expand All @@ -77,8 +88,65 @@ public struct SwitchCaseOnNewlineRule: ConfigurationProviderRule, Rule, OptInRul
return SyntaxKind(rawValue: token.type) == .keyword
}

private func tokenIsComment(token: SyntaxToken) -> Bool {
guard let kind = SyntaxKind(rawValue: token.type) else {
return false
}

return SyntaxKind.commentKinds().contains(kind)
}

private func contentForToken(token: SyntaxToken, file: File) -> String {
return file.contents.substringWithByteRange(start: token.offset,
length: token.length) ?? ""
return contentForRange(start: token.offset, length: token.length, file: file)
}

private func contentForRange(start: Int, length: Int, file: File) -> String {
return file.contents.substringWithByteRange(start: start,
length: length) ?? ""
}

private func trailingComments(tokens: [SyntaxToken]) -> [SyntaxToken] {
var lastWasComment = true
return tokens.reversed().filter { token in
let shouldRemove = lastWasComment && tokenIsComment(token: token)
if !shouldRemove {
lastWasComment = false
}

return shouldRemove
}.reversed()
}

private func isViolation(lineTokens: [SyntaxToken], file: File, line: Line) -> Bool {
let trailingCommentsTokens = trailingComments(tokens: lineTokens)

guard let firstToken = lineTokens.first else {
return false
}

if isEnumCase(file: file, token: firstToken) {
return false
}

guard let firstComment = trailingCommentsTokens.first,
let lastComment = trailingCommentsTokens.last else {
return true
}

let commentsLength = (lastComment.offset + lastComment.length) - firstComment.offset
let line = contentForRange(start: line.byteRange.location,
length: line.byteRange.length - commentsLength, file: file)
let cleaned = line.trimmingCharacters(in: .whitespaces)

return !cleaned.hasSuffix(":")
}

private func isEnumCase(file: File, token: SyntaxToken) -> Bool {
let kinds = file.structure.kindsFor(token.offset).flatMap {
SwiftDeclarationKind(rawValue: $0.kind)
}

// it's a violation unless it's actually an enum case declaration
return kinds.contains(.enumcase)
}
}

0 comments on commit 63a2b20

Please sign in to comment.