SwiftLint/Source/SwiftLintFramework/Rules/Lint/IdenticalOperandsRule.swift

98 lines
4.2 KiB
Swift

import Foundation
import SourceKittenFramework
public struct IdenticalOperandsRule: ConfigurationProviderRule, AutomaticTestableRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
private static let operators = ["==", "!=", "===", "!==", ">", ">=", "<", "<="]
public static let description = RuleDescription(
identifier: "identical_operands",
name: "Identical Operands",
description: "Comparing two identical operands is likely a mistake.",
kind: .lint,
isOptIn: true,
nonTriggeringExamples: operators.flatMap { operation in
[
"1 \(operation) 2",
"foo \(operation) bar",
"prefixedFoo \(operation) foo",
"foo.aProperty \(operation) foo.anotherProperty",
"self.aProperty \(operation) self.anotherProperty",
"\"1 \(operation) 1\"",
"self.aProperty \(operation) aProperty",
"lhs.aProperty \(operation) rhs.aProperty",
"lhs.identifier \(operation) rhs.identifier",
"i \(operation) index",
"$0 \(operation) 0",
"keyValues?.count ?? 0 \(operation) 0"
]
} + [
"func evaluate(_ mode: CommandMode) -> Result<AutoCorrectOptions, CommandantError<CommandantError<()>>>"
],
triggeringExamples: operators.flatMap { operation in
[
"↓1 \(operation) 1",
"↓foo \(operation) foo",
"↓foo.aProperty \(operation) foo.aProperty",
"↓self.aProperty \(operation) self.aProperty",
"↓$0 \(operation) $0"
]
}
)
public func validate(file: File) -> [StyleViolation] {
let operators = type(of: self).operators.joined(separator: "|")
let pattern = "(?<!\\.|\\$)(?:\\s|\\b|\\A)([\\$A-Za-z0-9_\\.]+)\\s*(\(operators))\\s*\\1\\b"
let syntaxKinds = SyntaxKind.commentKinds
let excludingPattern = "\\?\\?\\s*" + pattern
let range = NSRange(location: 0, length: file.contents.utf16.count)
let exclusionRanges = regex(excludingPattern).matches(in: file.contents, options: [],
range: range).map { $0.range }
return file.matchesAndTokens(matching: pattern)
.filter { result, _ in
let range = result.range(at: 1)
return !range.intersects(exclusionRanges)
}
.filter { result, tokens in
let contents = file.contents.bridge()
let range = result.range(at: 1)
guard let byteRange = contents.NSRangeToByteRange(start: range.location,
length: range.length) else {
return false
}
return tokens
.filter { $0.offset >= byteRange.location }
.compactMap { SyntaxKind(rawValue: $0.type) }
.filter(syntaxKinds.contains).isEmpty
}
.compactMap { result, tokens in
return (result, tokens.compactMap { SyntaxKind(rawValue: $0.type) })
}
.compactMap { result, syntaxKinds -> StyleViolation? in
guard Set(syntaxKinds) != [.typeidentifier] else {
return nil
}
let range = result.range(at: 1)
let operatorRange = result.range(at: 2)
let contents = file.contents.bridge()
guard let byteRange = contents.NSRangeToByteRange(start: operatorRange.location,
length: operatorRange.length),
file.syntaxMap.kinds(inByteRange: byteRange).isEmpty else {
return nil
}
return StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, characterOffset: range.location))
}
}
}