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 2, 2017
1 parent
a02d4e1
commit c35197a
Showing
7 changed files
with
222 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 | ||
} | ||
} |
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,95 @@ | ||
// | ||
// 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 = "(" + // First capturing group | ||
"(?:\\\"[\\\"\\w\\ ]+\")" + // Multiple words between quotes | ||
"|" + // OR | ||
"(?:\\d+" + // Number of digits | ||
"(?:\\.\\d*)?)" + // Optionally followed by a dot and any number digits | ||
")" + // End first capturing group | ||
"\\s+" + // Followed by whitespace | ||
"(" + // Second capturing group | ||
"==|!=|>|<|>=|<=" + // One of comparison operators | ||
")" + // End second capturing group | ||
"\\s+" + // Followed by whitespace | ||
"(" + // Third capturing group | ||
"\\w+" + // Number of words | ||
")" // End third 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" | ||
], | ||
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 { }" | ||
]) | ||
|
||
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