diff --git a/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift b/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift index 7f8efc91..2c309c6c 100644 --- a/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift +++ b/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -117,10 +117,14 @@ extension ConvertZeroParameterFunctionToComputedProperty: SyntaxRefactoringCodeA package static var title: String { "Convert to computed property" } static func nodeToRefactor(in scope: SyntaxCodeActionScope) -> Input? { - return scope.innermostNodeContainingRange?.findParentOfSelf( + let functionDecl = scope.innermostNodeContainingRange?.findParentOfSelf( ofType: FunctionDeclSyntax.self, stoppingIf: { $0.is(CodeBlockSyntax.self) || $0.is(MemberBlockSyntax.self) } ) + guard let functionDecl, !(functionDecl.signature.returnClause?.type.isVoid ?? true) else { + return nil + } + return functionDecl } } @@ -178,3 +182,18 @@ extension [SourceEdit] { ) } } + +// MARK: - Helper Extensions + +private extension TypeSyntax { + var isVoid: Bool { + switch self.as(TypeSyntaxEnum.self) { + case .identifierType(let identifierType) where identifierType.name.text == "Void": + return true + case .tupleType(let tupleType) where tupleType.elements.isEmpty: + return true + default: + return false + } + } +} diff --git a/Tests/SourceKitLSPTests/CodeActionTests.swift b/Tests/SourceKitLSPTests/CodeActionTests.swift index 8e5025e7..89c483ea 100644 --- a/Tests/SourceKitLSPTests/CodeActionTests.swift +++ b/Tests/SourceKitLSPTests/CodeActionTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -1506,6 +1506,41 @@ final class CodeActionTests: SourceKitLSPTestCase { } } + func testConvertZeroParameterFunctionToComputedPropertyNotOfferedForImplicitVoid() async throws { + try await assertCodeActions( + """ + 1️⃣func test()2️⃣ { }3️⃣ + """, + ranges: [("1️⃣", "2️⃣")], + exhaustive: false + ) { _, _ in + [] + } + } + + func testConvertZeroParameterFunctionToComputedPropertyNotOfferedForExplicitVoid() async throws { + try await assertCodeActions( + """ + 1️⃣func test() -> Void2️⃣ { }3️⃣ + """, + ranges: [("1️⃣", "2️⃣")], + exhaustive: false + ) { _, _ in + [] + } + } + + func testConvertZeroParameterFunctionToComputedPropertyNotOfferedForEmptyTuple() async throws { + try await assertCodeActions( + """ + 1️⃣func test() -> ()2️⃣ { }3️⃣ + """, + ranges: [("1️⃣", "2️⃣")], + exhaustive: false + ) { _, _ in + [] + } + } func testConvertComputedPropertyToZeroParameterFunction() async throws { let testClient = try await TestSourceKitLSPClient(capabilities: clientCapabilitiesWithCodeActionSupport) let uri = DocumentURI(for: .swift)