Files
SwiftLint/Source/SwiftLintFramework/Rules/Style/ModifierOrderRule.swift
T
Timofey Solonin efa68177b2 #2435 - Make modifier_order rule rely on an explicit set of rules (#2458)
* #2435 - Adjust modifier_order rule to require explicit modifier order specified to conclude a violation

* #2435 - Move modifier order rule examples to a separate file

* #2435 - Add modifier interference tests

* #2435 - Fix whitespaces

* Minor edits

* Add changelog entry
2018-11-28 15:10:49 -08:00

141 lines
5.3 KiB
Swift

import SourceKittenFramework
public struct ModifierOrderRule: ASTRule, OptInRule, ConfigurationProviderRule {
public var configuration = ModifierOrderConfiguration(
preferredModifierOrder: [
.override,
.acl,
.setterACL,
.dynamic,
.mutators,
.lazy,
.final,
.required,
.convenience,
.typeMethods,
.owned
]
)
public init() {}
public static let description = RuleDescription(
identifier: "modifier_order",
name: "Modifier Order",
description: "Modifier order should be consistent.",
kind: .style,
minSwiftVersion: .fourDotOne ,
nonTriggeringExamples: ModifierOrderRuleExamples.nonTriggeringExamples,
triggeringExamples: ModifierOrderRuleExamples.triggeringExamples
)
public func validate(file: File,
kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard let offset = dictionary.offset else {
return []
}
let violatableModifiers = self.violatableModifiers(declaredModifiers: dictionary.modifierDescriptions)
let prioritizedModifiers = self.prioritizedModifiers(violatableModifiers: violatableModifiers)
let sortedByPriorityModifiers = prioritizedModifiers.sorted(
by: { lhs, rhs in lhs.priority < rhs.priority }
).map { $0.modifier }
let violatingModifiers = zip(
sortedByPriorityModifiers,
violatableModifiers
).filter { sortedModifier, unsortedModifier in
return sortedModifier != unsortedModifier
}
if let first = violatingModifiers.first {
let preferredModifier = first.0
let declaredModifier = first.1
let reason = "\(preferredModifier.keyword) modifier should be before \(declaredModifier.keyword)."
return [
StyleViolation(
ruleDescription: type(of: self).description,
severity: configuration.severityConfiguration.severity,
location: Location(file: file, byteOffset: offset),
reason: reason
)
]
} else {
return []
}
}
private func violatableModifiers(declaredModifiers: [ModifierDescription]) -> [ModifierDescription] {
let preferredModifierGroups = ([.atPrefixed] + configuration.preferredModifierOrder)
return declaredModifiers.filter { preferredModifierGroups.contains($0.group) }
}
private func prioritizedModifiers(
violatableModifiers: [ModifierDescription]
) -> [(priority: Int, modifier: ModifierDescription)] {
let prioritizedPreferredModifierGroups = ([.atPrefixed] + configuration.preferredModifierOrder).enumerated()
return violatableModifiers.reduce(
[(priority: Int, modifier: ModifierDescription)]()
) { prioritizedModifiers, modifier in
guard let priority = prioritizedPreferredModifierGroups.first(
where: { _, group in modifier.group == group }
)?.offset else {
return prioritizedModifiers
}
return prioritizedModifiers + [(priority: priority, modifier: modifier)]
}
}
}
private extension Dictionary where Key == String, Value == SourceKitRepresentable {
var modifierDescriptions: [ModifierDescription] {
let staticKinds = [SwiftDeclarationKind.functionMethodClass, .functionMethodStatic, .varClass, .varStatic]
let staticKindsAndOffsets = kindsAndOffsets(in: staticKinds).map { [$0] } ?? []
return (swiftAttributes + staticKindsAndOffsets)
.sorted {
guard let rhsOffset = $0.offset, let lhsOffset = $1.offset else {
return false
}
return rhsOffset < lhsOffset
}
.compactMap {
if let attribute = $0.attribute,
let modifierGroup = SwiftDeclarationAttributeKind.ModifierGroup(rawAttribute: attribute) {
return ModifierDescription(
keyword: attribute.lastComponentAfter("."),
group: modifierGroup
)
} else if let kind = $0.kind {
return ModifierDescription(
keyword: kind.lastComponentAfter("."),
group: .typeMethods
)
}
return nil
}
}
private func kindsAndOffsets(in declarationKinds: [SwiftDeclarationKind]) -> [String: SourceKitRepresentable]? {
guard let kind = kind, let offset = offset,
let declarationKind = SwiftDeclarationKind(rawValue: kind),
declarationKinds.contains(declarationKind) else {
return nil
}
return ["key.kind": kind, "key.offset": Int64(offset)]
}
}
private extension String {
func lastComponentAfter(_ charachter: String) -> String {
return components(separatedBy: charachter).last ?? ""
}
}
private struct ModifierDescription: Equatable {
let keyword: String
let group: SwiftDeclarationAttributeKind.ModifierGroup
}