Skip to content

Commit defcaca

Browse files
committed
Add capability to convert computed property to stored property
1 parent 494f111 commit defcaca

5 files changed

+167
-20
lines changed

Sources/SwiftRefactor/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
add_swift_syntax_library(SwiftRefactor
1010
AddSeparatorsToIntegerLiteral.swift
1111
CallToTrailingClosures.swift
12+
ConvertComputedPropertyToStored.swift
1213
ConvertStoredPropertyToComputed.swift
1314
ExpandEditorPlaceholder.swift
1415
FormatRawStringLiteral.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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+
#if swift(>=6)
14+
public import SwiftSyntax
15+
#else
16+
import SwiftSyntax
17+
#endif
18+
19+
public struct ConvertComputedPropertyToStored: SyntaxRefactoringProvider {
20+
public static func refactor(syntax: VariableDeclSyntax, in context: ()) -> VariableDeclSyntax? {
21+
guard syntax.bindings.count == 1, let binding = syntax.bindings.first,
22+
let accessorBlock = binding.accessorBlock, case let .getter(body) = accessorBlock.accessors, !body.isEmpty
23+
else {
24+
return nil
25+
}
26+
27+
var initializer: InitializerClauseSyntax?
28+
29+
if body.count > 1 {
30+
let closure = ClosureExprSyntax(
31+
statements: body.with(\.trailingTrivia, .newline)
32+
)
33+
34+
initializer = InitializerClauseSyntax(
35+
value: FunctionCallExprSyntax(
36+
leadingTrivia: .space,
37+
callee: closure
38+
)
39+
)
40+
} else if let item = body.first?.item.as(ExprSyntax.self) {
41+
initializer = InitializerClauseSyntax(
42+
value: item.with(\.leadingTrivia, .space).with(\.trailingTrivia, Trivia())
43+
)
44+
}
45+
46+
guard let initializer else { return nil }
47+
48+
let newBinding =
49+
binding
50+
.with(\.initializer, initializer)
51+
.with(\.accessorBlock, nil)
52+
53+
let bindingSpecifier = syntax.bindingSpecifier
54+
.with(\.tokenKind, .keyword(.let))
55+
56+
return
57+
syntax
58+
.with(\.bindingSpecifier, bindingSpecifier)
59+
.with(\.bindings, PatternBindingListSyntax([newBinding]))
60+
}
61+
}
62+
63+
fileprivate extension FunctionCallExprSyntax {
64+
65+
init(
66+
leadingTrivia: Trivia? = nil,
67+
callee: some ExprSyntaxProtocol,
68+
argumentList: () -> LabeledExprListSyntax = { [] },
69+
trailingTrivia: Trivia? = nil
70+
) {
71+
let argumentList = argumentList()
72+
self.init(
73+
leadingTrivia: leadingTrivia,
74+
calledExpression: callee,
75+
leftParen: .leftParenToken(),
76+
arguments: argumentList,
77+
rightParen: .rightParenToken(),
78+
trailingTrivia: trailingTrivia
79+
)
80+
}
81+
}

Sources/SwiftRefactor/ConvertStoredPropertyToComputed.swift

+9-8
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,29 @@ import SwiftSyntax
1717
#endif
1818

1919
public struct ConvertStoredPropertyToComputed: SyntaxRefactoringProvider {
20-
2120
public static func refactor(syntax: VariableDeclSyntax, in context: ()) -> VariableDeclSyntax? {
2221
guard syntax.bindings.count == 1, let binding = syntax.bindings.first, let initializer = binding.initializer else {
2322
return nil
2423
}
25-
24+
2625
let body = CodeBlockItemSyntax(
2726
item: .expr(initializer.value)
2827
)
29-
30-
let newBinding = binding
28+
29+
let newBinding =
30+
binding
3131
.with(\.initializer, nil)
3232
.with(
3333
\.accessorBlock,
34-
AccessorBlockSyntax(
34+
AccessorBlockSyntax(
3535
leftBrace: .leftBraceToken(trailingTrivia: .space),
3636
accessors: .getter(CodeBlockItemListSyntax([body])),
3737
rightBrace: .rightBraceToken(leadingTrivia: .space)
38-
)
38+
)
3939
)
40-
41-
return syntax
40+
41+
return
42+
syntax
4243
.with(\.bindingSpecifier, .keyword(.var, trailingTrivia: .space))
4344
.with(\.bindings, PatternBindingListSyntax([newBinding]))
4445
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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 SwiftRefactor
14+
import SwiftSyntax
15+
import SwiftSyntaxBuilder
16+
import XCTest
17+
import _SwiftSyntaxTestSupport
18+
19+
final class ConvertComputedPropertyToStoredTest: XCTestCase {
20+
func testToStored() throws {
21+
let baseline: DeclSyntax = """
22+
var defaultColor: Color { Color() }
23+
"""
24+
25+
let expected: DeclSyntax = """
26+
let defaultColor: Color = Color()
27+
"""
28+
29+
try assertRefactorConvert(baseline, expected: expected)
30+
}
31+
32+
func testToStoredWithMultipleStatementsInAccessor() throws {
33+
let baseline: DeclSyntax = """
34+
var defaultColor: Color {
35+
let color = Color()
36+
return color
37+
}
38+
"""
39+
40+
let expected: DeclSyntax = """
41+
let defaultColor: Color = {
42+
let color = Color()
43+
return color
44+
}()
45+
"""
46+
47+
try assertRefactorConvert(baseline, expected: expected)
48+
}
49+
}
50+
51+
fileprivate func assertRefactorConvert(
52+
_ callDecl: DeclSyntax,
53+
expected: DeclSyntax?,
54+
file: StaticString = #filePath,
55+
line: UInt = #line
56+
) throws {
57+
try assertRefactor(
58+
callDecl,
59+
context: (),
60+
provider: ConvertComputedPropertyToStored.self,
61+
expected: expected,
62+
file: file,
63+
line: line
64+
)
65+
}

Tests/SwiftRefactorTest/ConvertStoredPropertyToComputed.swift renamed to Tests/SwiftRefactorTest/ConvertStoredPropertyToComputedTest.swift

+11-12
Original file line numberDiff line numberDiff line change
@@ -17,52 +17,51 @@ import XCTest
1717
import _SwiftSyntaxTestSupport
1818

1919
final class ConvertStoredPropertyToComputedTest: XCTestCase {
20-
2120
func testRefactoringStoredPropertyWithInitializer1() throws {
2221
let baseline: DeclSyntax = """
2322
static let defaultColor: Color = .red
2423
"""
25-
24+
2625
let expected: DeclSyntax = """
2726
static var defaultColor: Color { .red }
2827
"""
29-
28+
3029
try assertRefactorConvert(baseline, expected: expected)
3130
}
32-
31+
3332
func testRefactoringStoredPropertyWithInitializer2() throws {
3433
let baseline: DeclSyntax = """
3534
static let defaultColor: Color = Color.red
3635
"""
37-
36+
3837
let expected: DeclSyntax = """
3938
static var defaultColor: Color { Color.red }
4039
"""
41-
40+
4241
try assertRefactorConvert(baseline, expected: expected)
4342
}
44-
43+
4544
func testRefactoringStoredPropertyWithInitializer3() throws {
4645
let baseline: DeclSyntax = """
4746
var defaultColor: Color = Color.red
4847
"""
49-
48+
5049
let expected: DeclSyntax = """
5150
var defaultColor: Color { Color.red }
5251
"""
53-
52+
5453
try assertRefactorConvert(baseline, expected: expected)
5554
}
56-
55+
5756
func testRefactoringStoredPropertyWithInitializer4() throws {
5857
let baseline: DeclSyntax = """
5958
var defaultColor: Color = Color()
6059
"""
61-
60+
6261
let expected: DeclSyntax = """
6362
var defaultColor: Color { Color() }
6463
"""
65-
64+
6665
try assertRefactorConvert(baseline, expected: expected)
6766
}
6867
}

0 commit comments

Comments
 (0)