Skip to content

Commit 7b61dfa

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

5 files changed

+551
-2
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
let accessorBlock = binding.accessorBlock
30+
else { return nil }
31+
32+
switch accessorBlock.accessors {
33+
case .accessors(let accessors):
34+
guard accessors.count == 1, let accessor = accessors.first,
35+
accessor.accessorSpecifier.tokenKind == .keyword(.get), let codeBlock = accessor.body
36+
else { return nil }
37+
statements = codeBlock.statements
38+
statements.leadingTrivia = accessor.leadingTrivia + codeBlock.leadingTrivia + statements.leadingTrivia
39+
statements.trailingTrivia += codeBlock.trailingTrivia + accessor.trailingTrivia
40+
statements.trailingTrivia = statements.trailingTrivia.droppingTrailingWhitespace
41+
case .getter(let codeBlock):
42+
statements = codeBlock
43+
}
44+
45+
let returnType = typeAnnotation.type
46+
47+
var returnClause: ReturnClauseSyntax?
48+
let triviaAfterSignature: Trivia
49+
50+
if !returnType.isVoid {
51+
triviaAfterSignature = .space
52+
returnClause = ReturnClauseSyntax(
53+
arrow: .arrowToken(
54+
leadingTrivia: typeAnnotation.colon.leadingTrivia,
55+
trailingTrivia: typeAnnotation.colon.trailingTrivia
56+
),
57+
type: returnType
58+
)
59+
} else {
60+
triviaAfterSignature = typeAnnotation.colon.leadingTrivia + typeAnnotation.colon.trailingTrivia
61+
}
62+
63+
let body = CodeBlockSyntax(
64+
leftBrace: accessorBlock.leftBrace,
65+
statements: statements,
66+
rightBrace: accessorBlock.rightBrace
67+
)
68+
69+
var parameterClause = FunctionParameterClauseSyntax(parameters: [])
70+
parameterClause.trailingTrivia = identifierPattern.identifier.trailingTrivia + triviaAfterSignature
71+
72+
let functionSignature = FunctionSignatureSyntax(
73+
parameterClause: parameterClause,
74+
returnClause: returnClause
75+
)
76+
77+
return FunctionDeclSyntax(
78+
modifiers: syntax.modifiers,
79+
funcKeyword: .keyword(.func, trailingTrivia: syntax.bindingSpecifier.trailingTrivia),
80+
name: identifierPattern.identifier.with(\.trailingTrivia, []),
81+
signature: functionSignature,
82+
body: body
83+
)
84+
}
85+
}
86+
87+
fileprivate extension TypeSyntax {
88+
var isVoid: Bool {
89+
switch self.as(TypeSyntaxEnum.self) {
90+
case .identifierType(let identifierType) where identifierType.name.text == "Void": return true
91+
case .tupleType(let tupleType) where tupleType.elements.isEmpty: return true
92+
default: return false
93+
}
94+
}
95+
}
96+
97+
fileprivate extension Trivia {
98+
var droppingTrailingWhitespace: Trivia {
99+
return Trivia(pieces: self.reversed().drop(while: \.isWhitespace).reversed())
100+
}
101+
}
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)