mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
137 lines
5.2 KiB
Swift
137 lines
5.2 KiB
Swift
//
|
|
// RedundantStringEnumValueRule.swift
|
|
// SwiftLint
|
|
//
|
|
// Created by Marcelo Fabri on 08/12/16.
|
|
// Copyright © 2016 Realm. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import SourceKittenFramework
|
|
|
|
private func children(of dict: [String: SourceKitRepresentable],
|
|
matching kind: SwiftDeclarationKind) -> [[String: SourceKitRepresentable]] {
|
|
return dict.substructure.flatMap { subDict in
|
|
if let kindString = subDict["key.kind"] as? String,
|
|
SwiftDeclarationKind(rawValue: kindString) == kind {
|
|
return subDict
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public struct RedundantStringEnumValueRule: ASTRule, ConfigurationProviderRule {
|
|
public var configuration = SeverityConfiguration(.warning)
|
|
|
|
public init() {}
|
|
|
|
public static let description = RuleDescription(
|
|
identifier: "redundant_string_enum_value",
|
|
name: "Redudant String Enum Value",
|
|
description: "String enum values can be omitted when they are equal to the enumcase name.",
|
|
nonTriggeringExamples: [
|
|
"enum Numbers: String {\n case one\n case two\n}\n",
|
|
"enum Numbers: Int {\n case one = 1\n case two = 2\n}\n",
|
|
"enum Numbers: String {\n case one = \"ONE\"\n case two = \"TWO\"\n}\n",
|
|
"enum Numbers: String {\n case one = \"ONE\"\n case two = \"two\"\n}\n",
|
|
"enum Numbers: String {\n case one, two\n}\n"
|
|
],
|
|
triggeringExamples: [
|
|
"enum Numbers: String {\n case one = ↓\"one\"\n case two = ↓\"two\"\n}\n",
|
|
"enum Numbers: String {\n case one = ↓\"one\", two = ↓\"two\"\n}\n",
|
|
"enum Numbers: String {\n case one, two = ↓\"two\"\n}\n"
|
|
]
|
|
)
|
|
|
|
public func validateFile(_ file: File,
|
|
kind: SwiftDeclarationKind,
|
|
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
|
|
guard kind == .enum else {
|
|
return []
|
|
}
|
|
|
|
// Check if it's a String enum
|
|
guard dictionary.inheritedTypes.contains("String") else {
|
|
return []
|
|
}
|
|
|
|
let violations = violatingOffsetsForEnum(dictionary: dictionary, file: file)
|
|
return violations.map {
|
|
StyleViolation(ruleDescription: type(of: self).description,
|
|
severity: configuration.severity,
|
|
location: Location(file: file, byteOffset: $0))
|
|
}
|
|
}
|
|
|
|
private func violatingOffsetsForEnum(dictionary: [String: SourceKitRepresentable],
|
|
file: File) -> [Int] {
|
|
var caseCount = 0
|
|
var violations = [Int]()
|
|
|
|
for enumCase in children(of: dictionary, matching: .enumcase) {
|
|
caseCount += enumElementsCount(dictionary: enumCase)
|
|
violations += violatingOffsetsForEnumCase(dictionary: enumCase, file: file)
|
|
}
|
|
|
|
guard violations.count == caseCount else {
|
|
return []
|
|
}
|
|
|
|
return violations
|
|
}
|
|
|
|
private func enumElementsCount(dictionary: [String: SourceKitRepresentable]) -> Int {
|
|
return children(of: dictionary, matching: .enumelement).filter({ element in
|
|
return !filterEnumInits(dictionary: element).isEmpty
|
|
}).count
|
|
}
|
|
|
|
private func violatingOffsetsForEnumCase(dictionary: [String: SourceKitRepresentable],
|
|
file: File) -> [Int] {
|
|
return children(of: dictionary, matching: .enumelement).flatMap { element -> [Int] in
|
|
guard let name = element["key.name"] as? String else {
|
|
return []
|
|
}
|
|
return violatingOffsetsForEnumElement(dictionary: element, name: name, file: file)
|
|
}
|
|
}
|
|
|
|
private func violatingOffsetsForEnumElement(dictionary: [String: SourceKitRepresentable],
|
|
name: String,
|
|
file: File) -> [Int] {
|
|
let enumInits = filterEnumInits(dictionary: dictionary)
|
|
|
|
return enumInits.flatMap { dictionary -> Int? in
|
|
guard let offset = (dictionary["key.offset"] as? Int64).flatMap({ Int($0) }),
|
|
let length = (dictionary["key.length"] as? Int64).flatMap({ Int($0) }) else {
|
|
return nil
|
|
}
|
|
|
|
// the string would be quoted if offset and length were used directly
|
|
let enumCaseName = file.contents.bridge()
|
|
.substringWithByteRange(start: offset + 1, length: length - 2) ?? ""
|
|
guard enumCaseName == name else {
|
|
return nil
|
|
}
|
|
|
|
return offset
|
|
}
|
|
}
|
|
|
|
private func filterEnumInits(dictionary: [String: SourceKitRepresentable]) -> [[String: SourceKitRepresentable]] {
|
|
guard let elements = dictionary["key.elements"] as? [SourceKitRepresentable] else {
|
|
return []
|
|
}
|
|
|
|
let enumInitKind = "source.lang.swift.structure.elem.init_expr"
|
|
return elements.flatMap { element in
|
|
guard let dict = element as? [String: SourceKitRepresentable],
|
|
dict["key.kind"] as? String == enumInitKind else {
|
|
return nil
|
|
}
|
|
|
|
return dict
|
|
}
|
|
}
|
|
}
|