Skip to content

Commit e6bef7b

Browse files
committed
Port "Add Product" manifest edit refactor over from SwiftPM
1 parent 74be5c9 commit e6bef7b

File tree

6 files changed

+234
-2
lines changed

6 files changed

+234
-2
lines changed

Sources/SwiftRefactor/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ add_swift_syntax_library(SwiftRefactor
2424

2525
PackageManifest/AbsolutePath.swift
2626
PackageManifest/AddPackageDependency.swift
27+
PackageManifest/AddProduct.swift
2728
PackageManifest/AddTarget.swift
2829
PackageManifest/AddTargetDependency.swift
2930
PackageManifest/ManifestEditError.swift
@@ -32,6 +33,8 @@ add_swift_syntax_library(SwiftRefactor
3233
PackageManifest/PackageDependency.swift
3334
PackageManifest/PackageEditResult.swift
3435
PackageManifest/PackageIdentity.swift
36+
PackageManifest/ProductDescription.swift
37+
PackageManifest/ProductType.swift
3538
PackageManifest/RelativePath.swift
3639
PackageManifest/SemanticVersion.swift
3740
PackageManifest/SourceControlURL.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftParser
14+
import SwiftSyntax
15+
import SwiftSyntaxBuilder
16+
17+
/// Add a product to the manifest's source code.
18+
public struct AddProduct: ManifestEditRefactoringProvider {
19+
public struct Context {
20+
public let product: ProductDescription
21+
22+
public init(product: ProductDescription) {
23+
self.product = product
24+
}
25+
}
26+
/// The set of argument labels that can occur after the "products"
27+
/// argument in the Package initializers.
28+
///
29+
/// TODO: Could we generate this from the the PackageDescription module, so
30+
/// we don't have keep it up-to-date manually?
31+
private static let argumentLabelsAfterProducts: Set<String> = [
32+
"dependencies",
33+
"targets",
34+
"swiftLanguageVersions",
35+
"cLanguageStandard",
36+
"cxxLanguageStandard",
37+
]
38+
39+
/// Produce the set of source edits needed to add the given package
40+
/// dependency to the given manifest file.
41+
public static func manifestRefactor(
42+
syntax manifest: SourceFileSyntax,
43+
in context: Context
44+
) throws -> PackageEditResult {
45+
let product = context.product
46+
47+
guard let packageCall = manifest.findCall(calleeName: "Package") else {
48+
throw ManifestEditError.cannotFindPackage
49+
}
50+
51+
let newPackageCall = try packageCall.appendingToArrayArgument(
52+
label: "products",
53+
trailingLabels: argumentLabelsAfterProducts,
54+
newElement: product.asSyntax()
55+
)
56+
57+
return PackageEditResult(
58+
manifestEdits: [
59+
.replace(packageCall, with: newPackageCall.description)
60+
]
61+
)
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2014-2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
15+
/// Syntactic wrapper type that describes a product for refactoring
16+
/// purposes but does not interpret its contents.
17+
public struct ProductDescription {
18+
/// The name of the product.
19+
public let name: String
20+
21+
/// The targets in the product.
22+
public let targets: [String]
23+
24+
/// The type of product.
25+
public let type: ProductType
26+
27+
public init(
28+
name: String,
29+
type: ProductType,
30+
targets: [String]
31+
) {
32+
self.name = name
33+
self.type = type
34+
self.targets = targets
35+
}
36+
}
37+
38+
extension ProductDescription: ManifestSyntaxRepresentable {
39+
/// The function name in the package manifest.
40+
///
41+
/// Some of these are actually invalid, but it's up to the caller
42+
/// to check the precondition.
43+
private var functionName: String {
44+
switch type {
45+
case .executable: "executable"
46+
case .library(_): "library"
47+
case .macro: "macro"
48+
case .plugin: "plugin"
49+
case .snippet: "snippet"
50+
case .test: "test"
51+
}
52+
}
53+
54+
func asSyntax() -> ExprSyntax {
55+
var arguments: [LabeledExprSyntax] = []
56+
arguments.append(label: "name", stringLiteral: name)
57+
58+
// Libraries have a type.
59+
if case .library(let libraryType) = type {
60+
switch libraryType {
61+
case .automatic:
62+
break
63+
64+
case .dynamic, .static:
65+
arguments.append(
66+
label: "type",
67+
expression: ".\(raw: libraryType.rawValue)"
68+
)
69+
}
70+
}
71+
72+
arguments.appendIfNonEmpty(
73+
label: "targets",
74+
arrayLiteral: targets
75+
)
76+
77+
let separateParen: String = arguments.count > 1 ? "\n" : ""
78+
let argumentsSyntax = LabeledExprListSyntax(arguments)
79+
return ".\(raw: functionName)(\(argumentsSyntax)\(raw: separateParen))"
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2014-2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// Syntactic wrapper type that describes a product type for refactoring
14+
/// purposes but does not interpret its contents.
15+
public enum ProductType {
16+
/// The type of library.
17+
public enum LibraryType: String, Codable, Sendable {
18+
19+
/// Static library.
20+
case `static`
21+
22+
/// Dynamic library.
23+
case `dynamic`
24+
25+
/// The type of library is unspecified and should be decided by package manager.
26+
case automatic
27+
}
28+
29+
/// A library product.
30+
case library(LibraryType)
31+
32+
/// An executable product.
33+
case executable
34+
35+
/// An executable code snippet.
36+
case snippet
37+
38+
/// An plugin product.
39+
case plugin
40+
41+
/// A test product.
42+
case test
43+
44+
/// A macro product.
45+
case `macro`
46+
}

Sources/SwiftRefactor/PackageManifest/StringUtils.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ extension String {
2020
let replacementUnichar: UnicodeScalar = "_"
2121
var mangledUnichars: [UnicodeScalar] = self.unicodeScalars.map({
2222
switch $0.value {
23-
case // A-Z
23+
case // A-Z
2424
0x0041...0x005A,
2525
// a-z
2626
0x0061...0x007A,
@@ -199,7 +199,7 @@ extension String {
199199
// Apply further restrictions to the prefix.
200200
loop: for (idx, c) in mangledUnichars.enumerated() {
201201
switch c.value {
202-
case // 0-9
202+
case // 0-9
203203
0x0030...0x0039,
204204
// Annex D.
205205
0x0660...0x0669, 0x06F0...0x06F9, 0x0966...0x096F,

Tests/SwiftRefactorTest/ManifestEditTests.swift

+39
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,45 @@ final class ManifestEditTests: XCTestCase {
282282
}
283283
}
284284

285+
func testAddLibraryProduct() throws {
286+
try assertManifestRefactor(
287+
"""
288+
// swift-tools-version: 5.5
289+
let package = Package(
290+
name: "packages",
291+
targets: [
292+
.target(name: "MyLib"),
293+
],
294+
)
295+
""",
296+
expectedManifest: """
297+
// swift-tools-version: 5.5
298+
let package = Package(
299+
name: "packages",
300+
products: [
301+
.library(
302+
name: "MyLib",
303+
type: .dynamic,
304+
targets: [ "MyLib" ]
305+
),
306+
],
307+
targets: [
308+
.target(name: "MyLib"),
309+
],
310+
)
311+
""",
312+
provider: AddProduct.self,
313+
context: .init(
314+
product:
315+
ProductDescription(
316+
name: "MyLib",
317+
type: .library(.dynamic),
318+
targets: ["MyLib"]
319+
)
320+
)
321+
)
322+
}
323+
285324
func testAddLibraryTarget() throws {
286325
try assertManifestRefactor(
287326
"""

0 commit comments

Comments
 (0)