Skip to content

Commit c8ce60b

Browse files
committed
Convert implicitly unwrapped optionals to proper optionals #1320
- created ConvertIUOToProperOptional 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 ConvertIUOToProperOptional.swift to CMakeLists of SourceKitLSP
1 parent e5d93e1 commit c8ce60b

File tree

4 files changed

+89
-0
lines changed

4 files changed

+89
-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/ConvertIUOToProperOptional.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,59 @@
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 (IUOs) to proper optionals
18+
@_spi(Testing)
19+
public struct ConvertIUOToProperOptional: SyntaxRefactoringProvider {
20+
public typealias Input = ImplicitlyUnwrappedOptionalTypeSyntax
21+
public typealias Context = Void
22+
public typealias Output = OptionalTypeSyntax
23+
24+
@_spi(Testing)
25+
public static func refactor(syntax: Input, in context: Context) -> Output? {
26+
OptionalTypeSyntax(
27+
leadingTrivia: syntax.leadingTrivia,
28+
syntax.unexpectedBeforeWrappedType,
29+
wrappedType: syntax.wrappedType,
30+
syntax.unexpectedBetweenWrappedTypeAndExclamationMark,
31+
questionMark: .postfixQuestionMarkToken(
32+
leadingTrivia: syntax.exclamationMark.leadingTrivia,
33+
trailingTrivia: syntax.exclamationMark.trailingTrivia
34+
)
35+
)
36+
}
37+
}
38+
39+
extension ConvertIUOToProperOptional: SyntaxRefactoringCodeActionProvider {
40+
static let title: String = "Convert Implicitly Unwrapped Optional to Proper Optional"
41+
42+
static func nodeToRefactor(in scope: SyntaxCodeActionScope) -> Input? {
43+
guard let token = scope.innermostNodeContainingRange else {
44+
return nil
45+
}
46+
47+
return if let iuoType = token.as(Input.self) ?? token.parent?.as(Input.self) {
48+
iuoType
49+
} else if token.is(TokenSyntax.self),
50+
let wrappedType = token.parent?.as(TypeSyntax.self),
51+
let iuoType = wrappedType.parent?.as(Input.self),
52+
iuoType.wrappedType == wrappedType
53+
{
54+
iuoType
55+
} else {
56+
nil
57+
}
58+
}
59+
}

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+
ConvertIUOToProperOptional.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 Proper 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)