Files
SwiftLint/Source/SwiftLintFramework/Rules/ConditionalBindingCascadeRule.swift
T
JP Simard c66c5c8df0 formatting fixes
found running `swiftlint autocorrect --format`
2016-05-29 14:58:28 -07:00

129 lines
5.2 KiB
Swift

//
// ConditionalBindingCascadeRule.swift
// SwiftLint
//
// Created by Aaron McTavish on 08/01/2016.
// Copyright © 2016 Realm. All rights reserved.
//
import Foundation
import SourceKittenFramework
public struct ConditionalBindingCascadeRule: ASTRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.Warning)
public init() {}
public static let description = RuleDescription(
identifier: "conditional_binding_cascade",
name: "Conditional Binding Cascade",
description: "Repeated `let` statements in conditional binding cascade should be avoided.",
nonTriggeringExamples: [
"if let a = b, c = d {",
"if case .ar(let ao) = a, case .ar(let bo) = b {}",
"if case .ar(let aa) = $0 {",
"if case .ar(let aa, let bb) = $0 {",
"if let a = b, \n c = d {",
"if let a = b, \n c = d \n {",
"if let a = b { if let c = d {",
"if let a = b { let c = d({ foo in ... })",
"guard let a = b, c = d else {",
"guard let a = b where a, let c = d else {",
"guard let foo = someOptional(), var bar = someMutableOptional(foo) else { return }",
],
triggeringExamples: [
"if let a = b, let c = d {",
"if let a = b, \n let c = d {",
"if let a = b, c = d, let e = f {",
"if let a = b, let c = d \n {",
"if \n let a = b, let c = d {",
"if let a = b, c = d.indexOf({$0 == e}), let f = g {",
"guard let a = b, let c = d else {",
"if let a = a, b = b {\ndebugPrint(\"\")\n}\nif let c = a, let d = b {\n}",
// This is a rather exotic case that we cannot correctly identify as
// having too many `let` keywords because of the confounding `case`
// "if case .ar(let aa, let bb) = $0, let c = d, let e = f {",
]
)
private static let kinds = [StatementKind.Guard, .If].map { $0.rawValue }
public func validateFile(file: File, kind: String,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard self.dynamicType.kinds.contains(kind),
let elements = dictionary["key.elements"] as? [SourceKitRepresentable] else {
return []
}
let conditionByteRanges = elements
.filter { $0.kind == "source.lang.swift.structure.elem.condition_expr" }
.flatMap { return $0.byteRange }
let contents = file.contents as NSString
let resultsArray: [[StyleViolation]] = conditionByteRanges.map { conditionByteRange in
let substructureRanges = dictionary.substructure?.flatMap { $0.byteRange }
.filter { NSLocationInRange($0.location, conditionByteRange) } ?? []
let keywordTokensInRange = file.syntaxMap.tokensIn(conditionByteRange)
.filter { token in
// exclude tokens in sub structures
substructureRanges.indexOf { NSLocationInRange(token.offset, $0) } == nil
}
.filter { $0.type == SyntaxKind.Keyword.rawValue }
let byteOffsetAndKeywords = keywordTokensInRange.map {
($0.offset, contents.substringWithByteRange(start: $0.offset, length: $0.length)!)
}
var results = [StyleViolation]()
var previousBindingKeyword = ""
for (byteOffset, keyword) in byteOffsetAndKeywords {
switch keyword {
case "let": fallthrough
case "var":
if previousBindingKeyword == keyword {
results.append(StyleViolation(ruleDescription: self.dynamicType.description,
severity: configuration.severity,
location: Location(file: file, byteOffset: byteOffset)))
} else {
previousBindingKeyword = keyword
}
case "where":
previousBindingKeyword = ""
case "case":
// We cannot correctly differentiate valid uses of more than
// one let when a the case keyword is involved, so if we have
// one then bail rather than emit an invalid warning.
return []
default:
break
}
}
return results
}
return Array(resultsArray.flatten())
}
}
extension SourceKitRepresentable {
var dictionary: [String: SourceKitRepresentable]? {
return self as? [String: SourceKitRepresentable]
}
var substructure: [SourceKitRepresentable]? {
return dictionary?["key.substructure"] as? [SourceKitRepresentable]
}
var int: Int? {
guard let int64 = self as? Int64 else { return nil }
return Int(int64)
}
var kind: String? {
return dictionary?["key.kind"] as? String
}
var byteRange: NSRange? {
guard let byteOffset = dictionary?["key.offset"]?.int,
byteLength = dictionary?["key.length"]?.int else { return nil }
return NSRange(location: byteOffset, length: byteLength)
}
}