Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,19 @@ var targets: [Target] = [
exclude: ["CMakeLists.txt"],
),

.testTarget(
name: "SwiftSyntaxCodeActionsTests",
dependencies: [
"SwiftSyntaxCodeActions"
]
+ swiftSyntaxDependencies([
"SwiftParser",
"SwiftRefactor",
"SwiftSyntax",
"SwiftSyntaxBuilder",
]),
),

// MARK: SwiftSourceKitClientPlugin

.target(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 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
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftRefactor
package import SwiftSyntax

/// Format an integer literal by inserting underscores at base-appropriate
/// locations.
///
/// This pass will also clean up any errant underscores.
///
/// ## Before
///
/// ```swift
/// 123456789
/// 0xFFFFFFFFF
/// 0b1_0_1_0
/// ```
///
/// ## After
///
/// ```swift
/// 123_456_789
/// 0xF_FFFF_FFFF
/// 0b1_010
/// ```
package struct AddSeparatorsToIntegerLiteral: SyntaxRefactoringProvider {
package static func refactor(
syntax lit: IntegerLiteralExprSyntax,
in context: Void
) throws -> IntegerLiteralExprSyntax {
if lit.literal.text.contains("_") {
let strippedLiteral = try RemoveSeparatorsFromIntegerLiteral.refactor(syntax: lit)
return self.addSeparators(to: strippedLiteral)
} else {
return self.addSeparators(to: lit)
}
}

private static func addSeparators(to lit: IntegerLiteralExprSyntax) -> IntegerLiteralExprSyntax {
var formattedText = ""
let (prefix, value) = lit.split()
formattedText += prefix
formattedText += value.byAddingGroupSeparators(at: lit.idealGroupSize)
return
lit
.with(\.literal, lit.literal.with(\.tokenKind, .integerLiteral(formattedText)))
}
}

extension Substring {
fileprivate func byAddingGroupSeparators(at interval: Int) -> String {
var result = ""
result.reserveCapacity(self.count)
for (i, char) in self.filter({ $0 != "_" }).reversed().enumerated() {
if i > 0 && i % interval == 0 {
result.append("_")
}
result.append(char)
}
return String(result.reversed())
}
}
12 changes: 12 additions & 0 deletions Sources/SwiftSyntaxCodeActions/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
add_library(SwiftSyntaxCodeActions STATIC
AddDocumentation.swift
AddExplicitEnumRawValues.swift
AddSeparatorsToIntegerLiteral.swift
ApplyDeMorganLaw.swift
ConvertCommentToDocComment.swift
ConvertComputedPropertyToStored.swift
ConvertComputedPropertyToZeroParameterFunction.swift
ConvertIfLetToGuard.swift
ConvertIntegerLiteral.swift
ConvertJSONToCodableStruct.swift
ConvertStoredPropertyToComputed.swift
ConvertStringConcatenationToStringInterpolation.swift
ConvertZeroParameterFunctionToComputedProperty.swift
DeclModifierRemover.swift
FormatRawStringLiteral.swift
IndentationRemover.swift
IntegerLiteralUtilities.swift
MigrateToNewIfLetSyntax.swift
OpaqueParameterToGeneric.swift
PackageManifestEdits.swift
RemoveRedundantParentheses.swift
RemoveSeparatorsFromIntegerLiteral.swift
SyntaxCodeActionProvider.swift
SyntaxCodeActions.swift
SyntaxRefactoringCodeActionProvider.swift
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 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
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftRefactor
package import SwiftSyntax
import SwiftSyntaxBuilder

package struct ConvertComputedPropertyToStored: SyntaxRefactoringProvider {
package static func refactor(syntax: VariableDeclSyntax, in context: ()) throws -> VariableDeclSyntax {
guard syntax.bindings.count == 1, let binding = syntax.bindings.first else {
throw RefactoringNotApplicableError("unsupported variable declaration")
}

guard let accessorBlock = binding.accessorBlock,
case let .getter(body) = accessorBlock.accessors, !body.isEmpty
else {
throw RefactoringNotApplicableError("getter is missing or empty")
}

let refactored = { (initializer: InitializerClauseSyntax) -> VariableDeclSyntax in
let newBinding =
binding
.with(\.initializer, initializer)
.with(\.accessorBlock, nil)

let bindingSpecifier = syntax.bindingSpecifier
.with(\.tokenKind, .keyword(.let))

return
syntax
.with(\.bindingSpecifier, bindingSpecifier)
.with(\.bindings, PatternBindingListSyntax([newBinding]))
}

guard body.count == 1 else {
let closure = ClosureExprSyntax(
leftBrace: accessorBlock.leftBrace,
statements: body,
rightBrace: accessorBlock.rightBrace
)

return refactored(
InitializerClauseSyntax(
equal: .equalToken(trailingTrivia: .space),
value: FunctionCallExprSyntax(callee: closure)
)
)
}

guard body.count == 1, let item = body.first?.item else {
throw RefactoringNotApplicableError("getter body is not a single expression")
}

if let item = item.as(ReturnStmtSyntax.self), let expression = item.expression {
let trailingTrivia: Trivia = expression.leadingTrivia.isEmpty ? .space : []
return refactored(
InitializerClauseSyntax(
leadingTrivia: accessorBlock.leftBrace.trivia,
equal: .equalToken(trailingTrivia: trailingTrivia),
value: expression,
trailingTrivia: accessorBlock.rightBrace.trivia.droppingTrailingWhitespace
)
)
} else if var item = item.as(ExprSyntax.self) {
item.trailingTrivia = item.trailingTrivia.droppingTrailingWhitespace
return refactored(
InitializerClauseSyntax(
equal: .equalToken(trailingTrivia: .space),
value: item,
trailingTrivia: accessorBlock.trailingTrivia
)
)
}

throw RefactoringNotApplicableError("could not extract initial value of stored property")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 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
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftRefactor
package import SwiftSyntax

package struct ConvertComputedPropertyToZeroParameterFunction: SyntaxRefactoringProvider {
package static func refactor(syntax: VariableDeclSyntax, in context: Void) throws -> FunctionDeclSyntax {
guard syntax.bindings.count == 1,
let binding = syntax.bindings.first,
let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self)
else { throw RefactoringNotApplicableError("unsupported variable declaration") }

var statements: CodeBlockItemListSyntax

guard let typeAnnotation = binding.typeAnnotation,
var accessorBlock = binding.accessorBlock
else { throw RefactoringNotApplicableError("no type annotation or stored") }

var effectSpecifiers: AccessorEffectSpecifiersSyntax?

switch accessorBlock.accessors {
case .accessors(let accessors):
guard accessors.count == 1, let accessor = accessors.first,
accessor.accessorSpecifier.tokenKind == .keyword(.get), let codeBlock = accessor.body
else { throw RefactoringNotApplicableError("not a getter-only declaration") }
effectSpecifiers = accessor.effectSpecifiers
statements = codeBlock.statements
let accessorSpecifier = accessor.accessorSpecifier
statements.leadingTrivia =
accessorSpecifier.leadingTrivia + accessorSpecifier.trailingTrivia.droppingLeadingWhitespace
+ codeBlock.leftBrace.leadingTrivia.droppingLeadingWhitespace
+ codeBlock.leftBrace.trailingTrivia.droppingLeadingWhitespace
+ statements.leadingTrivia
statements.trailingTrivia += codeBlock.rightBrace.trivia.droppingLeadingWhitespace
statements.trailingTrivia = statements.trailingTrivia.droppingTrailingWhitespace
case .getter(let codeBlock):
statements = codeBlock
}

let returnType = typeAnnotation.type

var returnClause: ReturnClauseSyntax?
let triviaAfterSignature: Trivia

if !returnType.isVoid {
triviaAfterSignature = .space
returnClause = ReturnClauseSyntax(
arrow: .arrowToken(
leadingTrivia: typeAnnotation.colon.leadingTrivia,
trailingTrivia: typeAnnotation.colon.trailingTrivia
),
type: returnType
)
} else {
triviaAfterSignature = typeAnnotation.colon.leadingTrivia + typeAnnotation.colon.trailingTrivia
}

accessorBlock.leftBrace.leadingTrivia = accessorBlock.leftBrace.leadingTrivia.droppingLeadingWhitespace
accessorBlock.rightBrace.trailingTrivia = accessorBlock.rightBrace.trailingTrivia.droppingTrailingWhitespace

let body = CodeBlockSyntax(
leftBrace: accessorBlock.leftBrace,
statements: statements,
rightBrace: accessorBlock.rightBrace
)

var parameterClause = FunctionParameterClauseSyntax(parameters: [])
parameterClause.trailingTrivia = identifierPattern.identifier.trailingTrivia + triviaAfterSignature

let functionEffectSpecifiers = FunctionEffectSpecifiersSyntax(
asyncSpecifier: effectSpecifiers?.asyncSpecifier,
throwsClause: effectSpecifiers?.throwsClause
)
let functionSignature = FunctionSignatureSyntax(
parameterClause: parameterClause,
effectSpecifiers: functionEffectSpecifiers,
returnClause: returnClause
)

return FunctionDeclSyntax(
modifiers: syntax.modifiers,
funcKeyword: .keyword(
.func,
leadingTrivia: syntax.bindingSpecifier.leadingTrivia,
trailingTrivia: syntax.bindingSpecifier.trailingTrivia
),
name: identifierPattern.identifier.with(\.trailingTrivia, []),
signature: functionSignature,
body: body
)
}
}
Loading