Skip to content

Commit d566fa0

Browse files
committed
Detect and add a fix-it for using : instead of -> in a function
1 parent 6a82373 commit d566fa0

File tree

5 files changed

+96
-2
lines changed

5 files changed

+96
-2
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,9 +1337,24 @@ extension Parser {
13371337
/// If a `throws` keyword appears right in front of the `arrow`, it is returned as `misplacedThrowsKeyword` so it can be synthesized in front of the arrow.
13381338
mutating func parseFunctionReturnClause(
13391339
effectSpecifiers: inout (some RawMisplacedEffectSpecifiersTrait)?,
1340-
allowNamedOpaqueResultType: Bool
1340+
allowNamedOpaqueResultType: Bool,
1341+
unexpectedColon: RawTokenSyntax? = nil
13411342
) -> RawReturnClauseSyntax {
1342-
let (unexpectedBeforeArrow, arrow) = self.expect(.arrow)
1343+
let unexpectedBeforeArrow: RawUnexpectedNodesSyntax?
1344+
let arrow: RawTokenSyntax
1345+
if let unexpectedColon {
1346+
let diagnostic = TokenDiagnostic(
1347+
.expectedArrowBeforeReturnType,
1348+
byteOffset: unexpectedColon.leadingTriviaByteLength
1349+
)
1350+
unexpectedBeforeArrow = nil
1351+
arrow = unexpectedColon.tokenView.withTokenDiagnostic(
1352+
tokenDiagnostic: diagnostic,
1353+
arena: arena
1354+
)
1355+
} else {
1356+
(unexpectedBeforeArrow, arrow) = self.expect(.arrow)
1357+
}
13431358
let unexpectedBeforeReturnType = self.parseMisplacedEffectSpecifiers(&effectSpecifiers)
13441359
let type: RawTypeSyntax
13451360
if allowNamedOpaqueResultType {
@@ -1440,6 +1455,12 @@ extension Parser {
14401455
effectSpecifiers: &effectSpecifiers,
14411456
allowNamedOpaqueResultType: true
14421457
)
1458+
} else if let colon = self.consume(if: .colon) {
1459+
returnClause = self.parseFunctionReturnClause(
1460+
effectSpecifiers: &effectSpecifiers,
1461+
allowNamedOpaqueResultType: true,
1462+
unexpectedColon: colon
1463+
)
14431464
} else {
14441465
returnClause = nil
14451466
}

Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ extension TokenWarning {
6565
/// Please order the cases in this enum alphabetically by case name.
6666
public enum StaticTokenError: String, DiagnosticMessage {
6767
case editorPlaceholder = "editor placeholder in source file"
68+
case expectedArrowBeforeReturnType = "expected '->' before return type"
6869
case equalMustHaveConsistentWhitespaceOnBothSides = "'=' must have consistent whitespace on both sides"
6970
case expectedBinaryExponentInHexFloatLiteral = "hexadecimal floating point literal must end with an exponent"
7071
case expectedClosingBraceInUnicodeEscape = #"expected '}' in \u{...} escape sequence"#
@@ -198,6 +199,8 @@ extension SwiftSyntax.TokenDiagnostic {
198199

199200
switch self.kind {
200201
case .editorPlaceholder: return StaticTokenError.editorPlaceholder
202+
case .expectedArrowBeforeReturnType:
203+
return StaticTokenError.expectedArrowBeforeReturnType
201204
case .equalMustHaveConsistentWhitespaceOnBothSides:
202205
return StaticTokenError.equalMustHaveConsistentWhitespaceOnBothSides
203206
case .expectedBinaryExponentInHexFloatLiteral: return StaticTokenError.expectedBinaryExponentInHexFloatLiteral
@@ -301,6 +304,19 @@ extension SwiftSyntax.TokenDiagnostic {
301304
changes: [.replace(oldNode: Syntax(token), newNode: Syntax(fixedToken))]
302305
)
303306
]
307+
case .expectedArrowBeforeReturnType:
308+
var fixedToken = token.with(\.tokenKind, .arrow)
309+
if let previous = fixedToken.previousToken(viewMode: .sourceAccurate),
310+
previous.trailingTrivia.isEmpty,
311+
fixedToken.leadingTrivia.isEmpty {
312+
fixedToken.leadingTrivia = .space
313+
}
314+
return [
315+
FixIt(
316+
message: .replaceColonWithArrow,
317+
changes: [.replace(oldNode: Syntax(token), newNode: Syntax(fixedToken))]
318+
)
319+
]
304320
case .equalMustHaveConsistentWhitespaceOnBothSides:
305321
let hasLeadingSpace =
306322
token.previousToken(viewMode: .all)?.trailingTrivia.contains(where: { $0.isSpaceOrTab }) ?? false

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,9 @@ extension FixItMessage where Self == StaticParserFixIt {
730730
public static var replaceCurlyQuoteByNormalQuote: Self {
731731
.init(#"replace curly quotes with '"'"#)
732732
}
733+
public static var replaceColonWithArrow: Self {
734+
.init("replace ':' with '->'")
735+
}
733736
public static var replaceNonBreakingSpaceBySpace: Self {
734737
.init("replace non-breaking space with ' '")
735738
}

Sources/SwiftSyntax/TokenDiagnostic.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public struct TokenDiagnostic: Hashable, Sendable {
2525

2626
case editorPlaceholder
2727
case equalMustHaveConsistentWhitespaceOnBothSides
28+
case expectedArrowBeforeReturnType
2829
case expectedBinaryExponentInHexFloatLiteral
2930
case expectedClosingBraceInUnicodeEscape
3031
case expectedDigitInFloatLiteral
@@ -69,6 +70,7 @@ public struct TokenDiagnostic: Hashable, Sendable {
6970
switch self {
7071
case .editorPlaceholder: return .error
7172
case .equalMustHaveConsistentWhitespaceOnBothSides: return .error
73+
case .expectedArrowBeforeReturnType: return .error
7274
case .expectedBinaryExponentInHexFloatLiteral: return .error
7375
case .expectedClosingBraceInUnicodeEscape: return .error
7476
case .expectedDigitInFloatLiteral: return .error

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,58 @@ final class DeclarationTests: ParserTestCase {
156156
)
157157
}
158158

159+
func testFuncColonInsteadOfArrow() {
160+
assertParse(
161+
"""
162+
func foo()1️⃣: Slice<MinimalMutableCollection<T>> {}
163+
""",
164+
diagnostics: [
165+
DiagnosticSpec(
166+
locationMarker: "1️⃣",
167+
message: "expected '->' before return type",
168+
fixIts: ["replace ':' with '->'"]
169+
)
170+
],
171+
fixedSource: """
172+
func foo() -> Slice<MinimalMutableCollection<T>> {}
173+
"""
174+
)
175+
176+
assertParse(
177+
"""
178+
func foo() 1️⃣: [String] {}
179+
""",
180+
diagnostics: [
181+
DiagnosticSpec(
182+
locationMarker: "1️⃣",
183+
message: "expected '->' before return type",
184+
fixIts: ["replace ':' with '->'"]
185+
)
186+
],
187+
fixedSource: """
188+
func foo() -> [String] {}
189+
"""
190+
)
191+
192+
assertParse(
193+
"""
194+
func foo<T>() async throws
195+
1️⃣: Int where T: Hashable {}
196+
""",
197+
diagnostics: [
198+
DiagnosticSpec(
199+
locationMarker: "1️⃣",
200+
message: "expected '->' before return type",
201+
fixIts: ["replace ':' with '->'"]
202+
)
203+
],
204+
fixedSource: """
205+
func foo<T>() async throws
206+
-> Int where T: Hashable {}
207+
"""
208+
)
209+
}
210+
159211
func testFuncAfterUnbalancedClosingBrace() {
160212
assertParse(
161213
"""

0 commit comments

Comments
 (0)