Skip to content

Commit fb6939c

Browse files
committed
[SwiftRefactor] PackageManifest: Add refactoring to add introduce swift settings
Allows to add `enable{Upcoming, Experimental}Feature`, `swiftLanguageMode`, `strictMemorySafety` as well as custom ones.
1 parent 621ae13 commit fb6939c

File tree

4 files changed

+455
-0
lines changed

4 files changed

+455
-0
lines changed

Sources/SwiftRefactor/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ add_swift_syntax_library(SwiftRefactor
2727
PackageManifest/AddPackageTarget.swift
2828
PackageManifest/AddPluginUsage.swift
2929
PackageManifest/AddProduct.swift
30+
PackageManifest/AddSwiftSetting.swift
3031
PackageManifest/AddTargetDependency.swift
3132
PackageManifest/ManifestEditError.swift
3233
PackageManifest/ManifestEditRefactoringProvider.swift
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 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+
import SwiftParser
14+
import SwiftSyntax
15+
import SwiftSyntaxBuilder
16+
17+
/// Add a swift setting to a manifest's source code.
18+
public struct AddSwiftSetting: ManifestEditRefactoringProvider {
19+
public struct Context {
20+
let target: String
21+
let setting: String
22+
let value: ExprSyntax?
23+
}
24+
25+
/// The set of argument labels that can occur after the "targets"
26+
/// argument in the Package initializers.
27+
private static let argumentLabelsAfterSwiftSettings: Set<String> = [
28+
"linkerSettings",
29+
"plugins",
30+
]
31+
32+
public static func upcomingFeature(
33+
to target: String,
34+
name: String,
35+
manifest: SourceFileSyntax
36+
) throws -> PackageEdit {
37+
try manifestRefactor(
38+
syntax: manifest,
39+
in: .init(
40+
target: target,
41+
setting: "enableUpcomingFeature",
42+
value: name.asSyntax()
43+
)
44+
)
45+
}
46+
47+
public static func experimentalFeature(
48+
to target: String,
49+
name: String,
50+
manifest: SourceFileSyntax
51+
) throws -> PackageEdit {
52+
try manifestRefactor(
53+
syntax: manifest,
54+
in: .init(
55+
target: target,
56+
setting: "enableExperimentalFeature",
57+
value: name.asSyntax()
58+
)
59+
)
60+
}
61+
62+
public static func languageMode(
63+
to target: String,
64+
mode rawMode: String,
65+
manifest: SourceFileSyntax
66+
) throws -> PackageEdit {
67+
let mode: String
68+
switch rawMode {
69+
case "3", "4", "5", "6":
70+
mode = ".v\(rawMode)"
71+
case "4.2":
72+
mode = ".v4_2"
73+
default:
74+
mode = ".version(\"\(rawMode)\")"
75+
}
76+
77+
return try manifestRefactor(
78+
syntax: manifest,
79+
in: .init(
80+
target: target,
81+
setting: "swiftLanguageMode",
82+
value: "\(raw: mode)"
83+
)
84+
)
85+
}
86+
87+
public static func strictMemorySafety(
88+
to target: String,
89+
manifest: SourceFileSyntax
90+
) throws -> PackageEdit {
91+
try manifestRefactor(
92+
syntax: manifest,
93+
in: .init(
94+
target: target,
95+
setting: "strictMemorySafety()",
96+
value: .none
97+
)
98+
)
99+
}
100+
101+
public static func manifestRefactor(
102+
syntax manifest: SourceFileSyntax,
103+
in context: Context
104+
) throws -> PackageEdit {
105+
guard let packageCall = manifest.findCall(calleeName: "Package") else {
106+
throw ManifestEditError.cannotFindPackage
107+
}
108+
109+
guard let targetsArgument = packageCall.findArgument(labeled: "targets"),
110+
let targetArray = targetsArgument.expression.findArrayArgument()
111+
else {
112+
throw ManifestEditError.cannotFindTargets
113+
}
114+
115+
let targetCall = targetArray
116+
.elements
117+
.lazy
118+
.compactMap {
119+
$0.expression.as(FunctionCallExprSyntax.self)
120+
}.first { targetCall in
121+
if let nameArgument = targetCall.findArgument(labeled: "name"),
122+
let nameLiteral = nameArgument.expression.as(StringLiteralExprSyntax.self),
123+
nameLiteral.representedLiteralValue == context.target
124+
{
125+
return true
126+
}
127+
128+
return false
129+
}
130+
131+
guard let targetCall else {
132+
throw ManifestEditError.cannotFindTarget(targetName: context.target)
133+
}
134+
135+
if let memberRef = targetCall.calledExpression.as(MemberAccessExprSyntax.self),
136+
memberRef.declName.baseName.text == "plugin"
137+
{
138+
throw ManifestEditError.cannotAddSettingsToPluginTarget
139+
}
140+
141+
let newTargetCall =
142+
if let value = context.value {
143+
try targetCall.appendingToArrayArgument(
144+
label: "swiftSettings",
145+
labelsAfter: self.argumentLabelsAfterSwiftSettings,
146+
newElement: ".\(raw: context.setting)(\(value))"
147+
)
148+
} else {
149+
try targetCall.appendingToArrayArgument(
150+
label: "swiftSettings",
151+
labelsAfter: self.argumentLabelsAfterSwiftSettings,
152+
newElement: ".\(raw: context.setting)"
153+
)
154+
}
155+
156+
return PackageEdit(
157+
manifestEdits: [
158+
.replace(targetCall, with: newTargetCall.description)
159+
]
160+
)
161+
}
162+
}

Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public enum ManifestEditError: Error, Equatable {
1919
case cannotFindTargets
2020
case cannotFindTarget(targetName: String)
2121
case cannotFindArrayLiteralArgument(argumentName: String)
22+
case cannotAddSettingsToPluginTarget
2223
case existingDependency(dependencyName: String)
2324
case malformedManifest(error: String)
2425
}
@@ -34,6 +35,8 @@ extension ManifestEditError: CustomStringConvertible {
3435
return "unable to find target named '\(name)' in package"
3536
case .cannotFindArrayLiteralArgument(argumentName: let name):
3637
return "unable to find array literal for '\(name)' argument"
38+
case .cannotAddSettingsToPluginTarget:
39+
return "plugin targets do not support settings"
3740
case .existingDependency(let name):
3841
return "unable to add dependency '\(name)' because it already exists in the list of dependencies"
3942
case .malformedManifest(let error):

0 commit comments

Comments
 (0)