Skip to content

Commit

Permalink
Introduction of Yoda condition checking.
Browse files Browse the repository at this point in the history
This PR aims to implement [realm#1924][1].
[1]: realm#1924
  • Loading branch information
Daniel Metzing committed Dec 18, 2017
1 parent a02d4e1 commit e5d5a22
Show file tree
Hide file tree
Showing 9 changed files with 448 additions and 2 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
* Invalidate cache when Swift patch version changes.
[Norio Nomura](https://github.com/norio-nomura)

* Add `yoda_condition` opt-in rule which warns to avoid Yoda conditions.
[Daniel Metzing](https://github.com/dirtydanee)
[#1924](https://github.com/realm/SwiftLint/issues/1924)

##### Bug Fixes

* None.
Expand Down Expand Up @@ -106,7 +110,10 @@

##### Enhancements

* None.
* Add `private_action` opt-in rule which warns agaist public
@IBAction methods.
[Ornithologist Coder](https://github.com/ornithocoder)
[#1931](https://github.com/realm/SwiftLint/issues/1931)

##### Bug Fixes

Expand Down
219 changes: 219 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
* [Overridden methods call super](#overridden-methods-call-super)
* [Override in Extension](#override-in-extension)
* [Pattern Matching Keywords](#pattern-matching-keywords)
* [Private Actions](#private-actions)
* [Private Outlets](#private-outlets)
* [Private over fileprivate](#private-over-fileprivate)
* [Private Unit Test](#private-unit-test)
Expand Down Expand Up @@ -117,6 +118,7 @@
* [Void Return](#void-return)
* [Weak Delegate](#weak-delegate)
* [XCTFail Message](#xctfail-message)
* [Yoda condition rule](#yoda-condition-rule)
--------

## Array Init
Expand Down Expand Up @@ -8790,6 +8792,146 @@ switch foo {



## Private Actions

Identifier | Enabled by default | Supports autocorrection | Kind
--- | --- | --- | ---
`private_action` | Disabled | No | lint

IBActions should be private.

### Examples

<details>
<summary>Non Triggering Examples</summary>

```swift
class Foo {
@IBAction private func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
struct Foo {
@IBAction private func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
class Foo {
@IBAction fileprivate func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
struct Foo {
@IBAction fileprivate func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
private extension Foo {
@IBAction func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
fileprivate extension Foo {
@IBAction func barButtonTapped(_ sender: UIButton) {}
}

```

</details>
<details>
<summary>Triggering Examples</summary>

```swift
class Foo {
@IBAction ↓func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
struct Foo {
@IBAction ↓func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
class Foo {
@IBAction public ↓func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
struct Foo {
@IBAction public ↓func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
class Foo {
@IBAction internal ↓func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
struct Foo {
@IBAction internal ↓func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
extension Foo {
@IBAction ↓func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
extension Foo {
@IBAction public ↓func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
extension Foo {
@IBAction internal ↓func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
public extension Foo {
@IBAction ↓func barButtonTapped(_ sender: UIButton) {}
}

```

```swift
internal extension Foo {
@IBAction ↓func barButtonTapped(_ sender: UIButton) {}
}

```

</details>



## Private Outlets

Identifier | Enabled by default | Supports autocorrection | Kind
Expand Down Expand Up @@ -16145,3 +16287,80 @@ func testFoo() {
```

</details>



## Yoda condition rule

Identifier | Enabled by default | Supports autocorrection | Kind
--- | --- | --- | ---
`yoda_condition` | Disabled | No | lint

The variable should be placed on the left, the constant on the right of a comparison operator.

### Examples

<details>
<summary>Non Triggering Examples</summary>

```swift
if foo == 42 {}

```

```swift
if foo <= 42.42 {}

```

```swift
guard foo >= 42 else { return }

```

```swift
guard foo != "str str" else { return }
```

```swift
while foo < 10 { }

```

```swift
while foo > 1 { }

```

</details>
<details>
<summary>Triggering Examples</summary>

```swift
↓if 42 == foo {}

```

```swift
↓if 42.42 >= foo {}

```

```swift
↓guard 42 <= foo else { return }

```

```swift
↓guard "str str" != foo else { return }
```

```swift
↓while 10 > foo { }
```

```swift
↓while 1 < foo { }
```

</details>
4 changes: 3 additions & 1 deletion Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public let masterRuleList = RuleList(rules: [
OverriddenSuperCallRule.self,
OverrideInExtensionRule.self,
PatternMatchingKeywordsRule.self,
PrivateActionRule.self,
PrivateOutletRule.self,
PrivateOverFilePrivateRule.self,
PrivateUnitTestRule.self,
Expand Down Expand Up @@ -124,5 +125,6 @@ public let masterRuleList = RuleList(rules: [
VerticalWhitespaceRule.self,
VoidReturnRule.self,
WeakDelegateRule.self,
XCTFailMessageRule.self
XCTFailMessageRule.self,
YodaConditionRule.self
])
64 changes: 64 additions & 0 deletions Source/SwiftLintFramework/Rules/PrivateActionRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// PrivateActionRule.swift
// SwiftLint
//
// Created by Ornithologist Coder on 11/7/17.
// Copyright © 2016 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct PrivateActionRule: ASTRule, OptInRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)

public init() {}

public static let description = RuleDescription(
identifier: "private_action",
name: "Private Actions",
description: "IBActions should be private.",
kind: .lint,
nonTriggeringExamples: [
"class Foo {\n\t@IBAction private func barButtonTapped(_ sender: UIButton) {}\n}\n",
"struct Foo {\n\t@IBAction private func barButtonTapped(_ sender: UIButton) {}\n}\n",
"class Foo {\n\t@IBAction fileprivate func barButtonTapped(_ sender: UIButton) {}\n}\n",
"struct Foo {\n\t@IBAction fileprivate func barButtonTapped(_ sender: UIButton) {}\n}\n",
"private extension Foo {\n\t@IBAction func barButtonTapped(_ sender: UIButton) {}\n}\n",
"fileprivate extension Foo {\n\t@IBAction func barButtonTapped(_ sender: UIButton) {}\n}\n"
],
triggeringExamples: [
"class Foo {\n\t@IBAction ↓func barButtonTapped(_ sender: UIButton) {}\n}\n",
"struct Foo {\n\t@IBAction ↓func barButtonTapped(_ sender: UIButton) {}\n}\n",
"class Foo {\n\t@IBAction public ↓func barButtonTapped(_ sender: UIButton) {}\n}\n",
"struct Foo {\n\t@IBAction public ↓func barButtonTapped(_ sender: UIButton) {}\n}\n",
"class Foo {\n\t@IBAction internal ↓func barButtonTapped(_ sender: UIButton) {}\n}\n",
"struct Foo {\n\t@IBAction internal ↓func barButtonTapped(_ sender: UIButton) {}\n}\n",
"extension Foo {\n\t@IBAction ↓func barButtonTapped(_ sender: UIButton) {}\n}\n",
"extension Foo {\n\t@IBAction public ↓func barButtonTapped(_ sender: UIButton) {}\n}\n",
"extension Foo {\n\t@IBAction internal ↓func barButtonTapped(_ sender: UIButton) {}\n}\n",
"public extension Foo {\n\t@IBAction ↓func barButtonTapped(_ sender: UIButton) {}\n}\n",
"internal extension Foo {\n\t@IBAction ↓func barButtonTapped(_ sender: UIButton) {}\n}\n"
]
)

public func validate(file: File,
kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard
let offset = dictionary.offset,
kind == .functionMethodInstance,
dictionary.enclosedSwiftAttributes.contains("source.decl.attribute.ibaction"),
let controlLevel = dictionary.accessibility.flatMap(AccessControlLevel.init(identifier:)),
controlLevel.isPrivate == false
else {
return []
}

return [
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: offset))
]
}
}
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
}
}
Loading

0 comments on commit e5d5a22

Please sign in to comment.