Skip to content

Commit

Permalink
Add a package manifest edit refactor to introduce a new plugin usage …
Browse files Browse the repository at this point in the history
…for a target

This allows one to add a plugin usage to a given target, given the target
name and description of how the plugin should be used.
  • Loading branch information
DougGregor committed Dec 1, 2024
1 parent 30ae9e6 commit 4061163
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 27 deletions.
1 change: 1 addition & 0 deletions Sources/SwiftRefactor/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ add_swift_syntax_library(SwiftRefactor

PackageManifest/AbsolutePath.swift
PackageManifest/AddPackageDependency.swift
PackageManifest/AddPluginUsage.swift
PackageManifest/AddProduct.swift
PackageManifest/AddTarget.swift
PackageManifest/AddTargetDependency.swift
Expand Down
65 changes: 65 additions & 0 deletions Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftParser
import SwiftSyntax
import SwiftSyntaxBuilder

/// Add a plugin usage to a particular target in the manifest's source
/// code.
public struct AddPluginUsage: ManifestEditRefactoringProvider {
public struct Context {
public let targetName: String
public let pluginUsage: TargetDescription.PluginUsage

public init(targetName: String, pluginUsage: TargetDescription.PluginUsage) {
self.targetName = targetName
self.pluginUsage = pluginUsage
}
}

/// The set of argument labels that can occur after the "plugins"
/// argument in the Target initializers. (There aren't any right now)
///
/// TODO: Could we generate this from the the PackageDescription module, so
/// we don't have keep it up-to-date manually?
private static let argumentLabelsAfterPluginUsages: Set<String> = []

/// Produce the set of source edits needed to add the given package
/// dependency to the given manifest file.
public static func manifestRefactor(
syntax manifest: SourceFileSyntax,
in context: Context
) throws -> PackageEditResult {
let targetName = context.targetName
let pluginUsage = context.pluginUsage

guard let packageCall = manifest.findCall(calleeName: "Package") else {
throw ManifestEditError.cannotFindPackage
}

// Find the target to be modified.
let targetCall = try packageCall.findManifestTargetCall(targetName: targetName)

let newTargetCall = try targetCall.appendingToArrayArgument(
label: "plugins",
trailingLabels: Self.argumentLabelsAfterPluginUsages,
newElement: pluginUsage.asSyntax()
)

return PackageEditResult(
manifestEdits: [
.replace(targetCall, with: newTargetCall.description)
]
)
}
}
28 changes: 2 additions & 26 deletions Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,32 +61,8 @@ public struct AddTargetDependency: ManifestEditRefactoringProvider {
throw ManifestEditError.cannotFindPackage
}

// Dig out the array of targets.
guard let targetsArgument = packageCall.findArgument(labeled: "targets"),
let targetArray = targetsArgument.expression.findArrayArgument()
else {
throw ManifestEditError.cannotFindTargets
}

// Look for a call whose name is a string literal matching the
// requested target name.
func matchesTargetCall(call: FunctionCallExprSyntax) -> Bool {
guard let nameArgument = call.findArgument(labeled: "name") else {
return false
}

guard let stringLiteral = nameArgument.expression.as(StringLiteralExprSyntax.self),
let literalValue = stringLiteral.representedLiteralValue
else {
return false
}

return literalValue == targetName
}

guard let targetCall = FunctionCallExprSyntax.findFirst(in: targetArray, matching: matchesTargetCall) else {
throw ManifestEditError.cannotFindTarget(targetName: targetName)
}
// Find the target to be modified.
let targetCall = try packageCall.findManifestTargetCall(targetName: targetName)

let newTargetCall = try addTargetDependencyLocal(
dependency,
Expand Down
35 changes: 35 additions & 0 deletions Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,41 @@ extension SyntaxProtocol {
}
}

extension FunctionCallExprSyntax {
/// Find the call that forms a target with the given name in this
/// package manifest.
func findManifestTargetCall(targetName: String) throws -> FunctionCallExprSyntax {
// Dig out the array of targets.
guard let targetsArgument = findArgument(labeled: "targets"),
let targetArray = targetsArgument.expression.findArrayArgument()
else {
throw ManifestEditError.cannotFindTargets
}

// Look for a call whose name is a string literal matching the
// requested target name.
func matchesTargetCall(call: FunctionCallExprSyntax) -> Bool {
guard let nameArgument = call.findArgument(labeled: "name") else {
return false
}

guard let stringLiteral = nameArgument.expression.as(StringLiteralExprSyntax.self),
let literalValue = stringLiteral.representedLiteralValue
else {
return false
}

return literalValue == targetName
}

guard let targetCall = FunctionCallExprSyntax.findFirst(in: targetArray, matching: matchesTargetCall) else {
throw ManifestEditError.cannotFindTarget(targetName: targetName)
}

return targetCall
}
}

extension ArrayExprSyntax {
/// Produce a new array literal expression that appends the given
/// element, while trying to maintain similar indentation.
Expand Down
29 changes: 28 additions & 1 deletion Sources/SwiftRefactor/PackageManifest/TargetDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ public struct TargetDescription {

public let checksum: String?

/// The usages of package plugins by the target.
public let pluginUsages: [PluginUsage]?

/// Represents a target's usage of a plugin target or product.
public enum PluginUsage {
case plugin(name: String, package: String?)
}

public enum TargetKind: String {
case binary
case executable
Expand All @@ -43,20 +51,23 @@ public struct TargetDescription {
case target(name: String)
case product(name: String, package: String?)
}

public init(
name: String,
type: TargetKind = .library,
dependencies: [Dependency] = [],
path: String? = nil,
url: String? = nil,
checksum: String? = nil
checksum: String? = nil,
pluginUsages: [PluginUsage]? = nil
) {
self.name = name
self.type = type
self.dependencies = dependencies
self.path = path
self.url = url
self.checksum = checksum
self.pluginUsages = pluginUsages
}
}

Expand Down Expand Up @@ -90,6 +101,10 @@ extension TargetDescription: ManifestSyntaxRepresentable {
// Only for plugins
arguments.appendIf(label: "checksum", stringLiteral: checksum)

if let pluginUsages {
arguments.appendIfNonEmpty(label: "plugins", arrayLiteral: pluginUsages)
}

let separateParen: String = arguments.count > 1 ? "\n" : ""
let argumentsSyntax = LabeledExprListSyntax(arguments)
return ".\(raw: functionName)(\(argumentsSyntax)\(raw: separateParen))"
Expand All @@ -113,3 +128,15 @@ extension TargetDescription.Dependency: ManifestSyntaxRepresentable {
}
}
}

extension TargetDescription.PluginUsage: ManifestSyntaxRepresentable {
func asSyntax() -> ExprSyntax {
switch self {
case .plugin(name: let name, package: nil):
".plugin(name: \(literal: name))"

case .plugin(name: let name, package: let package):
".plugin(name: \(literal: name), package: \(literal: package))"
}
}
}
34 changes: 34 additions & 0 deletions Tests/SwiftRefactorTest/ManifestEditTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,40 @@ final class ManifestEditTests: XCTestCase {
)
}

func testAddJava2SwiftPlugin() throws {
try assertManifestRefactor(
"""
// swift-tools-version: 5.7
let package = Package(
name: "packages",
targets: [
.target(
name: "MyLib"
)
]
)
""",
expectedManifest: """
// swift-tools-version: 5.7
let package = Package(
name: "packages",
targets: [
.target(
name: "MyLib",
plugins: [
.plugin(name: "Java2SwiftPlugin", package: "swift-java"),
]
)
]
)
""",
provider: AddPluginUsage.self,
context: .init(
targetName: "MyLib",
pluginUsage: .plugin(name: "Java2SwiftPlugin", package: "swift-java")
)
)
}
}

/// Assert that applying the given edit/refactor operation to the manifest
Expand Down

0 comments on commit 4061163

Please sign in to comment.