Files
SwiftLint/Source/SwiftLintFramework/Rules/Lint/UnusedControlFlowLabelRule.swift
T
Zev Eisenberg fcf848608e Add Inline test failure messages (#3040)
* Add Example wrapper in order to display test failures inline when running in Xcode.
* Stop using Swift 5.1-only features so we can compile on Xcode 10.2.
* Wrap strings in Example.
* Add Changelog entry.
* Wrap all examples in Example struct.
* Better and more complete capturing of line numbers.
* Fix broken test.
* Better test traceability.
* Address or disable linting warnings.
* Add documentation comments.
* Disable linter for a few cases.
* Limit mutability and add copy-and-mutate utility functions.
* Limit scope of mutability.
2020-02-02 10:35:37 +02:00

134 lines
5.1 KiB
Swift

import Foundation
import SourceKittenFramework
public struct UnusedControlFlowLabelRule: SubstitutionCorrectableASTRule, 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: [
Example("loop: while true { break loop }"),
Example("loop: while true { continue loop }"),
Example("loop:\n while true { break loop }"),
Example("while true { break }"),
Example("loop: for x in array { break loop }"),
Example("""
label: switch number {
case 1: print("1")
case 2: print("2")
default: break label
}
"""),
Example("""
loop: repeat {
if x == 10 {
break loop
}
} while true
""")
],
triggeringExamples: [
Example("↓loop: while true { break }"),
Example("↓loop: while true { break loop1 }"),
Example("↓loop: while true { break outerLoop }"),
Example("↓loop: for x in array { break }"),
Example("""
↓label: switch number {
case 1: print("1")
case 2: print("2")
default: break
}
"""),
Example("""
↓loop: repeat {
if x == 10 {
break
}
} while true
""")
],
corrections: [
Example("↓loop: while true { break }"): Example("while true { break }"),
Example("↓loop: while true { break loop1 }"): Example("while true { break loop1 }"),
Example("↓loop: while true { break outerLoop }"): Example("while true { break outerLoop }"),
Example("↓loop: for x in array { break }"): Example("for x in array { break }"),
Example("""
↓label: switch number {
case 1: print("1")
case 2: print("2")
default: break
}
"""): Example("""
switch number {
case 1: print("1")
case 2: print("2")
default: break
}
"""),
Example("""
↓loop: repeat {
if x == 10 {
break
}
} while true
"""): Example("""
repeat {
if x == 10 {
break
}
} while true
""")
]
)
private static let kinds: Set<StatementKind> = [.if, .for, .forEach, .while, .repeatWhile, .switch]
public func validate(file: SwiftLintFile, kind: StatementKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
return self.violationRanges(in: file, kind: kind, dictionary: dictionary).map { range in
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, characterOffset: range.location))
}
}
public func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? {
var rangeToRemove = violationRange
let contentsNSString = file.stringView
if let byteRange = contentsNSString.NSRangeToByteRange(start: violationRange.location,
length: violationRange.length),
let nextToken = file.syntaxMap.tokens.first(where: { $0.offset > byteRange.location }) {
let nextTokenLocation = contentsNSString.location(fromByteOffset: nextToken.offset)
rangeToRemove.length = nextTokenLocation - violationRange.location
}
return (rangeToRemove, "")
}
public func violationRanges(in file: SwiftLintFile, kind: StatementKind,
dictionary: SourceKittenDictionary) -> [NSRange] {
guard type(of: self).kinds.contains(kind),
let byteRange = dictionary.byteRange,
case let tokens = file.syntaxMap.tokens(inByteRange: byteRange),
let firstToken = tokens.first,
firstToken.kind == .identifier,
let tokenContent = file.contents(for: firstToken),
case let contents = file.stringView,
let range = contents.byteRangeToNSRange(byteRange),
case let pattern = "(?:break|continue)\\s+\(tokenContent)\\b",
file.match(pattern: pattern, with: [.keyword, .identifier], range: range).isEmpty,
let violationRange = contents.byteRangeToNSRange(firstToken.range)
else {
return []
}
return [violationRange]
}
}