Skip to content

Commit ccf3637

Browse files
LaurenWhiteallevato
authored andcommitted
Implement use shorthand type names. (swiftlang#91)
1 parent c09e5e8 commit ccf3637

File tree

2 files changed

+293
-0
lines changed

2 files changed

+293
-0
lines changed

tools/swift-format/Sources/Rules/UseShorthandTypeNames.swift

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,245 @@ import SwiftSyntax
1313
/// - SeeAlso: https://google.github.io/swift#types-with-shorthand-names
1414
public final class UseShorthandTypeNames: SyntaxFormatRule {
1515

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+
}
16256
}
257+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import Foundation
2+
import XCTest
3+
import SwiftSyntax
4+
5+
@testable import Rules
6+
7+
public class UseShorthandTypeNamesTests: DiagnosingTestCase {
8+
public func testLongFormNames() {
9+
XCTAssertFormatting(
10+
UseShorthandTypeNames.self,
11+
input: """
12+
func enumeratedDictionary<Element>(
13+
from values: Array<Element>,
14+
start: Optional<Array<Element>.Index> = nil
15+
) -> Dictionary<Int, Array<Element>> {
16+
// Specializer syntax
17+
Array<Array<Optional<Int>>.Index>.init()
18+
// More specializer syntax
19+
Array<[Int]>.init()
20+
}
21+
func nestedLongForms(
22+
x: Array<Dictionary<String, Int>>,
23+
y: Dictionary<Array<Optional<String>>, Optional<Int>>) {
24+
Dictionary<Array<Int>.Index, String>.init()
25+
Dictionary<String, Optional<Float>>.init()
26+
}
27+
""",
28+
expected: """
29+
func enumeratedDictionary<Element>(
30+
from values: [Element],
31+
start: Array<Element>.Index? = nil
32+
) -> [Int: [Element]] {
33+
// Specializer syntax
34+
[Array<Int?>.Index].init()
35+
// More specializer syntax
36+
[[Int]].init()
37+
}
38+
func nestedLongForms(
39+
x: [[String: Int]],
40+
y: [[String?]: Int?]) {
41+
[Array<Int>.Index: String].init()
42+
[String: Float?].init()
43+
}
44+
""")
45+
}
46+
47+
#if !os(macOS)
48+
static let allTests = [
49+
UseShorthandTypeNamesTests.testLongFormNames,
50+
]
51+
#endif
52+
}

0 commit comments

Comments
 (0)