@@ -13,4 +13,245 @@ import SwiftSyntax
13
13
/// - SeeAlso: https://google.github.io/swift#types-with-shorthand-names
14
14
public final class UseShorthandTypeNames : SyntaxFormatRule {
15
15
16
+ // Visits all potential long forms interpreted as types
17
+ public override func visit( _ node: SimpleTypeIdentifierSyntax ) -> TypeSyntax {
18
+ // If nested in a member type identifier, type must be left in long form for the compiler
19
+ guard let parent = node. parent,
20
+ !( parent is MemberTypeIdentifierSyntax ) else { return node }
21
+ // Type is in long form if it has a non-nil generic argument clause
22
+ guard let genArg = node. genericArgumentClause else { return node }
23
+ diagnose ( . useTypeShorthand( type: node. name. text. lowercased ( ) ) , on: node)
24
+
25
+ // Ensure that all arguments in the clause are shortened and in expected-format by visiting
26
+ // the argument list, first
27
+ let argList = super. visit ( genArg. arguments) as! GenericArgumentListSyntax
28
+ // Store trivia of the long form type to pass to the new shorthand type later
29
+ let trivia = retrieveTrivia ( from: node)
30
+
31
+ switch node. name. text {
32
+ case " Array " :
33
+ guard argList. count == 1 else { return node }
34
+ let newArray = shortenArrayType ( arguments: argList, trivia: trivia)
35
+ return newArray
36
+ case " Dictionary " :
37
+ guard argList. count == 2 else { return node }
38
+ let newDictionary = shortenDictionaryType ( arguments: argList, trivia: trivia)
39
+ return newDictionary
40
+ case " Optional " :
41
+ guard argList. count == 1 else { return node }
42
+ let newOptional = shortenOptionalType ( arguments: argList, trivia: trivia)
43
+ return newOptional
44
+ default :
45
+ break
46
+ }
47
+ return node
48
+ }
49
+
50
+ // Visits all potential long forms interpreted as expressions
51
+ public override func visit( _ node: SpecializeExprSyntax ) -> ExprSyntax {
52
+ let argList = super. visit ( node. genericArgumentClause. arguments) as! GenericArgumentListSyntax
53
+ guard let exp = node. expression as? IdentifierExprSyntax else { return node }
54
+ let trivia = retrieveTrivia ( from: node)
55
+
56
+ switch exp. identifier. text {
57
+ case " Array " :
58
+ guard argList. count == 1 else { return node }
59
+ let newArray = shortenArrayExp ( arguments: argList, trivia: trivia)
60
+ return newArray ?? node
61
+ case " Dictionary " :
62
+ guard argList. count == 2 else { return node }
63
+ let newDictionary = shortenDictExp ( arguments: argList, trivia: trivia)
64
+ return newDictionary ?? node
65
+ default :
66
+ break
67
+ }
68
+ return node
69
+ }
70
+
71
+ // Get type identifier from generic argument, construct shorthand array form, as a type
72
+ func shortenArrayType( arguments: GenericArgumentListSyntax ,
73
+ trivia: ( Trivia , Trivia ) ) -> TypeSyntax {
74
+ let type = arguments [ 0 ] . argumentType
75
+ let ( leading, trailing) = trivia
76
+ let leftBracket = SyntaxFactory . makeLeftSquareBracketToken ( leadingTrivia: leading)
77
+ let rightBracket = SyntaxFactory . makeRightSquareBracketToken ( trailingTrivia: trailing)
78
+ let newArray = SyntaxFactory . makeArrayType ( leftSquareBracket: leftBracket,
79
+ elementType: type,
80
+ rightSquareBracket: rightBracket)
81
+ return newArray
82
+ }
83
+
84
+ // Get type identifiers from generic arguments, construct shorthand dictionary form, as a type
85
+ func shortenDictionaryType( arguments: GenericArgumentListSyntax ,
86
+ trivia: ( Trivia , Trivia ) ) -> TypeSyntax {
87
+ let firstType = arguments [ 0 ] . argumentType
88
+ let secondType = arguments [ 1 ] . argumentType
89
+ let ( leading, trailing) = trivia
90
+ let leftBracket = SyntaxFactory . makeLeftSquareBracketToken ( leadingTrivia: leading)
91
+ let rightBracket = SyntaxFactory . makeRightSquareBracketToken ( trailingTrivia: trailing)
92
+ let colon = SyntaxFactory . makeColonToken ( trailingTrivia: . spaces( 1 ) )
93
+ let newDictionary = SyntaxFactory . makeDictionaryType ( leftSquareBracket: leftBracket,
94
+ keyType: firstType,
95
+ colon: colon,
96
+ valueType: secondType,
97
+ rightSquareBracket: rightBracket)
98
+ return newDictionary
99
+ }
100
+
101
+ // Get type identifier from generic argument, construct shorthand optional form, as a type
102
+ func shortenOptionalType( arguments: GenericArgumentListSyntax ,
103
+ trivia: ( Trivia , Trivia ) ) -> TypeSyntax {
104
+ let type = arguments [ 0 ] . argumentType
105
+ let ( _, trailing) = trivia
106
+ let questionMark = SyntaxFactory . makePostfixQuestionMarkToken ( trailingTrivia: trailing)
107
+ let newOptional = SyntaxFactory . makeOptionalType ( wrappedType: type,
108
+ questionMark: questionMark)
109
+ return newOptional
110
+ }
111
+
112
+ // Construct an array expression from type information in the generic argument
113
+ func shortenArrayExp( arguments: GenericArgumentListSyntax ,
114
+ trivia: ( Trivia , Trivia ) ) -> ArrayExprSyntax ? {
115
+ var element = SyntaxFactory . makeBlankArrayElement ( )
116
+
117
+ // Get type id, create an expression, nest in the array element
118
+ let arg = arguments [ 0 ]
119
+ // Type id can be in a simple type identifier (ex: Int)
120
+ if let simpleId = arg. argumentType as? SimpleTypeIdentifierSyntax {
121
+ let idExp = SyntaxFactory . makeIdentifierExpr ( identifier: simpleId. name,
122
+ declNameArguments: nil )
123
+ element = SyntaxFactory . makeArrayElement ( expression: idExp, trailingComma: nil )
124
+ // Type id can be in a long form array (ex: Array<Int>.Index)
125
+ } else if let memberTypeId = arg. argumentType as? MemberTypeIdentifierSyntax {
126
+ guard let memberAccessExp = restructureLongForm ( member: memberTypeId) else { return nil }
127
+ element = SyntaxFactory . makeArrayElement ( expression: memberAccessExp, trailingComma: nil )
128
+ // Type id can be in an array, dictionary, or optional type (ex: [Int], [String: Int], Int?)
129
+ } else if arg. argumentType is ArrayTypeSyntax ||
130
+ arg. argumentType is DictionaryTypeSyntax ||
131
+ arg. argumentType is OptionalTypeSyntax {
132
+ if let newExp = restructureTypeSyntax ( type: arg. argumentType) {
133
+ element = SyntaxFactory . makeArrayElement ( expression: newExp, trailingComma: nil )
134
+ }
135
+ } else { return nil }
136
+
137
+ let elementList = SyntaxFactory . makeArrayElementList ( [ element] )
138
+ let ( leading, trailing) = trivia
139
+ let leftBracket = SyntaxFactory . makeLeftSquareBracketToken ( leadingTrivia: leading)
140
+ let rightBracket = SyntaxFactory . makeRightSquareBracketToken ( trailingTrivia: trailing)
141
+ let arrayExp = SyntaxFactory . makeArrayExpr ( leftSquare: leftBracket,
142
+ elements: elementList,
143
+ rightSquare: rightBracket)
144
+ return arrayExp
145
+ }
146
+
147
+ // Construct a dictionary expression from type information in the generic arguments
148
+ func shortenDictExp( arguments: GenericArgumentListSyntax ,
149
+ trivia: ( Trivia , Trivia ) ) -> DictionaryExprSyntax ? {
150
+ let blank = SyntaxFactory . makeBlankIdentifierExpr ( )
151
+ let colon = SyntaxFactory . makeColonToken ( trailingTrivia: . spaces( 1 ) )
152
+ var element = SyntaxFactory . makeDictionaryElement ( keyExpression: blank,
153
+ colon: colon,
154
+ valueExpression: blank,
155
+ trailingComma: nil )
156
+ // Get type id, create an expression, add to the dictionary element
157
+ for (idx, arg) in arguments. enumerated ( ) {
158
+ if let simpleId = arg. argumentType as? SimpleTypeIdentifierSyntax {
159
+ let idExp = SyntaxFactory . makeIdentifierExpr ( identifier: simpleId. name,
160
+ declNameArguments: nil )
161
+ element = idx == 0 ? element. withKeyExpression ( idExp) : element. withValueExpression ( idExp)
162
+ } else if let memberTypeId = arg. argumentType as? MemberTypeIdentifierSyntax {
163
+ guard let memberAccessExp = restructureLongForm ( member: memberTypeId) else { return nil }
164
+ element = idx == 0 ? element. withKeyExpression ( memberAccessExp) :
165
+ element. withValueExpression ( memberAccessExp)
166
+ } else if arg. argumentType is ArrayTypeSyntax ||
167
+ arg. argumentType is DictionaryTypeSyntax ||
168
+ arg. argumentType is OptionalTypeSyntax {
169
+ let newExp = restructureTypeSyntax ( type: arg. argumentType)
170
+ element = idx == 0 ? element. withKeyExpression ( newExp) : element. withValueExpression ( newExp)
171
+ } else { return nil }
172
+ }
173
+
174
+ let elementList = SyntaxFactory . makeDictionaryElementList ( [ element] )
175
+ let ( leading, trailing) = trivia
176
+ let leftBracket = SyntaxFactory . makeLeftSquareBracketToken ( leadingTrivia: leading)
177
+ let rightBracket = SyntaxFactory . makeRightSquareBracketToken ( trailingTrivia: trailing)
178
+ let dictExp = SyntaxFactory . makeDictionaryExpr ( leftSquare: leftBracket,
179
+ content: elementList,
180
+ rightSquare: rightBracket)
181
+ return dictExp
182
+ }
183
+
184
+ // Convert member type identifier to an equivalent member access expression
185
+ // The node will appear the same, but the structure of the tree is different
186
+ func restructureLongForm( member: MemberTypeIdentifierSyntax ) -> MemberAccessExprSyntax ? {
187
+ guard let simpleTypeId = member. baseType as? SimpleTypeIdentifierSyntax else { return nil }
188
+ guard let genArgClause = simpleTypeId. genericArgumentClause else { return nil }
189
+ // Node will only change if an argument in the generic argument clause is shortened
190
+ let argClause = super. visit ( genArgClause) as! GenericArgumentClauseSyntax
191
+ let idExp = SyntaxFactory . makeIdentifierExpr ( identifier: simpleTypeId. name,
192
+ declNameArguments: nil )
193
+ let specialExp = SyntaxFactory . makeSpecializeExpr ( expression: idExp,
194
+ genericArgumentClause: argClause)
195
+ let memberAccessExp = SyntaxFactory . makeMemberAccessExpr ( base: specialExp,
196
+ dot: member. period,
197
+ name: member. name,
198
+ declNameArguments: nil )
199
+ return memberAccessExp
200
+ }
201
+
202
+ // Convert array, dictionary, or optional type to an equivalent expression
203
+ // The node will appear the same, but the structure of the tree is different
204
+ func restructureTypeSyntax( type: TypeSyntax ) -> ExprSyntax ? {
205
+ if let arrayType = type as? ArrayTypeSyntax {
206
+ let type = arrayType. elementType. description. trimmingCharacters ( in: . whitespacesAndNewlines)
207
+ let typeId = SyntaxFactory . makeIdentifier ( type)
208
+ let id = SyntaxFactory . makeIdentifierExpr ( identifier: typeId,
209
+ declNameArguments: nil )
210
+ let element = SyntaxFactory . makeArrayElement ( expression: id, trailingComma: nil )
211
+ let elementList = SyntaxFactory . makeArrayElementList ( [ element] )
212
+ let arrayExp = SyntaxFactory . makeArrayExpr ( leftSquare: arrayType. leftSquareBracket,
213
+ elements: elementList,
214
+ rightSquare: arrayType. rightSquareBracket)
215
+ return arrayExp
216
+ } else if let dictType = type as? DictionaryTypeSyntax {
217
+ let keyType = dictType. keyType. description. trimmingCharacters ( in: . whitespacesAndNewlines)
218
+ let keyTypeId = SyntaxFactory . makeIdentifier ( keyType)
219
+ let keyIdExp = SyntaxFactory . makeIdentifierExpr ( identifier: keyTypeId, declNameArguments: nil )
220
+ let valueType = dictType. valueType. description. trimmingCharacters ( in: . whitespacesAndNewlines)
221
+ let valueTypeId = SyntaxFactory . makeIdentifier ( valueType)
222
+ let valueIdExp = SyntaxFactory . makeIdentifierExpr ( identifier: valueTypeId,
223
+ declNameArguments: nil )
224
+ let element = SyntaxFactory . makeDictionaryElement ( keyExpression: keyIdExp,
225
+ colon: dictType. colon,
226
+ valueExpression: valueIdExp,
227
+ trailingComma: nil )
228
+ let elementList = SyntaxFactory . makeDictionaryElementList ( [ element] )
229
+ let dictExp = SyntaxFactory . makeDictionaryExpr ( leftSquare: dictType. leftSquareBracket,
230
+ content: elementList,
231
+ rightSquare: dictType. rightSquareBracket)
232
+ return dictExp
233
+ } else if let optionalType = type as? OptionalTypeSyntax {
234
+ let type = optionalType. wrappedType. description. trimmingCharacters (
235
+ in: . whitespacesAndNewlines)
236
+ let typeId = SyntaxFactory . makeIdentifier ( type)
237
+ let idExp = SyntaxFactory . makeIdentifierExpr ( identifier: typeId, declNameArguments: nil )
238
+ let optionalExp = SyntaxFactory . makeOptionalChainingExpr ( expression: idExp,
239
+ questionMark: optionalType. questionMark)
240
+ return optionalExp
241
+ }
242
+ return nil
243
+ }
244
+
245
+ // Returns trivia from the front and end of the entire given node
246
+ func retrieveTrivia( from node: Syntax ) -> ( Trivia , Trivia ) {
247
+ guard let firstTok = node. firstToken, let lastTok = node. lastToken else { return ( [ ] , [ ] ) }
248
+ return ( firstTok. leadingTrivia, lastTok. trailingTrivia)
249
+ }
250
+ }
251
+
252
+ extension Diagnostic . Message {
253
+ static func useTypeShorthand( type: String ) -> Diagnostic . Message {
254
+ return . init( . warning, " use \( type) type shorthand form " )
255
+ }
16
256
}
257
+
0 commit comments