-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Expand file tree
/
Copy pathModifierOrderRule.swift
More file actions
181 lines (165 loc) · 6.62 KB
/
ModifierOrderRule.swift
File metadata and controls
181 lines (165 loc) · 6.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import SourceKittenFramework
import SwiftLintCore
import SwiftSyntax
@SwiftSyntaxRule(explicitRewriter: true, optIn: true)
struct ModifierOrderRule: Rule {
var configuration = ModifierOrderConfiguration()
static let description = RuleDescription(
identifier: "modifier_order",
name: "Modifier Order",
description: "Modifier order should be consistent.",
kind: .style,
nonTriggeringExamples: ModifierOrderRuleExamples.nonTriggeringExamples,
triggeringExamples: ModifierOrderRuleExamples.triggeringExamples
)
}
private extension ModifierOrderRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
override func visitPost(_ node: DeclModifierListSyntax) {
guard let parent = node.parent else {
return
}
let introducer: TokenSyntax? = parent.asProtocol((any DeclGroupSyntax).self)?.introducer
?? parent.as(FunctionDeclSyntax.self)?.funcKeyword
?? parent.as(InitializerDeclSyntax.self)?.initKeyword
?? parent.as(SubscriptDeclSyntax.self)?.subscriptKeyword
?? parent.as(VariableDeclSyntax.self)?.bindingSpecifier
guard let introducer else {
return
}
let descriptions = node.modifierDescriptions
let differences = descriptions
.bubbleSort(by: configuration.preferredModifierOrder)
.difference(from: descriptions)
let orderedDescriptions = descriptions.applying(differences) ?? descriptions
if let diff = zip(orderedDescriptions, descriptions).first(where: { $0.keyword != $1.keyword }) {
violations.append(.init(
position: introducer.positionAfterSkippingLeadingTrivia,
reason: "\(diff.0.keyword) modifier should come before \(diff.1.keyword)"
))
}
}
}
final class Rewriter: ViolationsSyntaxRewriter<ConfigurationType> {
override func visit(_ node: DeclModifierListSyntax) -> DeclModifierListSyntax {
let modifierDescriptions = node.modifierDescriptions
let prevModifiers = modifierDescriptions.map(\.modifier)
let differences = modifierDescriptions
.bubbleSort(by: configuration.preferredModifierOrder)
.map(\.modifier)
.difference(from: prevModifiers)
if differences.isEmpty {
return super.visit(node)
}
numberOfCorrections += differences.count
var newModifiers = prevModifiers
for change in differences {
switch change {
case let .remove(offset, _, _):
newModifiers.remove(at: offset)
case let .insert(offset, element, _):
let prevModifier = prevModifiers[offset]
newModifiers.insert(element.with(\.leadingTrivia, prevModifier.leadingTrivia), at: offset)
if offset == 0, newModifiers.count > 1 {
newModifiers[1] = newModifiers[1].with(\.leadingTrivia, [])
}
}
}
let newNode = DeclModifierListSyntax(newModifiers)
.with(\.leadingTrivia, node.leadingTrivia)
.with(\.trailingTrivia, node.trailingTrivia)
return super.visit(newNode)
}
}
}
private extension DeclModifierListSyntax {
var modifierDescriptions: [ModifierDescription] {
var descriptions: [ModifierDescription] = []
for modifier in self {
let keyword = modifier.name.text
let position = modifier.positionAfterSkippingLeadingTrivia
guard let group = SwiftDeclarationAttributeKind.ModifierGroup(modifierKeyword: keyword) else {
continue
}
// Handle setter access modifiers like `private(set)``.
if let detail = modifier.detail?.detail.tokenKind,
case .identifier(let detailText) = detail, detailText == "set" {
if case .acl = group {
descriptions.append(.init(
keyword: "\(keyword)(set)",
modifier: modifier,
group: .setterACL,
position: position
))
}
continue
}
descriptions.append(.init(
keyword: keyword,
modifier: modifier,
group: group,
position: position
))
}
return descriptions
}
}
private extension SwiftDeclarationAttributeKind.ModifierGroup {
init?(modifierKeyword: String) { // swiftlint:disable:this cyclomatic_complexity
switch modifierKeyword {
case "override":
self = .override
case "weak":
self = .owned
case "final":
self = .final
case "required":
self = .required
case "convenience":
self = .convenience
case "lazy":
self = .lazy
case "dynamic":
self = .dynamic
case "isolated", "nonisolated":
self = .isolation
case "private", "fileprivate", "internal", "public", "open":
self = .acl
case "mutating", "nonmutating":
self = .mutators
case "static", "class":
self = .typeMethods
case _ where modifierKeyword.hasPrefix("@"):
self = .atPrefixed
default:
return nil
}
}
}
private struct ModifierDescription: Equatable {
let keyword: String
let modifier: DeclModifierSyntax
let group: SwiftDeclarationAttributeKind.ModifierGroup
let position: AbsolutePosition
}
private extension [ModifierDescription] {
func bubbleSort(by preferredOrder: [SwiftDeclarationAttributeKind.ModifierGroup]) -> [ModifierDescription] {
var sorted = Self()
for element in self {
var inserted = false
for (index, sortedElement) in sorted.enumerated() {
let elementIndex = preferredOrder.firstIndex(of: element.group)
let sortedElementIndex = preferredOrder.firstIndex(of: sortedElement.group)
if let elementIndex, let sortedElementIndex, elementIndex < sortedElementIndex {
sorted.insert(element, at: index)
inserted = true
break
}
}
if !inserted {
sorted.append(element)
}
}
return sorted
}
}