From f095486c916921b4f2bbb369ea3abcebc1e71ba4 Mon Sep 17 00:00:00 2001 From: Divya Prakash Date: Tue, 6 Jan 2026 13:42:00 +0530 Subject: [PATCH 1/4] Fix #2430: Hide 'Convert to computed property' for Void functions --- .../SyntaxRefactoringCodeActionProvider.swift | 35 +++++++- Tests/SourceKitLSPTests/CodeActionTests.swift | 90 +++++++++++++++++++ 2 files changed, 121 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift b/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift index 7f8efc91..0c5a8b89 100644 --- a/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift +++ b/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import Foundation @_spi(SourceKitLSP) import LanguageServerProtocol import SourceKitLSP import SwiftRefactor @@ -117,10 +118,21 @@ extension ConvertZeroParameterFunctionToComputedProperty: SyntaxRefactoringCodeA package static var title: String { "Convert to computed property" } static func nodeToRefactor(in scope: SyntaxCodeActionScope) -> Input? { - return scope.innermostNodeContainingRange?.findParentOfSelf( - ofType: FunctionDeclSyntax.self, - stoppingIf: { $0.is(CodeBlockSyntax.self) || $0.is(MemberBlockSyntax.self) } - ) + guard + let functionDecl = scope.innermostNodeContainingRange?.findParentOfSelf( + ofType: FunctionDeclSyntax.self, + stoppingIf: { $0.is(CodeBlockSyntax.self) || $0.is(MemberBlockSyntax.self) } + ) + else { + return nil + } + + // Only offer the conversion if the function has a return type and it's not Void + guard !isVoidReturnType(functionDecl.signature.returnClause?.type) else { + return nil + } + + return functionDecl } } @@ -178,3 +190,18 @@ extension [SourceEdit] { ) } } + +// MARK: - Helper Functions + +/// Checks if a return type is effectively Void (no return type, explicit Void, or empty tuple) +private func isVoidReturnType(_ returnType: TypeSyntax?) -> Bool { + guard let returnType else { + return true // No explicit return type is implicitly Void + } + + // Remove all whitespace to normalize the type description + let typeDescription = returnType.description.filter { !$0.isWhitespace } + + // Check for explicit Void or empty tuple () + return typeDescription == "Void" || typeDescription == "()" +} diff --git a/Tests/SourceKitLSPTests/CodeActionTests.swift b/Tests/SourceKitLSPTests/CodeActionTests.swift index 19e09397..22342450 100644 --- a/Tests/SourceKitLSPTests/CodeActionTests.swift +++ b/Tests/SourceKitLSPTests/CodeActionTests.swift @@ -1238,6 +1238,96 @@ final class CodeActionTests: SourceKitLSPTestCase { } } + func testConvertZeroParameterFunctionToComputedPropertyNotOfferedForImplicitVoid() async throws { + let testClient = try await TestSourceKitLSPClient(capabilities: clientCapabilitiesWithCodeActionSupport) + let uri = DocumentURI(for: .swift) + + let positions = testClient.openDocument( + """ + 1️⃣func test()2️⃣ { }3️⃣ + """, + uri: uri + ) + + let request = CodeActionRequest( + range: positions["1️⃣"].. Void2️⃣ { }3️⃣ + """, + uri: uri + ) + + let request = CodeActionRequest( + range: positions["1️⃣"].. ()2️⃣ { }3️⃣ + """, + uri: uri + ) + + let request = CodeActionRequest( + range: positions["1️⃣"].. Date: Wed, 7 Jan 2026 21:59:46 +0530 Subject: [PATCH 2/4] Made changes based on review --- .../SyntaxRefactoringCodeActionProvider.swift | 53 ++++++----- Tests/SourceKitLSPTests/CodeActionTests.swift | 87 ++++--------------- 2 files changed, 47 insertions(+), 93 deletions(-) diff --git a/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift b/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift index 0c5a8b89..490378a9 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 @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import Foundation @_spi(SourceKitLSP) import LanguageServerProtocol import SourceKitLSP import SwiftRefactor @@ -118,20 +117,13 @@ extension ConvertZeroParameterFunctionToComputedProperty: SyntaxRefactoringCodeA package static var title: String { "Convert to computed property" } static func nodeToRefactor(in scope: SyntaxCodeActionScope) -> Input? { - guard - let functionDecl = scope.innermostNodeContainingRange?.findParentOfSelf( - ofType: FunctionDeclSyntax.self, - stoppingIf: { $0.is(CodeBlockSyntax.self) || $0.is(MemberBlockSyntax.self) } - ) - else { + let functionDecl = scope.innermostNodeContainingRange?.findParentOfSelf( + ofType: FunctionDeclSyntax.self, + stoppingIf: { $0.is(CodeBlockSyntax.self) || $0.is(MemberBlockSyntax.self) } + ) + guard let functionDecl, !isVoidReturnType(functionDecl.signature.returnClause?.type) else { return nil } - - // Only offer the conversion if the function has a return type and it's not Void - guard !isVoidReturnType(functionDecl.signature.returnClause?.type) else { - return nil - } - return functionDecl } } @@ -194,14 +186,31 @@ extension [SourceEdit] { // MARK: - Helper Functions /// Checks if a return type is effectively Void (no return type, explicit Void, or empty tuple) -private func isVoidReturnType(_ returnType: TypeSyntax?) -> Bool { +func isVoidReturnType(_ returnType: TypeSyntax?) -> Bool { guard let returnType else { - return true // No explicit return type is implicitly Void + return true + } + switch returnType.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 + } +} + +// 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 + } } - - // Remove all whitespace to normalize the type description - let typeDescription = returnType.description.filter { !$0.isWhitespace } - - // Check for explicit Void or empty tuple () - return typeDescription == "Void" || typeDescription == "()" } diff --git a/Tests/SourceKitLSPTests/CodeActionTests.swift b/Tests/SourceKitLSPTests/CodeActionTests.swift index 22342450..755402b3 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 @@ -1239,95 +1239,40 @@ final class CodeActionTests: SourceKitLSPTestCase { } func testConvertZeroParameterFunctionToComputedPropertyNotOfferedForImplicitVoid() async throws { - let testClient = try await TestSourceKitLSPClient(capabilities: clientCapabilitiesWithCodeActionSupport) - let uri = DocumentURI(for: .swift) - - let positions = testClient.openDocument( + try await assertCodeActions( """ 1️⃣func test()2️⃣ { }3️⃣ """, - uri: uri - ) - - let request = CodeActionRequest( - range: positions["1️⃣"].. Void2️⃣ { }3️⃣ """, - uri: uri - ) - - let request = CodeActionRequest( - range: positions["1️⃣"].. ()2️⃣ { }3️⃣ """, - uri: uri - ) - - let request = CodeActionRequest( - range: positions["1️⃣"].. Date: Thu, 8 Jan 2026 09:59:57 +0530 Subject: [PATCH 3/4] removed: helper extension --- .../SyntaxRefactoringCodeActionProvider.swift | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift b/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift index 490378a9..f6bf39c2 100644 --- a/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift +++ b/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift @@ -199,18 +199,3 @@ func isVoidReturnType(_ returnType: TypeSyntax?) -> Bool { return false } } - -// 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 - } - } -} From a9331eacea47c4dbb380f0379c394f53a989fbd5 Mon Sep 17 00:00:00 2001 From: Divya Prakash Date: Thu, 8 Jan 2026 14:45:43 +0530 Subject: [PATCH 4/4] restored extensions and removed functions --- .../SyntaxRefactoringCodeActionProvider.swift | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift b/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift index f6bf39c2..2c309c6c 100644 --- a/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift +++ b/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift @@ -121,7 +121,7 @@ extension ConvertZeroParameterFunctionToComputedProperty: SyntaxRefactoringCodeA ofType: FunctionDeclSyntax.self, stoppingIf: { $0.is(CodeBlockSyntax.self) || $0.is(MemberBlockSyntax.self) } ) - guard let functionDecl, !isVoidReturnType(functionDecl.signature.returnClause?.type) else { + guard let functionDecl, !(functionDecl.signature.returnClause?.type.isVoid ?? true) else { return nil } return functionDecl @@ -183,19 +183,17 @@ extension [SourceEdit] { } } -// MARK: - Helper Functions +// MARK: - Helper Extensions -/// Checks if a return type is effectively Void (no return type, explicit Void, or empty tuple) -func isVoidReturnType(_ returnType: TypeSyntax?) -> Bool { - guard let returnType else { - return true - } - switch returnType.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 +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 + } } }