Skip to content

Commit

Permalink
Add block_based_kvo rule
Browse files Browse the repository at this point in the history
Fixes #1714.
  • Loading branch information
marcelofabri committed Jul 27, 2017
1 parent 7c5da2f commit 3ff9636
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@
[Marcelo Fabri](https://github.com/marcelofabri)
[#1294](https://github.com/realm/SwiftLint/issues/1294)

* Add `block_based_kvo` rule that enforces the usage of the new block based
KVO API added on Swift 3.2 and later.
[Marcelo Fabri](https://github.com/marcelofabri)
[#1714](https://github.com/realm/SwiftLint/issues/1714)

##### Bug Fixes

* Fix false positive on `redundant_discardable_let` rule when using
Expand Down
46 changes: 45 additions & 1 deletion Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Rules

* [Attributes](#attributes)
* [Block Based KVO](#block-based-kvo)
* [Class Delegate Protocol](#class-delegate-protocol)
* [Closing Brace Spacing](#closing-brace-spacing)
* [Closure End Indentation](#closure-end-indentation)
Expand Down Expand Up @@ -439,6 +440,49 @@ func foo(completionHandler: @escaping () -> Void)



## Block Based KVO

Identifier | Enabled by default | Supports autocorrection | Kind
--- | --- | --- | ---
`block_based_kvo` | Enabled | No | idiomatic

Prefer the new block based KVO API with keypaths when using Swift 3.2 or later.

### Examples

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

```swift
let observer = foo.observe(\.value, options: [.new]) { (foo, change) in
print(change.newValue)
}
```

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

```swift
class Foo: NSObject {
override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {}
}
```

```swift
class Foo: NSObject {
override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: Dictionary<NSKeyValueChangeKey, Any>?,
context: UnsafeMutableRawPointer?) {}
}
```

</details>



## Class Delegate Protocol

Identifier | Enabled by default | Supports autocorrection | Kind
Expand Down Expand Up @@ -9011,7 +9055,7 @@ Identifier | Enabled by default | Supports autocorrection | Kind
--- | --- | --- | ---
`trailing_closure` | Disabled | No | style

Trailing closure syntax should be used whenever possible
Trailing closure syntax should be used whenever possible.

### Examples

Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

public let masterRuleList = RuleList(rules: [
AttributesRule.self,
BlockBasedKVORule.self,
ClassDelegateProtocolRule.self,
ClosingBraceRule.self,
ClosureEndIndentationRule.self,
Expand Down
80 changes: 80 additions & 0 deletions Source/SwiftLintFramework/Rules/BlockBasedKVORule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// BlockBasedKVORule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 07/27/17.
// Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

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

public init() {}

public static let description = RuleDescription(
identifier: "block_based_kvo",
name: "Block Based KVO",
description: "Prefer the new block based KVO API with keypaths when using Swift 3.2 or later.",
kind: .idiomatic,
nonTriggeringExamples: [
"let observer = foo.observe(\\.value, options: [.new]) { (foo, change) in\n" +
" print(change.newValue)\n" +
"}"
],
triggeringExamples: [
"class Foo: NSObject {\n" +
" override ↓func observeValue(forKeyPath keyPath: String?, of object: Any?,\n" +
" change: [NSKeyValueChangeKey : Any]?,\n" +
" context: UnsafeMutableRawPointer?) {}\n" +
"}",
"class Foo: NSObject {\n" +
" override ↓func observeValue(forKeyPath keyPath: String?, of object: Any?,\n" +
" change: Dictionary<NSKeyValueChangeKey, Any>?,\n" +
" context: UnsafeMutableRawPointer?) {}\n" +
"}"
]
)

public func validate(file: File, kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard SwiftVersion.current == .four, kind == .functionMethodInstance,
dictionary.enclosedSwiftAttributes.contains("source.decl.attribute.override"),
dictionary.name == "observeValue(forKeyPath:of:change:context:)",
hasExpectedParamTypes(types: dictionary.enclosedVarParameters.parameterTypes),
let offset = dictionary.offset else {
return []
}

return [
StyleViolation(ruleDescription: type(of: self).description,
location: Location(file: file, byteOffset: offset))
]
}

private func hasExpectedParamTypes(types: [String]) -> Bool {
guard types.count == 4,
types[0] == "String?",
types[1] == "Any?",
types[2] == "[NSKeyValueChangeKey:Any]?" || types[2] == "Dictionary<NSKeyValueChangeKey,Any>?",
types[3] == "UnsafeMutableRawPointer?" else {
return false
}

return true
}
}

private extension Array where Element == [String: SourceKitRepresentable] {
var parameterTypes: [String] {
return flatMap { element in
guard element.kind.flatMap(SwiftDeclarationKind.init) == .varParameter else {
return nil
}

return element.typeName?.replacingOccurrences(of: " ", with: "")
}
}
}
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
D4DAE8BC1DE14E8F00B0AE7A /* NimbleOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */; };
D4DB92251E628898005DE9C1 /* TodoRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB92241E628898005DE9C1 /* TodoRuleTests.swift */; };
D4FBADD01E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4FBADCF1E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift */; };
D4FD4C851F2A260A00DD8AA8 /* BlockBasedKVORule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4FD4C841F2A260A00DD8AA8 /* BlockBasedKVORule.swift */; };
D4FD58B21E12A0200019503C /* LinterCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4FD58B11E12A0200019503C /* LinterCache.swift */; };
D93DA3D11E699E6300809827 /* NestingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93DA3CF1E699E4E00809827 /* NestingConfiguration.swift */; };
DAD3BE4A1D6ECD9500660239 /* PrivateOutletRuleConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD3BE491D6ECD9500660239 /* PrivateOutletRuleConfiguration.swift */; };
Expand Down Expand Up @@ -495,6 +496,7 @@
D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NimbleOperatorRule.swift; sourceTree = "<group>"; };
D4DB92241E628898005DE9C1 /* TodoRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodoRuleTests.swift; sourceTree = "<group>"; };
D4FBADCF1E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorUsageWhitespaceRule.swift; sourceTree = "<group>"; };
D4FD4C841F2A260A00DD8AA8 /* BlockBasedKVORule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockBasedKVORule.swift; sourceTree = "<group>"; };
D4FD58B11E12A0200019503C /* LinterCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinterCache.swift; sourceTree = "<group>"; };
D93DA3CF1E699E4E00809827 /* NestingConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NestingConfiguration.swift; sourceTree = "<group>"; };
DAD3BE491D6ECD9500660239 /* PrivateOutletRuleConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOutletRuleConfiguration.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -898,6 +900,7 @@
D47A510F1DB2DD4800A4CC21 /* AttributesRule.swift */,
D48AE2CB1DFB58C5001C6A4A /* AttributesRulesExamples.swift */,
D4B0228D1E0CC608007E5297 /* ClassDelegateProtocolRule.swift */,
D4FD4C841F2A260A00DD8AA8 /* BlockBasedKVORule.swift */,
1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */,
D43B046A1E075905004016AF /* ClosureEndIndentationRule.swift */,
D47079A81DFDBED000027086 /* ClosureParameterPositionRule.swift */,
Expand Down Expand Up @@ -1408,6 +1411,7 @@
C328A2F71E6759AE00A9E4D7 /* ExplicitTypeInterfaceRule.swift in Sources */,
93E0C3CE1D67BD7F007FA25D /* ConditionalReturnsOnNewlineRule.swift in Sources */,
D43DB1081DC573DA00281215 /* ImplicitGetterRule.swift in Sources */,
D4FD4C851F2A260A00DD8AA8 /* BlockBasedKVORule.swift in Sources */,
7C0C2E7A1D2866CB0076435A /* ExplicitInitRule.swift in Sources */,
E88DEA771B098D0C00A66CB0 /* Rule.swift in Sources */,
D47079AB1DFDCF7A00027086 /* SwiftExpressionKind.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ extension RuleTests {

extension RulesTests {
static var allTests: [(String, (RulesTests) -> () throws -> Void)] = [
("testBlockBasedKVO", testBlockBasedKVO),
("testClassDelegateProtocol", testClassDelegateProtocol),
("testClosingBrace", testClosingBrace),
("testClosureEndIndentation", testClosureEndIndentation),
Expand Down
6 changes: 6 additions & 0 deletions Tests/SwiftLintFrameworkTests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import XCTest
// swiftlint:disable file_length
class RulesTests: XCTestCase {

func testBlockBasedKVO() {
#if swift(>=3.2)
verifyRule(BlockBasedKVORule.description)
#endif
}

func testClassDelegateProtocol() {
verifyRule(ClassDelegateProtocolRule.description)
}
Expand Down

0 comments on commit 3ff9636

Please sign in to comment.