Skip to content

Commit

Permalink
Untyped error checks in catch statement
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Metzing committed Feb 19, 2018
1 parent dddb0f6 commit 1fb429d
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 1 deletion.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

#### Enhancements

* None.
* Adds `untyped-error-in-catch-rule` opt-in rule to warn against declaring errors without type check in catch statements.
[Daniel Metzing](https://github.com/dirtydanee)
[#2045](https://github.com/realm/SwiftLint/issues/2045)

#### Bug Fixes

Expand Down
91 changes: 91 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
* [Type Name](#type-name)
* [Unneeded Break in Switch](#unneeded-break-in-switch)
* [Unneeded Parentheses in Closure Argument](#unneeded-parentheses-in-closure-argument)
* [Untyped error in catch rule](#untyped-error-in-catch-rule)
* [Unused Closure Parameter](#unused-closure-parameter)
* [Unused Enumerated](#unused-enumerated)
* [Unused Optional Binding](#unused-optional-binding)
Expand Down Expand Up @@ -16379,6 +16380,96 @@ foo.bar { [weak self] ↓(x, y) in }



## Untyped error in catch rule

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

Catch statements should not declare error variables without type casting.

### Examples

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

```swift
do {
try foo()
} catch {}
```

```swift
do {
try foo()
} catch Error.invalidOperation {
} catch {}
```

```swift
do {
try foo()
} catch let error as MyError {
} catch {}
```

```swift
do {
try foo()
} catch var error as MyError {
} catch {}
```

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

```swift
do {
try foo()
} ↓catch var error {}
```

```swift
do {
try foo()
} ↓catch let error {}
```

```swift
do {
try foo()
} ↓catch let someError {}
```

```swift
do {
try foo()
} ↓catch var someError {}
```

```swift
do {
try foo()
} ↓catch let e {}
```

```swift
do {
try foo()
} ↓catch(let error) {}
```

```swift
do {
try foo()
} ↓catch (let error) {}
```

</details>



## Unused Closure Parameter

Identifier | Enabled by default | Supports autocorrection | Kind
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 @@ -121,6 +121,7 @@ public let masterRuleList = RuleList(rules: [
TypeNameRule.self,
UnneededBreakInSwitchRule.self,
UnneededParenthesesInClosureArgumentRule.self,
UntypedErrorInCatchRule.self,
UnusedClosureParameterRule.self,
UnusedEnumeratedRule.self,
UnusedOptionalBindingRule.self,
Expand Down
78 changes: 78 additions & 0 deletions Source/SwiftLintFramework/Rules/UntypedErrorInCatchRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// UntypedErrorInCatchRule.swift
// SwiftLint
//
// Created by Daniel.Metzing on 17/02/18.
// Copyright © 2018 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct UntypedErrorInCatchRule: OptInRule, ConfigurationProviderRule {

public var configuration = SeverityConfiguration(.warning)

public init() {}

private static let regularExpression = regex(
"catch" + // The catch keyword
"(?:" + // Start of the first non-capturing group
"\\s*" + // Zero or multiple whitespace character
"\\(" + // The `(` character
"?" + // Zero or one occurrence of the previous character
"\\s*" + // Zero or multiple whitespace character
"(?:" + // Start of the alternative non-capturing group
"let" + // `let` keyword
"|" + // OR
"var" + // `var` keyword
")" + // End of the alternative non-capturing group
"\\s+" + // At least one any type of whitespace character
"\\w+" + // At least one any type of word character
"\\s*" + // Zero or multiple whitespace character
"\\)" + // The `)` character
"?" + // Zero or one occurrence of the previous character
")" + // End of the first non-capturing group
"(?:" + // Start of the second non-capturing group
"\\s*" + // Zero or unlimited any whitespace character
")" + // End of the second non-capturing group
"\\{" // Start scope character
)

public static let description = RuleDescription(
identifier: "untyped_error_in_catch",
name: "Untyped error in catch rule",
description: "Catch statements should not declare error variables without type casting.",
kind: .idiomatic,
nonTriggeringExamples: [
"do {\n try foo() \n} catch {}",
"do {\n try foo() \n} catch Error.invalidOperation {\n} catch {}",
"do {\n try foo() \n} catch let error as MyError {\n} catch {}",
"do {\n try foo() \n} catch var error as MyError {\n} catch {}"
],
triggeringExamples: [
"do {\n try foo() \n} ↓catch var error {}",
"do {\n try foo() \n} ↓catch let error {}",
"do {\n try foo() \n} ↓catch let someError {}",
"do {\n try foo() \n} ↓catch var someError {}",
"do {\n try foo() \n} ↓catch let e {}",
"do {\n try foo() \n} ↓catch(let error) {}",
"do {\n try foo() \n} ↓catch (let error) {}"
])

public func validate(file: File) -> [StyleViolation] {
let matchesAndSyntaxKinds = file.matchesAndSyntaxKinds(matching: type(of: self).regularExpression.pattern)
return matchesAndSyntaxKinds.flatMap { match, syntaxKinds in
guard syntaxKinds.contains(.keyword) else {
return nil
}

let location = Location(file: file,
characterOffset: match.range.lowerBound)
return StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: location,
reason: configuration.consoleDescription)
}
}
}
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
02FD8AEF1BFC18D60014BFFB /* ExtendedNSStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */; };
094385011D5D2894009168CF /* WeakDelegateRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094384FF1D5D2382009168CF /* WeakDelegateRule.swift */; };
094385041D5D4F7C009168CF /* PrivateOutletRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094385021D5D4F78009168CF /* PrivateOutletRule.swift */; };
181D9E172038343D001F6887 /* UntypedErrorInCatchRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181D9E162038343D001F6887 /* UntypedErrorInCatchRule.swift */; };
187290721FC37CA50016BEA2 /* YodaConditionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1872906F1FC37A9B0016BEA2 /* YodaConditionRule.swift */; };
1E18574B1EADBA51004F89F7 /* NoExtensionAccessModifierRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifierRule.swift */; };
1E3C2D711EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C2D701EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift */; };
Expand Down Expand Up @@ -365,6 +366,7 @@
02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedNSStringTests.swift; sourceTree = "<group>"; };
094384FF1D5D2382009168CF /* WeakDelegateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDelegateRule.swift; sourceTree = "<group>"; };
094385021D5D4F78009168CF /* PrivateOutletRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOutletRule.swift; sourceTree = "<group>"; };
181D9E162038343D001F6887 /* UntypedErrorInCatchRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UntypedErrorInCatchRule.swift; sourceTree = "<group>"; };
1872906F1FC37A9B0016BEA2 /* YodaConditionRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YodaConditionRule.swift; sourceTree = "<group>"; };
1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifierRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoExtensionAccessModifierRule.swift; sourceTree = "<group>"; };
1E3C2D701EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOverFilePrivateRule.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1142,6 +1144,7 @@
D4130D981E16CC1300242361 /* TypeNameRuleExamples.swift */,
D4EA77C71F817FD200C315FB /* UnneededBreakInSwitchRule.swift */,
D46A317E1F1CEDCD00AF914A /* UnneededParenthesesInClosureArgumentRule.swift */,
181D9E162038343D001F6887 /* UntypedErrorInCatchRule.swift */,
D40AD0891E032F9700F48C30 /* UnusedClosureParameterRule.swift */,
D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */,
92CCB2D61E1EEFA300C8E5A3 /* UnusedOptionalBindingRule.swift */,
Expand Down Expand Up @@ -1492,6 +1495,7 @@
D4C4A34E1DEA877200E0E04C /* FileHeaderRule.swift in Sources */,
6250D32A1ED4DFEB00735129 /* MultilineParametersRule.swift in Sources */,
009E092A1DFEE4DD00B588A7 /* ProhibitedSuperConfiguration.swift in Sources */,
181D9E172038343D001F6887 /* UntypedErrorInCatchRule.swift in Sources */,
47FF3BE11E7C75B600187E6D /* ImplicitlyUnwrappedOptionalRule.swift in Sources */,
623E36F01F3DB1B1002E5B71 /* QuickDiscouragedCallRule.swift in Sources */,
BFF028AE1CBCF8A500B38A9D /* TrailingWhitespaceConfiguration.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Tests/SwiftLintFrameworkTests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,10 @@ class RulesTests: XCTestCase {
verifyRule(UnneededParenthesesInClosureArgumentRule.description)
}

func testUntypedErrorInCatch() {
verifyRule(UntypedErrorInCatchRule.description)
}

func testUnusedClosureParameter() {
verifyRule(UnusedClosureParameterRule.description)
}
Expand Down

0 comments on commit 1fb429d

Please sign in to comment.