mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
b83e0991b9
The MIT license doesn't require that all files be prepended with this licensing or copyright information. Realm confirmed that they're ok with this change. This will enable some companies to contribute to SwiftLint and the date & authorship information will remain accessible via git source control.
110 lines
4.3 KiB
Swift
110 lines
4.3 KiB
Swift
import SourceKittenFramework
|
|
|
|
public struct ControlStatementRule: ConfigurationProviderRule {
|
|
|
|
public var configuration = SeverityConfiguration(.warning)
|
|
|
|
public init() {}
|
|
|
|
public static let description = RuleDescription(
|
|
identifier: "control_statement",
|
|
name: "Control Statement",
|
|
description:
|
|
"if,for,while,do,catch statements shouldn't wrap their conditionals or arguments in parentheses.",
|
|
kind: .style,
|
|
nonTriggeringExamples: [
|
|
"if condition {\n",
|
|
"if (a, b) == (0, 1) {\n",
|
|
"if (a || b) && (c || d) {\n",
|
|
"if (min...max).contains(value) {\n",
|
|
"if renderGif(data) {\n",
|
|
"renderGif(data)\n",
|
|
"for item in collection {\n",
|
|
"for (key, value) in dictionary {\n",
|
|
"for (index, value) in enumerate(array) {\n",
|
|
"for var index = 0; index < 42; index++ {\n",
|
|
"guard condition else {\n",
|
|
"while condition {\n",
|
|
"} while condition {\n",
|
|
"do { ; } while condition {\n",
|
|
"switch foo {\n",
|
|
"do {\n} catch let error as NSError {\n}",
|
|
"foo().catch(all: true) {}"
|
|
],
|
|
triggeringExamples: [
|
|
"↓if (condition) {\n",
|
|
"↓if(condition) {\n",
|
|
"↓if ((a || b) && (c || d)) {\n",
|
|
"↓if ((min...max).contains(value)) {\n",
|
|
"↓for (item in collection) {\n",
|
|
"↓for (var index = 0; index < 42; index++) {\n",
|
|
"↓for(item in collection) {\n",
|
|
"↓for(var index = 0; index < 42; index++) {\n",
|
|
"↓guard (condition) else {\n",
|
|
"↓while (condition) {\n",
|
|
"↓while(condition) {\n",
|
|
"} ↓while (condition) {\n",
|
|
"} ↓while(condition) {\n",
|
|
"do { ; } ↓while(condition) {\n",
|
|
"do { ; } ↓while (condition) {\n",
|
|
"↓switch (foo) {\n",
|
|
"do {\n} ↓catch(let error as NSError) {\n}"
|
|
]
|
|
)
|
|
|
|
public func validate(file: File) -> [StyleViolation] {
|
|
let statements = ["if", "for", "guard", "switch", "while", "catch"]
|
|
let statementPatterns: [String] = statements.map { statement -> String in
|
|
let isGuard = statement == "guard"
|
|
let elsePattern = isGuard ? "else\\s*" : ""
|
|
return "\(statement)\\s*\\([^,{]*\\)\\s*\(elsePattern)\\{"
|
|
}
|
|
return statementPatterns.flatMap { pattern -> [StyleViolation] in
|
|
return file.match(pattern: pattern)
|
|
.filter { match, syntaxKinds -> Bool in
|
|
let matchString = file.contents.substring(from: match.location, length: match.length)
|
|
return !isFalsePositive(matchString, syntaxKind: syntaxKinds.first)
|
|
}
|
|
.filter { match, _ -> Bool in
|
|
let contents = file.contents.bridge()
|
|
guard let byteOffset = contents.NSRangeToByteRange(start: match.location, length: 1)?.location,
|
|
let outerKind = file.structure.kinds(forByteOffset: byteOffset).last else {
|
|
return true
|
|
}
|
|
|
|
return SwiftExpressionKind(rawValue: outerKind.kind) != .call
|
|
}
|
|
.map { match, _ -> StyleViolation in
|
|
return StyleViolation(ruleDescription: type(of: self).description,
|
|
severity: configuration.severity,
|
|
location: Location(file: file, characterOffset: match.location))
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func isFalsePositive(_ content: String, syntaxKind: SyntaxKind?) -> Bool {
|
|
if syntaxKind != .keyword {
|
|
return true
|
|
}
|
|
|
|
guard let lastClosingParenthesePosition = content.lastIndex(of: ")") else {
|
|
return false
|
|
}
|
|
|
|
var depth = 0
|
|
var index = 0
|
|
for char in content {
|
|
if char == ")" {
|
|
if index != lastClosingParenthesePosition && depth == 1 {
|
|
return true
|
|
}
|
|
depth -= 1
|
|
} else if char == "(" {
|
|
depth += 1
|
|
}
|
|
index += 1
|
|
}
|
|
return false
|
|
}
|
|
}
|