Skip to content

Commit e772363

Browse files
committed
Convert implicitly unwrapped optionals to proper optionals #1320
- created ConvertImplicitlyUnwrappedOptionalToOptional that will suggest a conversion when the token in concern is - an IUO; - a direct child of an IUO; or - a direct child of the wrappedType of an IUO. - registered in SyntaxCodeActions.allSyntaxCodeActions - added a test in CodeActionTests - added ConvertImplicitlyUnwrappedOptionalToOptional.swift to CMakeLists of SourceKitLSP
1 parent e5d93e1 commit e772363

File tree

4 files changed

+86
-0
lines changed

4 files changed

+86
-0
lines changed

Sources/SourceKitLSP/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ target_sources(SourceKitLSP PRIVATE
3030
Swift/AdjustPositionToStartOfIdentifier.swift
3131
Swift/CodeActions/AddDocumentation.swift
3232
Swift/CodeActions/ConvertIntegerLiteral.swift
33+
Swift/CodeActions/ConvertImplicitlyUnwrappedOptionalToOptional.swift
3334
Swift/CodeActions/ConvertJSONToCodableStruct.swift
3435
Swift/CodeActions/PackageManifestEdits.swift
3536
Swift/CodeActions/SyntaxCodeActionProvider.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import LanguageServerProtocol
14+
import SwiftRefactor
15+
import SwiftSyntax
16+
17+
/// Convert implicitly unwrapped optionals to optionals
18+
struct ConvertImplicitlyUnwrappedOptionalToOptional: SyntaxRefactoringProvider {
19+
public static func refactor(syntax: ImplicitlyUnwrappedOptionalTypeSyntax, in context: Void) -> OptionalTypeSyntax? {
20+
OptionalTypeSyntax(
21+
leadingTrivia: syntax.leadingTrivia,
22+
syntax.unexpectedBeforeWrappedType,
23+
wrappedType: syntax.wrappedType,
24+
syntax.unexpectedBetweenWrappedTypeAndExclamationMark,
25+
questionMark: .postfixQuestionMarkToken(
26+
leadingTrivia: syntax.exclamationMark.leadingTrivia,
27+
trailingTrivia: syntax.exclamationMark.trailingTrivia
28+
)
29+
)
30+
}
31+
}
32+
33+
extension ConvertImplicitlyUnwrappedOptionalToOptional: SyntaxRefactoringCodeActionProvider {
34+
static let title: String = "Convert Implicitly Unwrapped Optional to Optional"
35+
36+
static func nodeToRefactor(in scope: SyntaxCodeActionScope) -> ImplicitlyUnwrappedOptionalTypeSyntax? {
37+
guard let token = scope.innermostNodeContainingRange else {
38+
return nil
39+
}
40+
41+
return
42+
if let iuoType = token.as(ImplicitlyUnwrappedOptionalTypeSyntax.self)
43+
?? token.parent?.as(ImplicitlyUnwrappedOptionalTypeSyntax.self)
44+
{
45+
iuoType
46+
} else if token.is(TokenSyntax.self),
47+
let wrappedType = token.parent?.as(TypeSyntax.self),
48+
let iuoType = wrappedType.parent?.as(ImplicitlyUnwrappedOptionalTypeSyntax.self),
49+
iuoType.wrappedType == wrappedType
50+
{
51+
iuoType
52+
} else {
53+
nil
54+
}
55+
}
56+
}

Sources/SourceKitLSP/Swift/CodeActions/SyntaxCodeActions.swift

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ let allSyntaxCodeActions: [SyntaxCodeActionProvider.Type] = [
1818
AddDocumentation.self,
1919
AddSeparatorsToIntegerLiteral.self,
2020
ConvertIntegerLiteral.self,
21+
ConvertImplicitlyUnwrappedOptionalToOptional.self,
2122
ConvertJSONToCodableStruct.self,
2223
FormatRawStringLiteral.self,
2324
MigrateToNewIfLetSyntax.self,

Tests/SourceKitLSPTests/CodeActionTests.swift

+28
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,34 @@ final class CodeActionTests: XCTestCase {
10061006
}
10071007
}
10081008

1009+
func testConvertIUOToProperOptional() async throws {
1010+
try await assertCodeActions(
1011+
"""
1012+
func (a: 1️⃣(String, /* tuple */ Int)!2️⃣/*tra3️⃣iling*/4️⃣)
1013+
""",
1014+
markers: ["1️⃣", "2️⃣", "3️⃣"],
1015+
ranges: [("1️⃣", "2️⃣"), ("1️⃣", "3️⃣")],
1016+
exhaustive: false
1017+
) { uri, positions in
1018+
[
1019+
CodeAction(
1020+
title: "Convert Implicitly Unwrapped Optional to Optional",
1021+
kind: .refactorInline,
1022+
edit: WorkspaceEdit(
1023+
changes: [
1024+
uri: [
1025+
TextEdit(
1026+
range: positions["1️⃣"]..<positions["4️⃣"],
1027+
newText: "(String, /* tuple */ Int)?/*trailing*/"
1028+
)
1029+
]
1030+
]
1031+
)
1032+
)
1033+
]
1034+
}
1035+
}
1036+
10091037
/// Retrieves the code action at a set of markers and asserts that it matches a list of expected code actions.
10101038
///
10111039
/// - Parameters:

0 commit comments

Comments
 (0)