Add inert_defer rule

Fixes #2123
This commit is contained in:
Marcelo Fabri
2018-08-26 03:10:16 -07:00
parent 99412bc589
commit 53647b4e6e
7 changed files with 173 additions and 0 deletions
+5
View File
@@ -24,6 +24,11 @@
[Marcelo Fabri](https://github.com/marcelofabri)
[#2365](https://github.com/realm/SwiftLint/pull/2365)
* Add `inert_defer` rule to validate that `defer` is not used at the end of a
scope.
[Marcelo Fabri](https://github.com/marcelofabri)
[#2123](https://github.com/realm/SwiftLint/issues/2123)
#### Bug Fixes
* Fix `comma` rule false positives on object literals (for example, images).
+61
View File
@@ -56,6 +56,7 @@
* [Implicit Getter](#implicit-getter)
* [Implicit Return](#implicit-return)
* [Implicitly Unwrapped Optional](#implicitly-unwrapped-optional)
* [Inert Defer](#inert-defer)
* [Is Disjoint](#is-disjoint)
* [Joined Default Parameter](#joined-default-parameter)
* [Large Tuple](#large-tuple)
@@ -8213,6 +8214,66 @@ func foo(int: Int!) {}
## Inert Defer
Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version
--- | --- | --- | --- | ---
`inert_defer` | Enabled | No | lint | 3.0.0
If defer is at the end of its parent scope, it will be executed right where it is anyway.
### Examples
<details>
<summary>Non Triggering Examples</summary>
```swift
func example3() {
defer { /* deferred code */ }
print("other code")
}
```
```swift
func example4() {
if condition {
defer { /* deferred code */ }
print("other code")
}
}
```
</details>
<details>
<summary>Triggering Examples</summary>
```swift
func example0() {
↓defer { /* deferred code */ }
}
```
```swift
func example1() {
↓defer { /* deferred code */ }
// comment
}
```
```swift
func example2() {
if condition {
↓defer { /* deferred code */ }
// comment
}
}
```
</details>
## Is Disjoint
Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version
@@ -57,6 +57,7 @@ public let masterRuleList = RuleList(rules: [
ImplicitGetterRule.self,
ImplicitReturnRule.self,
ImplicitlyUnwrappedOptionalRule.self,
InertDeferRule.self,
IsDisjointRule.self,
JoinedDefaultParameterRule.self,
LargeTupleRule.self,
@@ -0,0 +1,89 @@
import Foundation
import SourceKittenFramework
public struct InertDeferRule: ConfigurationProviderRule, AutomaticTestableRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
public static let description = RuleDescription(
identifier: "inert_defer",
name: "Inert Defer",
description: "If defer is at the end of its parent scope, it will be executed right where it is anyway.",
kind: .lint,
nonTriggeringExamples: [
"""
func example3() {
defer { /* deferred code */ }
print("other code")
}
""",
"""
func example4() {
if condition {
defer { /* deferred code */ }
print("other code")
}
}
"""
],
triggeringExamples: [
"""
func example0() {
↓defer { /* deferred code */ }
}
""",
"""
func example1() {
↓defer { /* deferred code */ }
// comment
}
""",
"""
func example2() {
if condition {
↓defer { /* deferred code */ }
// comment
}
}
"""
]
)
public func validate(file: File) -> [StyleViolation] {
let defers = file.match(pattern: "defer\\s*\\{", with: [.keyword])
return defers.compactMap { range -> StyleViolation? in
let contents = file.contents.bridge()
guard let byteRange = contents.NSRangeToByteRange(start: range.location, length: range.length),
case let kinds = file.structure.kinds(forByteOffset: byteRange.upperBound),
let brace = kinds.enumerated().lazy.reversed().first(where: isBrace),
brace.offset > kinds.startIndex,
case let outerKindIndex = kinds.index(before: brace.offset),
case let outerKind = kinds[outerKindIndex],
case let braceEnd = brace.element.byteRange.upperBound,
case let tokensRange = NSRange(location: braceEnd, length: outerKind.byteRange.upperBound - braceEnd),
case let tokens = file.syntaxMap.tokens(inByteRange: tokensRange),
!tokens.contains(where: isNotComment) else {
return nil
}
return StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, characterOffset: range.location))
}
}
}
private func isBrace(offset: Int, element: (kind: String, byteRange: NSRange)) -> Bool {
return StatementKind(rawValue: element.kind) == .brace
}
private func isNotComment(token: SyntaxToken) -> Bool {
guard let kind = SyntaxKind(rawValue: token.type) else {
return false
}
return !SyntaxKind.commentKinds.contains(kind)
}
+4
View File
@@ -213,6 +213,7 @@
D44037972132730000FDA77B /* ProhibitedInterfaceBuilderRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44037962132730000FDA77B /* ProhibitedInterfaceBuilderRule.swift */; };
D44254201DB87CA200492EA4 /* ValidIBInspectableRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */; };
D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */; };
D4441A28213279950020896F /* InertDeferRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4441A27213279950020896F /* InertDeferRule.swift */; };
D4470D571EB69225008A1B2E /* ImplicitReturnRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4470D561EB69225008A1B2E /* ImplicitReturnRule.swift */; };
D4470D591EB6B4D1008A1B2E /* EmptyEnumArgumentsRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4470D581EB6B4D1008A1B2E /* EmptyEnumArgumentsRule.swift */; };
D4470D5B1EB76F44008A1B2E /* UnusedOptionalBindingRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4470D5A1EB76F44008A1B2E /* UnusedOptionalBindingRuleTests.swift */; };
@@ -637,6 +638,7 @@
D44037962132730000FDA77B /* ProhibitedInterfaceBuilderRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProhibitedInterfaceBuilderRule.swift; sourceTree = "<group>"; };
D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidIBInspectableRule.swift; sourceTree = "<group>"; };
D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyntacticSugarRule.swift; sourceTree = "<group>"; };
D4441A27213279950020896F /* InertDeferRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InertDeferRule.swift; sourceTree = "<group>"; };
D4470D561EB69225008A1B2E /* ImplicitReturnRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImplicitReturnRule.swift; sourceTree = "<group>"; };
D4470D581EB6B4D1008A1B2E /* EmptyEnumArgumentsRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyEnumArgumentsRule.swift; sourceTree = "<group>"; };
D4470D5A1EB76F44008A1B2E /* UnusedOptionalBindingRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedOptionalBindingRuleTests.swift; sourceTree = "<group>"; };
@@ -947,6 +949,7 @@
E315B83B1DFA4BC500621B44 /* DynamicInlineRule.swift */,
62A3E95B209E078000547A86 /* EmptyXCTestMethodRule.swift */,
626B01B420A1735900D2C42F /* EmptyXCTestMethodRuleExamples.swift */,
D4441A27213279950020896F /* InertDeferRule.swift */,
C26330352073DAA200D7B4FD /* LowerACLThanParentRule.swift */,
856651A61D6B395F005E6B29 /* MarkRule.swift */,
F9E691272091952E0085B53E /* MissingDocsRule.swift */,
@@ -1942,6 +1945,7 @@
D4D5A5FF1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift in Sources */,
C3DE5DAC1E7DF9CA00761483 /* FatalErrorMessageRule.swift in Sources */,
626C16E21F948EBC00BB7475 /* QuickDiscouragedFocusedTestRuleExamples.swift in Sources */,
D4441A28213279950020896F /* InertDeferRule.swift in Sources */,
B89F3BCF1FD5EE1400931E59 /* RequiredEnumCaseRuleConfiguration.swift in Sources */,
D48B51211F4F5DEF0068AB98 /* RuleList+Documentation.swift in Sources */,
8FC9F5111F4B8E48006826C1 /* IsDisjointRule.swift in Sources */,
+7
View File
@@ -531,6 +531,12 @@ extension ImplicitlyUnwrappedOptionalRuleTests {
]
}
extension InertDeferRuleTests {
static var allTests: [(String, (InertDeferRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
]
}
extension IntegrationTests {
static var allTests: [(String, (IntegrationTests) -> () throws -> Void)] = [
("testSwiftLintLints", testSwiftLintLints),
@@ -1306,6 +1312,7 @@ XCTMain([
testCase(ImplicitReturnRuleTests.allTests),
testCase(ImplicitlyUnwrappedOptionalConfigurationTests.allTests),
testCase(ImplicitlyUnwrappedOptionalRuleTests.allTests),
testCase(InertDeferRuleTests.allTests),
testCase(IntegrationTests.allTests),
testCase(IsDisjointRuleTests.allTests),
testCase(JoinedDefaultParameterRuleTests.allTests),
@@ -246,6 +246,12 @@ class ImplicitReturnRuleTests: XCTestCase {
}
}
class InertDeferRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(InertDeferRule.description)
}
}
class IsDisjointRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(IsDisjointRule.description)