Skip to content

Commit 742157a

Browse files
authored
add-target breaks resulting package when target name is not valid Swift identifier (#7764)
Executing `swift package add-target my-target --type executable` breaks the updated package because the executable target name is not a valid Swift identifier. ### Motivation: After executing `swift package add-target my-target --type executable`, I expect the generated stub to not break the modified package. However, in this case, the resulting stub file would contain invalid Swift code: ```swift @main struct my-targetMain { static func main() { print("Hello, world") } } ``` The same issue applies to `macro` and `test` target types. It's also important to note that `dash case` is quite often used for target names, like e.g. in [swift-argument-parser](https://github.com/apple/swift-argument-parser/blob/main/Package.swift#L65-L68). Additionaly, when `AddTarget` is consumed via an API (as opposed to the CLI), the following parsing error is thrown into `stdout`: <img width="796" alt="Screenshot 2024-07-08 at 08 40 51" src="https://github.com/swiftlang/swift-package-manager/assets/1008612/c2696127-3e34-47a2-aa60-a843cfb64bda"> ### Modifications: I believe there are at least two ways to solve this issue: 1. Use a predefined type name in the generated stub, such as `struct ExampleMain`, so it doesn't depend on the target name. 2. Introduce a sanitizer function to ensure that the target name is always a valid Swift identifier. For example, a function that would convert "$my-target-name%" into "myTargetName". For the sake of simplicity, I opted for approach `#1` in the proposed PR. However, if it's necessary to preserve target names in the generated stubs, we can certainly go with approach `#2`. ### Result: `swift package add-target my-target --type executable` ```swift @main struct ExampleMain { static func main() { print("Hello, world") } } ```
1 parent 338d5e9 commit 742157a

File tree

2 files changed

+38
-26
lines changed

2 files changed

+38
-26
lines changed

Sources/PackageModelSyntax/AddTarget.swift

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,10 @@ public struct AddTarget {
217217
case .macro:
218218
"""
219219
\(imports)
220-
struct \(raw: target.name): Macro {
220+
struct \(raw: target.sanitizedName): Macro {
221221
/// TODO: Implement one or more of the protocols that inherit
222222
/// from Macro. The appropriate macro protocol is determined
223-
/// by the "macro" declaration that \(raw: target.name) implements.
223+
/// by the "macro" declaration that \(raw: target.sanitizedName) implements.
224224
/// Examples include:
225225
/// @freestanding(expression) macro --> ExpressionMacro
226226
/// @attached(member) macro --> MemberMacro
@@ -238,8 +238,8 @@ public struct AddTarget {
238238
case .xctest:
239239
"""
240240
\(imports)
241-
class \(raw: target.name): XCTestCase {
242-
func test\(raw: target.name)() {
241+
class \(raw: target.sanitizedName)Tests: XCTestCase {
242+
func test\(raw: target.sanitizedName)() {
243243
XCTAssertEqual(42, 17 + 25)
244244
}
245245
}
@@ -249,8 +249,8 @@ public struct AddTarget {
249249
"""
250250
\(imports)
251251
@Suite
252-
struct \(raw: target.name)Tests {
253-
@Test("\(raw: target.name) tests")
252+
struct \(raw: target.sanitizedName)Tests {
253+
@Test("\(raw: target.sanitizedName) tests")
254254
func example() {
255255
#expect(42 == 17 + 25)
256256
}
@@ -267,7 +267,7 @@ public struct AddTarget {
267267
"""
268268
\(imports)
269269
@main
270-
struct \(raw: target.name)Main {
270+
struct \(raw: target.sanitizedName)Main {
271271
static func main() {
272272
print("Hello, world")
273273
}
@@ -296,9 +296,9 @@ public struct AddTarget {
296296
import SwiftCompilerPlugin
297297
298298
@main
299-
struct \(raw: target.name)Macros: CompilerPlugin {
299+
struct \(raw: target.sanitizedName)Macros: CompilerPlugin {
300300
let providingMacros: [Macro.Type] = [
301-
\(raw: target.name).self,
301+
\(raw: target.sanitizedName).self,
302302
]
303303
}
304304
"""
@@ -364,3 +364,15 @@ fileprivate extension PackageDependency {
364364
)
365365
}
366366
}
367+
368+
fileprivate extension TargetDescription {
369+
var sanitizedName: String {
370+
name
371+
.spm_mangledToC99ExtendedIdentifier()
372+
.localizedFirstWordCapitalized()
373+
}
374+
}
375+
376+
fileprivate extension String {
377+
func localizedFirstWordCapitalized() -> String { prefix(1).localizedCapitalized + dropFirst() }
378+
}

Tests/PackageModelSyntaxTests/ManifestEditTests.swift

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ class ManifestEditTests: XCTestCase {
468468
// These are the targets
469469
.target(name: "MyLib"),
470470
.executableTarget(
471-
name: "MyProgram",
471+
name: "MyProgram target-name",
472472
dependencies: [
473473
.product(name: "SwiftSyntax", package: "swift-syntax"),
474474
.target(name: "TargetLib"),
@@ -479,13 +479,13 @@ class ManifestEditTests: XCTestCase {
479479
)
480480
""",
481481
expectedAuxiliarySources: [
482-
RelativePath("Sources/MyProgram/MyProgram.swift") : """
482+
RelativePath("Sources/MyProgram target-name/MyProgram target-name.swift") : """
483483
import MyLib
484484
import SwiftSyntax
485485
import TargetLib
486486
487487
@main
488-
struct MyProgramMain {
488+
struct MyProgram_target_nameMain {
489489
static func main() {
490490
print("Hello, world")
491491
}
@@ -494,7 +494,7 @@ class ManifestEditTests: XCTestCase {
494494
]) { manifest in
495495
try AddTarget.addTarget(
496496
TargetDescription(
497-
name: "MyProgram",
497+
name: "MyProgram target-name",
498498
dependencies: [
499499
.product(name: "SwiftSyntax", package: "swift-syntax"),
500500
.target(name: "TargetLib", condition: nil),
@@ -528,7 +528,7 @@ class ManifestEditTests: XCTestCase {
528528
],
529529
targets: [
530530
.macro(
531-
name: "MyMacro",
531+
name: "MyMacro target-name",
532532
dependencies: [
533533
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
534534
.product(name: "SwiftSyntaxMacros", package: "swift-syntax")
@@ -538,33 +538,33 @@ class ManifestEditTests: XCTestCase {
538538
)
539539
""",
540540
expectedAuxiliarySources: [
541-
RelativePath("Sources/MyMacro/MyMacro.swift") : """
541+
RelativePath("Sources/MyMacro target-name/MyMacro target-name.swift") : """
542542
import SwiftCompilerPlugin
543543
import SwiftSyntaxMacros
544544
545-
struct MyMacro: Macro {
545+
struct MyMacro_target_name: Macro {
546546
/// TODO: Implement one or more of the protocols that inherit
547547
/// from Macro. The appropriate macro protocol is determined
548-
/// by the "macro" declaration that MyMacro implements.
548+
/// by the "macro" declaration that MyMacro_target_name implements.
549549
/// Examples include:
550550
/// @freestanding(expression) macro --> ExpressionMacro
551551
/// @attached(member) macro --> MemberMacro
552552
}
553553
""",
554-
RelativePath("Sources/MyMacro/ProvidedMacros.swift") : """
554+
RelativePath("Sources/MyMacro target-name/ProvidedMacros.swift") : """
555555
import SwiftCompilerPlugin
556556
557557
@main
558-
struct MyMacroMacros: CompilerPlugin {
558+
struct MyMacro_target_nameMacros: CompilerPlugin {
559559
let providingMacros: [Macro.Type] = [
560-
MyMacro.self,
560+
MyMacro_target_name.self,
561561
]
562562
}
563563
"""
564564
]
565565
) { manifest in
566566
try AddTarget.addTarget(
567-
TargetDescription(name: "MyMacro", type: .macro),
567+
TargetDescription(name: "MyMacro target-name", type: .macro),
568568
to: manifest
569569
)
570570
}
@@ -582,17 +582,17 @@ class ManifestEditTests: XCTestCase {
582582
let package = Package(
583583
name: "packages",
584584
targets: [
585-
.testTarget(name: "MyTest"),
585+
.testTarget(name: "MyTest target-name"),
586586
]
587587
)
588588
""",
589589
expectedAuxiliarySources: [
590-
RelativePath("Tests/MyTest/MyTest.swift") : """
590+
RelativePath("Tests/MyTest target-name/MyTest target-name.swift") : """
591591
import Testing
592592
593593
@Suite
594-
struct MyTestTests {
595-
@Test("MyTest tests")
594+
struct MyTest_target_nameTests {
595+
@Test("MyTest_target_name tests")
596596
func example() {
597597
#expect(42 == 17 + 25)
598598
}
@@ -601,7 +601,7 @@ class ManifestEditTests: XCTestCase {
601601
]) { manifest in
602602
try AddTarget.addTarget(
603603
TargetDescription(
604-
name: "MyTest",
604+
name: "MyTest target-name",
605605
type: .test
606606
),
607607
to: manifest,

0 commit comments

Comments
 (0)