Skip to content

Commit

Permalink
Add unused_control_flow_label rule
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelofabri authored and sjavora committed Mar 9, 2019
1 parent 48d5fc9 commit 17427c4
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
`.last(where: { /* ... */ })` is more efficient.
[Marcelo Fabri](https://github.com/marcelofabri)

* Add `unused_control_flow_label` rule to validate that control flow labels are
used.
[Marcelo Fabri](https://github.com/marcelofabri)
[#2227](https://github.com/realm/SwiftLint/issues/2227)

#### Bug Fixes

* Fix false positives on `first_where` rule when calling `filter` without a
Expand Down
91 changes: 91 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
* [Unneeded Parentheses in Closure Argument](#unneeded-parentheses-in-closure-argument)
* [Untyped Error in Catch](#untyped-error-in-catch)
* [Unused Closure Parameter](#unused-closure-parameter)
* [Unused Control Flow Label](#unused-control-flow-label)
* [Unused Enumerated](#unused-enumerated)
* [Unused Import](#unused-import)
* [Unused Optional Binding](#unused-optional-binding)
Expand Down Expand Up @@ -21539,6 +21540,96 @@ func foo () {



## Unused Control Flow Label

Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
--- | --- | --- | --- | --- | ---
`unused_control_flow_label` | Enabled | No | lint | No | 3.0.0

Unused control flow label should be removed.

### Examples

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

```swift
loop: while true { break loop }
```

```swift
loop: while true { continue loop }
```

```swift
loop:
while true { break loop }
```

```swift
while true { break }
```

```swift
loop: for x in array { break loop }
```

```swift
label: switch number {
case 1: print("1")
case 2: print("2")
default: break label
}
```

```swift
loop: repeat {
if x == 10 {
break loop
}
} while true
```

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

```swift
↓loop: while true { break }
```

```swift
↓loop: while true { break loop1 }
```

```swift
↓loop: while true { break outerLoop }
```

```swift
↓loop: for x in array { break }
```

```swift
↓label: switch number {
case 1: print("1")
case 2: print("2")
default: break
}
```

```swift
↓loop: repeat {
if x == 10 {
break
}
} while true
```

</details>



## Unused Enumerated

Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
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 @@ -146,6 +146,7 @@ public let masterRuleList = RuleList(rules: [
UnneededParenthesesInClosureArgumentRule.self,
UntypedErrorInCatchRule.self,
UnusedClosureParameterRule.self,
UnusedControlFlowLabelRule.self,
UnusedEnumeratedRule.self,
UnusedImportRule.self,
UnusedOptionalBindingRule.self,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import Foundation
import SourceKittenFramework

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

public init() {}

public static let description = RuleDescription(
identifier: "unused_control_flow_label",
name: "Unused Control Flow Label",
description: "Unused control flow label should be removed.",
kind: .lint,
nonTriggeringExamples: [
"loop: while true { break loop }",
"loop: while true { continue loop }",
"loop:\n while true { break loop }",
"while true { break }",
"loop: for x in array { break loop }",
"""
label: switch number {
case 1: print("1")
case 2: print("2")
default: break label
}
""",
"""
loop: repeat {
if x == 10 {
break loop
}
} while true
"""
],
triggeringExamples: [
"↓loop: while true { break }",
"↓loop: while true { break loop1 }",
"↓loop: while true { break outerLoop }",
"↓loop: for x in array { break }",
"""
↓label: switch number {
case 1: print("1")
case 2: print("2")
default: break
}
""",
"""
↓loop: repeat {
if x == 10 {
break
}
} while true
"""
]
)

private static let kinds: Set<StatementKind> = [.if, .for, .forEach, .while, .repeatWhile, .switch]

public func validate(file: File, kind: StatementKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard type(of: self).kinds.contains(kind),
let offset = dictionary.offset, let length = dictionary.length,
case let byteRange = NSRange(location: offset, length: length),
case let tokens = file.syntaxMap.tokens(inByteRange: byteRange),
let firstToken = tokens.first,
SyntaxKind(rawValue: firstToken.type) == .identifier,
case let contents = file.contents.bridge(),
let tokenContent = contents.substring(with: firstToken),
let range = contents.byteRangeToNSRange(start: offset, length: length) else {
return []
}

let pattern = "(?:break|continue)\\s+\(tokenContent)\\b"
guard file.match(pattern: pattern, with: [.keyword, .identifier], range: range).isEmpty else {
return []
}

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

private extension NSString {
func substring(with token: SyntaxToken) -> String? {
return substringWithByteRange(start: token.offset, length: token.length)
}
}
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@
D4D1B9BB1EAC2C910028BE6A /* AccessControlLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D1B9B91EAC2C870028BE6A /* AccessControlLevel.swift */; };
D4D383852145F550000235BD /* StaticOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D383842145F550000235BD /* StaticOperatorRule.swift */; };
D4D5A5FF1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */; };
D4D7320D21E15ED4001C07D9 /* UnusedControlFlowLabelRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D7320C21E15ED4001C07D9 /* UnusedControlFlowLabelRule.swift */; };
D4DA1DF41E17511D0037413D /* CompilerProtocolInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DF31E17511D0037413D /* CompilerProtocolInitRule.swift */; };
D4DA1DFA1E18D6200037413D /* LargeTupleRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DF91E18D6200037413D /* LargeTupleRule.swift */; };
D4DA1DFC1E19CD300037413D /* GenerateDocsCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DFB1E19CD300037413D /* GenerateDocsCommand.swift */; };
Expand Down Expand Up @@ -745,6 +746,7 @@
D4D1B9B91EAC2C870028BE6A /* AccessControlLevel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessControlLevel.swift; sourceTree = "<group>"; };
D4D383842145F550000235BD /* StaticOperatorRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticOperatorRule.swift; sourceTree = "<group>"; };
D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShorthandOperatorRule.swift; sourceTree = "<group>"; };
D4D7320C21E15ED4001C07D9 /* UnusedControlFlowLabelRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedControlFlowLabelRule.swift; sourceTree = "<group>"; };
D4DA1DF31E17511D0037413D /* CompilerProtocolInitRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompilerProtocolInitRule.swift; sourceTree = "<group>"; };
D4DA1DF91E18D6200037413D /* LargeTupleRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LargeTupleRule.swift; sourceTree = "<group>"; };
D4DA1DFB1E19CD300037413D /* GenerateDocsCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateDocsCommand.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1033,6 +1035,7 @@
D40E041B1F46E3B30043BC4E /* SuperfluousDisableCommandRule.swift */,
E88DEA811B0990A700A66CB0 /* TodoRule.swift */,
D40AD0891E032F9700F48C30 /* UnusedClosureParameterRule.swift */,
D4D7320C21E15ED4001C07D9 /* UnusedControlFlowLabelRule.swift */,
8F715B82213B528B00427BD9 /* UnusedImportRule.swift */,
8F6B3153213CDCD100858E44 /* UnusedPrivateDeclarationRule.swift */,
D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */,
Expand Down Expand Up @@ -1920,6 +1923,7 @@
62DADC481FFF0423002B6319 /* PrefixedTopLevelConstantRule.swift in Sources */,
D4130D991E16CC1300242361 /* TypeNameRuleExamples.swift in Sources */,
24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */,
D4D7320D21E15ED4001C07D9 /* UnusedControlFlowLabelRule.swift in Sources */,
6C1D763221A4E69600DEF783 /* Request+DisableSourceKit.swift in Sources */,
47ACC8981E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift in Sources */,
787CDE39208E7D41005F3D2F /* SwitchCaseAlignmentConfiguration.swift in Sources */,
Expand Down
7 changes: 7 additions & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,12 @@ extension UnusedClosureParameterRuleTests {
]
}

extension UnusedControlFlowLabelRuleTests {
static var allTests: [(String, (UnusedControlFlowLabelRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
]
}

extension UnusedEnumeratedRuleTests {
static var allTests: [(String, (UnusedEnumeratedRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
Expand Down Expand Up @@ -1548,6 +1554,7 @@ XCTMain([
testCase(UnneededParenthesesInClosureArgumentRuleTests.allTests),
testCase(UntypedErrorInCatchRuleTests.allTests),
testCase(UnusedClosureParameterRuleTests.allTests),
testCase(UnusedControlFlowLabelRuleTests.allTests),
testCase(UnusedEnumeratedRuleTests.allTests),
testCase(UnusedImportRuleTests.allTests),
testCase(UnusedOptionalBindingRuleTests.allTests),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,12 @@ class UnusedClosureParameterRuleTests: XCTestCase {
}
}

class UnusedControlFlowLabelRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnusedControlFlowLabelRule.description)
}
}

class UnusedEnumeratedRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnusedEnumeratedRule.description)
Expand Down

0 comments on commit 17427c4

Please sign in to comment.