Skip to content

Commit dccabd4

Browse files
committed
Convert between computed properties and zero-parameters functions
1 parent 9cc77ac commit dccabd4

5 files changed

+694
-2
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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 ConvertComputedPropertyToZeroParameterFunction: SyntaxRefactoringProvider {
20+
public static func refactor(syntax: VariableDeclSyntax, in context: Void) -> FunctionDeclSyntax? {
21+
guard syntax.bindings.count == 1,
22+
let binding = syntax.bindings.first,
23+
let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self)
24+
else { return nil }
25+
26+
var statements: CodeBlockItemListSyntax
27+
28+
guard let typeAnnotation = binding.typeAnnotation,
29+
var accessorBlock = binding.accessorBlock
30+
else { return nil }
31+
32+
var effectSpecifiers: AccessorEffectSpecifiersSyntax?
33+
34+
switch accessorBlock.accessors {
35+
case .accessors(let accessors):
36+
guard accessors.count == 1, let accessor = accessors.first,
37+
accessor.accessorSpecifier.tokenKind == .keyword(.get), let codeBlock = accessor.body
38+
else { return nil }
39+
effectSpecifiers = accessor.effectSpecifiers
40+
statements = codeBlock.statements
41+
let accessorSpecifier = accessor.accessorSpecifier
42+
statements.leadingTrivia =
43+
accessorSpecifier.leadingTrivia + accessorSpecifier.trailingTrivia.droppingLeadingWhitespace
44+
+ codeBlock.leftBrace.leadingTrivia.droppingLeadingWhitespace
45+
+ codeBlock.leftBrace.trailingTrivia.droppingLeadingWhitespace
46+
+ statements.leadingTrivia
47+
statements.trailingTrivia += codeBlock.rightBrace.trivia.droppingLeadingWhitespace
48+
statements.trailingTrivia = statements.trailingTrivia.droppingTrailingWhitespace
49+
case .getter(let codeBlock):
50+
statements = codeBlock
51+
}
52+
53+
let returnType = typeAnnotation.type
54+
55+
var returnClause: ReturnClauseSyntax?
56+
let triviaAfterSignature: Trivia
57+
58+
if !returnType.isVoid {
59+
triviaAfterSignature = .space
60+
returnClause = ReturnClauseSyntax(
61+
arrow: .arrowToken(
62+
leadingTrivia: typeAnnotation.colon.leadingTrivia,
63+
trailingTrivia: typeAnnotation.colon.trailingTrivia
64+
),
65+
type: returnType
66+
)
67+
} else {
68+
triviaAfterSignature = typeAnnotation.colon.leadingTrivia + typeAnnotation.colon.trailingTrivia
69+
}
70+
71+
accessorBlock.leftBrace.leadingTrivia = accessorBlock.leftBrace.leadingTrivia.droppingLeadingWhitespace
72+
accessorBlock.rightBrace.trailingTrivia = accessorBlock.rightBrace.trailingTrivia.droppingTrailingWhitespace
73+
74+
let body = CodeBlockSyntax(
75+
leftBrace: accessorBlock.leftBrace,
76+
statements: statements,
77+
rightBrace: accessorBlock.rightBrace
78+
)
79+
80+
var parameterClause = FunctionParameterClauseSyntax(parameters: [])
81+
parameterClause.trailingTrivia = identifierPattern.identifier.trailingTrivia + triviaAfterSignature
82+
83+
let functionEffectSpecifiers = FunctionEffectSpecifiersSyntax(
84+
leadingTrivia: effectSpecifiers?.leadingTrivia,
85+
asyncSpecifier: effectSpecifiers?.asyncSpecifier,
86+
throwsClause: effectSpecifiers?.throwsClause,
87+
trailingTrivia: effectSpecifiers?.trailingTrivia.droppingTrailingWhitespace
88+
)
89+
let functionSignature = FunctionSignatureSyntax(
90+
parameterClause: parameterClause,
91+
effectSpecifiers: functionEffectSpecifiers,
92+
returnClause: returnClause
93+
)
94+
95+
return FunctionDeclSyntax(
96+
modifiers: syntax.modifiers,
97+
funcKeyword: .keyword(
98+
.func,
99+
leadingTrivia: syntax.bindingSpecifier.leadingTrivia,
100+
trailingTrivia: syntax.bindingSpecifier.trailingTrivia
101+
),
102+
name: identifierPattern.identifier.with(\.trailingTrivia, []),
103+
signature: functionSignature,
104+
body: body
105+
)
106+
}
107+
}
108+
109+
fileprivate extension TypeSyntax {
110+
var isVoid: Bool {
111+
switch self.as(TypeSyntaxEnum.self) {
112+
case .identifierType(let identifierType) where identifierType.name.text == "Void": return true
113+
case .tupleType(let tupleType) where tupleType.elements.isEmpty: return true
114+
default: return false
115+
}
116+
}
117+
}
118+
119+
fileprivate extension Trivia {
120+
var droppingLeadingWhitespace: Trivia {
121+
return Trivia(pieces: self.drop(while: \.isWhitespace))
122+
}
123+
124+
var droppingTrailingWhitespace: Trivia {
125+
return Trivia(pieces: self.reversed().drop(while: \.isWhitespace).reversed())
126+
}
127+
}
128+
129+
fileprivate extension TokenSyntax {
130+
var trivia: Trivia {
131+
return leadingTrivia + trailingTrivia
132+
}
133+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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 ConvertZeroParameterFunctionToComputedProperty: SyntaxRefactoringProvider {
20+
public static func refactor(syntax: FunctionDeclSyntax, in context: ()) -> VariableDeclSyntax? {
21+
guard syntax.signature.parameterClause.parameters.isEmpty,
22+
let body = syntax.body
23+
else { return nil }
24+
25+
let variableName = PatternSyntax(
26+
IdentifierPatternSyntax(
27+
leadingTrivia: syntax.funcKeyword.trailingTrivia,
28+
identifier: syntax.name
29+
)
30+
)
31+
32+
let triviaFromParameters =
33+
(syntax.signature.parameterClause.leftParen.trivia + syntax.signature.parameterClause.rightParen.trivia)
34+
.droppingTrailingWhitespace
35+
36+
var variableType: TypeAnnotationSyntax?
37+
38+
if let returnClause = syntax.signature.returnClause {
39+
variableType = TypeAnnotationSyntax(
40+
colon: .colonToken(
41+
leadingTrivia: triviaFromParameters + returnClause.arrow.leadingTrivia,
42+
trailingTrivia: returnClause.arrow.trailingTrivia
43+
),
44+
type: returnClause.type
45+
)
46+
} else {
47+
variableType = TypeAnnotationSyntax(
48+
colon: .colonToken(
49+
leadingTrivia: triviaFromParameters,
50+
trailingTrivia: .space
51+
),
52+
type: TypeSyntax("Void").with(\.trailingTrivia, .space)
53+
)
54+
}
55+
56+
let accessorBlock = AccessorBlockSyntax(
57+
leftBrace: body.leftBrace,
58+
accessors: .getter(body.statements),
59+
rightBrace: body.rightBrace
60+
)
61+
62+
return VariableDeclSyntax(
63+
modifiers: syntax.modifiers,
64+
.var,
65+
name: variableName,
66+
type: variableType,
67+
accessorBlock: accessorBlock
68+
)
69+
}
70+
}
71+
72+
fileprivate extension TokenSyntax {
73+
var trivia: Trivia {
74+
return leadingTrivia + trailingTrivia
75+
}
76+
}
77+
78+
fileprivate extension Trivia {
79+
var droppingTrailingWhitespace: Trivia {
80+
return Trivia(pieces: self.reversed().drop(while: \.isWhitespace).reversed())
81+
}
82+
}

Sources/SwiftSyntaxBuilder/ConvenienceInitializers.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ extension VariableDeclSyntax {
403403
_ bindingSpecifier: Keyword,
404404
name: PatternSyntax,
405405
type: TypeAnnotationSyntax? = nil,
406-
initializer: InitializerClauseSyntax? = nil
406+
initializer: InitializerClauseSyntax? = nil,
407+
accessorBlock: AccessorBlockSyntax? = nil
407408
) {
408409
self.init(
409410
leadingTrivia: leadingTrivia,
@@ -414,7 +415,8 @@ extension VariableDeclSyntax {
414415
PatternBindingSyntax(
415416
pattern: name,
416417
typeAnnotation: type,
417-
initializer: initializer
418+
initializer: initializer,
419+
accessorBlock: accessorBlock
418420
)
419421
}
420422
}

0 commit comments

Comments
 (0)