forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduction of Yoda condition checking.
This PR aims to implement [realm#1924][1]. [1]: realm#1924
- Loading branch information
Daniel Metzing
committed
Dec 18, 2017
1 parent
755f3ad
commit f8f9c0e
Showing
7 changed files
with
249 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
Source/SwiftLintFramework/Rules/RuleConfigurations/YodaConditionConfiguration.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// | ||
// YodaConditionConfiguration.swift | ||
// SwiftLint | ||
// | ||
// Created by Daniel.Metzing on 02/12/17. | ||
// Copyright © 2017 Realm. All rights reserved. | ||
// | ||
|
||
public struct YodaConditionConfiguration: RuleConfiguration, Equatable { | ||
|
||
private(set) var severityConfiguration = SeverityConfiguration(.warning) | ||
|
||
public var consoleDescription: String { | ||
return severityConfiguration.consoleDescription | ||
} | ||
|
||
public mutating func apply(configuration: Any) throws { | ||
guard let configuration = configuration as? [String: Any] else { | ||
throw ConfigurationError.unknownConfiguration | ||
} | ||
|
||
if let severityString = configuration["severity"] as? String { | ||
try severityConfiguration.apply(configuration: severityString) | ||
} | ||
} | ||
|
||
public static func == (lhs: YodaConditionConfiguration, | ||
rhs: YodaConditionConfiguration) -> Bool { | ||
return lhs.severityConfiguration == rhs.severityConfiguration | ||
} | ||
} |
107 changes: 107 additions & 0 deletions
107
Source/SwiftLintFramework/Rules/YodaConditionRule.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// | ||
// YodaConditionRule.swift | ||
// SwiftLint | ||
// | ||
// Created by Daniel.Metzing on 20/11/17. | ||
// Copyright © 2017 Realm. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import SourceKittenFramework | ||
|
||
public struct YodaConditionRule: ASTRule, OptInRule, ConfigurationProviderRule { | ||
|
||
public var configuration = YodaConditionConfiguration() | ||
|
||
public init() {} | ||
|
||
private static let pattern = "(?<!" + // Starting negative lookbehind | ||
"(" + // First capturing group | ||
"\\+|-|\\*|\\/|%|\\?" + // One of the operators | ||
")" + // Ending negative lookbehind | ||
")" + // End first capturing group | ||
"\\s+" + // Starting with whitespace | ||
"(" + // Second capturing group | ||
"(?:\\\"[\\\"\\w\\ ]+\")" + // Multiple words between quotes | ||
"|" + // OR | ||
"(?:\\d+" + // Number of digits | ||
"(?:\\.\\d*)?)" + // Optionally followed by a dot and any number digits | ||
"|" + // OR | ||
"(nil)" + // `nil` value | ||
")" + // End second capturing group | ||
"\\s+" + // Followed by whitespace | ||
"(" + // Third capturing group | ||
"==|!=|>|<|>=|<=" + // One of comparison operators | ||
")" + // End third capturing group | ||
"\\s+" + // Followed by whitespace | ||
"(" + // Fourth capturing group | ||
"\\w+" + // Number of words | ||
")" // End fourth capturing group | ||
private static let regularExpression = regex(pattern) | ||
private let observedStatements: Set <StatementKind> = [.if, .guard, .while] | ||
|
||
public static let description = RuleDescription( | ||
identifier: "yoda_condition", | ||
name: "Yoda condition rule", | ||
description: "The variable should be placed on the left, the constant on the right of a comparison operator.", | ||
kind: .lint, | ||
nonTriggeringExamples: [ | ||
"if foo == 42 {}\n", | ||
"if foo <= 42.42 {}\n", | ||
"guard foo >= 42 else { return }\n", | ||
"guard foo != \"str str\" else { return }", | ||
"while foo < 10 { }\n", | ||
"while foo > 1 { }\n", | ||
"while foo + 1 == 2", | ||
"if optionalValue?.property ?? 0 == 2", | ||
"if foo == nil" | ||
], | ||
triggeringExamples: [ | ||
"↓if 42 == foo {}\n", | ||
"↓if 42.42 >= foo {}\n", | ||
"↓guard 42 <= foo else { return }\n", | ||
"↓guard \"str str\" != foo else { return }", | ||
"↓while 10 > foo { }", | ||
"↓while 1 < foo { }", | ||
"↓if nil == foo" | ||
]) | ||
|
||
public func validate(file: File, | ||
kind: StatementKind, | ||
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { | ||
|
||
guard observedStatements.contains(kind), | ||
let offset = dictionary.offset, | ||
let length = dictionary.length | ||
else { | ||
return [] | ||
} | ||
|
||
var matches = [NSTextCheckingResult]() | ||
for line in file.lines where line.byteRange.contains(offset) { | ||
matches = YodaConditionRule.regularExpression.matches(in: line.content, | ||
options: NSRegularExpression.MatchingOptions(), | ||
range: NSRange(location: 0, | ||
length: line.content.utf16.count)) | ||
} | ||
|
||
return matches.map { _ -> StyleViolation in | ||
return StyleViolation(ruleDescription: type(of: self).description, | ||
severity: .warning, | ||
location: Location(file: file, | ||
characterOffset: startOffset(of: offset, | ||
with: length, | ||
in: file)), | ||
reason: configuration.consoleDescription) | ||
} | ||
} | ||
|
||
private func startOffset(of offset: Int, with length: Int, in file: File) -> Int { | ||
let range = file.contents.bridge().byteRangeToNSRange(start: offset, length: length) | ||
guard let startOffset = range?.location else { | ||
return offset | ||
} | ||
|
||
return startOffset | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters