mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
fcf848608e
* 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.
134 lines
5.1 KiB
Swift
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]
|
|
}
|
|
}
|