From 8e71dfdeb66d495020b772440a3ba3ee9bbf78c5 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 30 Nov 2024 08:12:57 -0800 Subject: [PATCH 01/30] Introduce package manifest refactoring action "Add Package Dependency" Start porting the package manifest editing operations from the Swift Package Manager package over here, so that all of the syntactic refactorings are together in one common place. These refactorings are needed by a number of tools, including SwiftPM, SourceKit-LSP, and (soon) the Swift compiler itself, which can all depend on swift-syntax. The implementation here stubs out the various types used to describe package syntax, using simple string-backed types in place of some of the semantic types that are part of SwiftPM itself, such as SemanticVersion or SourceControlURL. I've also introduced the notion of a ManifestEditRefactoringProvider to generalize over all of the package manifest editing operations. This commit ports over the "Add Package Dependency" command and its tests. --- Package.swift | 2 +- Sources/SwiftRefactor/CMakeLists.txt | 13 + .../PackageManifest/AbsolutePath.swift | 21 + .../AddPackageDependency.swift | 73 +++ .../PackageManifest/ManifestEditError.swift | 37 ++ .../ManifestEditRefactoringProvider.swift | 25 + .../ManifestSyntaxRepresentable.swift | 45 ++ .../PackageManifest/PackageDependency.swift | 158 ++++++ .../PackageManifest/PackageEditResult.swift | 23 + .../PackageManifest/PackageIdentity.swift | 21 + .../PackageManifest/RelativePath.swift | 21 + .../PackageManifest/SemanticVersion.swift | 29 + .../PackageManifest/SourceControlURL.swift | 21 + .../PackageManifest/SyntaxEditUtils.swift | 517 ++++++++++++++++++ .../SwiftRefactorTest/ManifestEditTests.swift | 370 +++++++++++++ 15 files changed, 1375 insertions(+), 1 deletion(-) create mode 100644 Sources/SwiftRefactor/PackageManifest/AbsolutePath.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/ManifestSyntaxRepresentable.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/PackageDependency.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/PackageEditResult.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/PackageIdentity.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/RelativePath.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/SemanticVersion.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/SourceControlURL.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift create mode 100644 Tests/SwiftRefactorTest/ManifestEditTests.swift diff --git a/Package.swift b/Package.swift index 844bfacc40a..9801d35218e 100644 --- a/Package.swift +++ b/Package.swift @@ -393,7 +393,7 @@ let package = Package( .testTarget( name: "SwiftRefactorTest", - dependencies: ["_SwiftSyntaxTestSupport", "SwiftRefactor"] + dependencies: ["_SwiftSyntaxTestSupport", "SwiftIDEUtils", "SwiftRefactor"] ), // MARK: - Deprecated targets diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index 8494f3b91cc..e1cc6a7d8b8 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -22,6 +22,19 @@ add_swift_syntax_library(SwiftRefactor RefactoringProvider.swift RemoveSeparatorsFromIntegerLiteral.swift SyntaxUtils.swift + + PackageManifest/AbsolutePath.swift + PackageManifest/AddPackageDependency.swift + PackageManifest/ManifestEditError.swift + PackageManifest/ManifestEditRefactoringProvider.swift + PackageManifest/ManifestSyntaxRepresentable.swift + PackageManifest/PackageDependency.swift + PackageManifest/PackageEditResult.swift + PackageManifest/PackageIdentity.swift + PackageManifest/RelativePath.swift + PackageManifest/SemanticVersion.swift + PackageManifest/SourceControlURL.swift + PackageManifest/SyntaxEditUtils.swift ) target_link_swift_syntax_libraries(SwiftRefactor PUBLIC diff --git a/Sources/SwiftRefactor/PackageManifest/AbsolutePath.swift b/Sources/SwiftRefactor/PackageManifest/AbsolutePath.swift new file mode 100644 index 00000000000..d6ed26d345e --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/AbsolutePath.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Syntactic wrapper type that describes an absolute path for refactoring +/// purposes but does not interpret its contents. +public struct AbsolutePath: CustomStringConvertible, Equatable, Hashable, Sendable { + public private(set) var description: String + + public init(_ description: String) { + self.description = description + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift new file mode 100644 index 00000000000..2d5cc6c4bba --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift @@ -0,0 +1,73 @@ +//===----------------------------------------------------------------------===// +// +// 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 package dependency to a package manifest's source code. +public struct AddPackageDependency: ManifestEditRefactoringProvider { + public struct Context { + public var dependency: PackageDependency + + public init(dependency: PackageDependency) { + self.dependency = dependency + } + } + + /// The set of argument labels that can occur after the "dependencies" + /// argument in the Package initializers. + /// + /// TODO: Could we generate this from the the PackageDescription module, so + /// we don't have keep it up-to-date manually? + private static let argumentLabelsAfterDependencies: Set = [ + "targets", + "swiftLanguageVersions", + "cLanguageStandard", + "cxxLanguageStandard", + ] + + /// 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 dependency = context.dependency + guard let packageCall = manifest.findCall(calleeName: "Package") else { + throw ManifestEditError.cannotFindPackage + } + + let newPackageCall = try addPackageDependencyLocal( + dependency, + to: packageCall + ) + + return PackageEditResult( + manifestEdits: [ + .replace(packageCall, with: newPackageCall.description) + ] + ) + } + + /// Implementation of adding a package dependency to an existing call. + static func addPackageDependencyLocal( + _ dependency: PackageDependency, + to packageCall: FunctionCallExprSyntax + ) throws -> FunctionCallExprSyntax { + try packageCall.appendingToArrayArgument( + label: "dependencies", + trailingLabels: Self.argumentLabelsAfterDependencies, + newElement: dependency.asSyntax() + ) + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift new file mode 100644 index 00000000000..3c600ddd9c0 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftSyntax + +/// An error describing problems that can occur when attempting to edit a +/// package manifest programattically. +public enum ManifestEditError: Error { + case cannotFindPackage + case cannotFindTargets + case cannotFindTarget(targetName: String) + case cannotFindArrayLiteralArgument(argumentName: String, node: Syntax) +} + +extension ManifestEditError: CustomStringConvertible { + public var description: String { + switch self { + case .cannotFindPackage: + "invalid manifest: unable to find 'Package' declaration" + case .cannotFindTargets: + "unable to find package targets in manifest" + case .cannotFindTarget(targetName: let name): + "unable to find target named '\(name)' in package" + case .cannotFindArrayLiteralArgument(argumentName: let name, node: _): + "unable to find array literal for '\(name)' argument" + } + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift new file mode 100644 index 00000000000..e6532ec7979 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftSyntax + +public protocol ManifestEditRefactoringProvider: EditRefactoringProvider +where Self.Input == SourceFileSyntax { + + static func manifestRefactor(syntax: SourceFileSyntax, in context: Context) throws -> PackageEditResult +} + +extension EditRefactoringProvider where Self: ManifestEditRefactoringProvider { + public static func textRefactor(syntax: Input, in context: Context) -> [SourceEdit] { + return (try? manifestRefactor(syntax: syntax, in: context).manifestEdits) ?? [] + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestSyntaxRepresentable.swift b/Sources/SwiftRefactor/PackageManifest/ManifestSyntaxRepresentable.swift new file mode 100644 index 00000000000..e9899333fdb --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/ManifestSyntaxRepresentable.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift 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 http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// Describes an entity in the package model that can be represented as +/// a syntax node. +protocol ManifestSyntaxRepresentable { + /// The most specific kind of syntax node that best describes this entity + /// in the manifest. + /// + /// There might be other kinds of syntax nodes that can also represent + /// the syntax, but this is the one that a canonical manifest will use. + /// As an example, a package dependency is usually expressed as, e.g., + /// .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1") + /// + /// However, there could be other forms, e.g., this is also valid: + /// Package.Dependency.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1") + associatedtype PreferredSyntax: SyntaxProtocol + + /// Provides a suitable syntax node to describe this entity in the package + /// model. + /// + /// The resulting syntax is a fragment that describes just this entity, + /// and it's enclosing entity will need to understand how to fit it in. + /// For example, a `PackageDependency` entity would map to syntax for + /// something like + /// .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1") + func asSyntax() -> PreferredSyntax +} + +extension String: ManifestSyntaxRepresentable { + typealias PreferredSyntax = ExprSyntax + + func asSyntax() -> ExprSyntax { "\(literal: self)" } +} diff --git a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift new file mode 100644 index 00000000000..6b9c9ac6337 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift @@ -0,0 +1,158 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift 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 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 + +/// Describes a package dependency for refactoring purposes. This is a syntactic +/// subset of the full package manifest's description of a package dependency. +public enum PackageDependency: Sendable { + case fileSystem(FileSystem) + case sourceControl(SourceControl) + case registry(Registry) + + public struct FileSystem: Sendable { + public let identity: PackageIdentity + public let nameForTargetDependencyResolutionOnly: String? + public let path: AbsolutePath + } + + public struct SourceControl: Sendable { + public let identity: PackageIdentity + public let location: Location + public let requirement: Requirement + + public init(identity: PackageIdentity, location: Location, requirement: Requirement) { + self.identity = identity + self.location = location + self.requirement = requirement + } + + public enum Requirement: Sendable { + case exact(SemanticVersion) + case rangeFrom(SemanticVersion) + case range(lowerBound: SemanticVersion, upperBound: SemanticVersion) + case revision(String) + case branch(String) + } + + public enum Location: Sendable { + case local(AbsolutePath) + case remote(SourceControlURL) + } + } + + public struct Registry: Sendable { + public let identity: PackageIdentity + public let requirement: Requirement + + /// The dependency requirement. + public enum Requirement: Sendable { + case exact(SemanticVersion) + case rangeFrom(SemanticVersion) + case range(lowerBound: SemanticVersion, upperBound: SemanticVersion) + } + } +} + +extension PackageDependency: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + switch self { + case .fileSystem(let filesystem): filesystem.asSyntax() + case .sourceControl(let sourceControl): sourceControl.asSyntax() + case .registry(let registry): registry.asSyntax() + } + } +} + +extension PackageDependency.FileSystem: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + ".package(path: \(literal: path.description))" + } +} + +extension PackageDependency.SourceControl: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + // TODO: Not handling identity, nameForTargetDependencyResolutionOnly, + // or productFilter yet. + switch location { + case .local: + fatalError() + case .remote(let url): + ".package(url: \(literal: url.description), \(requirement.asSyntax()))" + } + } +} + +extension PackageDependency.Registry: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + ".package(id: \(literal: identity.description), \(requirement.asSyntax()))" + } +} + +extension PackageDependency.SourceControl.Requirement: ManifestSyntaxRepresentable { + func asSyntax() -> LabeledExprSyntax { + switch self { + case .exact(let version): + LabeledExprSyntax( + label: "exact", + expression: version.asSyntax() + ) + + case .rangeFrom(let range): + LabeledExprSyntax( + label: "from", + expression: range.asSyntax() + ) + + case .range(let lowerBound, let upperBound): + LabeledExprSyntax( + expression: "\(lowerBound.asSyntax())..<\(upperBound.asSyntax())" as ExprSyntax + ) + + case .revision(let revision): + LabeledExprSyntax( + label: "revision", + expression: "\(literal: revision)" as ExprSyntax + ) + + case .branch(let branch): + LabeledExprSyntax( + label: "branch", + expression: "\(literal: branch)" as ExprSyntax + ) + } + } +} + +extension PackageDependency.Registry.Requirement: ManifestSyntaxRepresentable { + func asSyntax() -> LabeledExprSyntax { + switch self { + case .exact(let version): + LabeledExprSyntax( + label: "exact", + expression: version.asSyntax() + ) + + case .rangeFrom(let range): + LabeledExprSyntax( + label: "from", + expression: range.asSyntax() + ) + + case .range(let lowerBound, let upperBound): + LabeledExprSyntax( + expression: "\(lowerBound.asSyntax())..<\(upperBound.asSyntax())" as ExprSyntax + ) + } + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/PackageEditResult.swift b/Sources/SwiftRefactor/PackageManifest/PackageEditResult.swift new file mode 100644 index 00000000000..b3dd59b83da --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/PackageEditResult.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftSyntax + +/// The result of editing a package, including any edits to the package +/// manifest and any new files that are introduced. +public struct PackageEditResult { + /// Edits to perform to the package manifest. + public var manifestEdits: [SourceEdit] = [] + + /// Auxiliary files to write. + public var auxiliaryFiles: [(RelativePath, SourceFileSyntax)] = [] +} diff --git a/Sources/SwiftRefactor/PackageManifest/PackageIdentity.swift b/Sources/SwiftRefactor/PackageManifest/PackageIdentity.swift new file mode 100644 index 00000000000..d51561b33f9 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/PackageIdentity.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift 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 http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// Describes a package identity for refactoring purposes. This is a syntactic +/// subset of the full package manifest's notion of package identity. +public struct PackageIdentity: CustomStringConvertible, Equatable, Hashable, Sendable { + public private(set) var description: String + + public init(_ description: String) { + self.description = description + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/RelativePath.swift b/Sources/SwiftRefactor/PackageManifest/RelativePath.swift new file mode 100644 index 00000000000..c7e07451636 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/RelativePath.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift 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 http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// Syntactic wrapper type that describes a relative path for refactoring +/// purposes but does not interpret its contents. +public struct RelativePath: CustomStringConvertible, Equatable, Hashable, Sendable { + public private(set) var description: String + + public init(_ description: String) { + self.description = description + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/SemanticVersion.swift b/Sources/SwiftRefactor/PackageManifest/SemanticVersion.swift new file mode 100644 index 00000000000..bcd819207c4 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/SemanticVersion.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift 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 http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// Syntactic wrapper type that describes a semantic version for refactoring +/// purposes but does not interpret its contents. +public struct SemanticVersion: CustomStringConvertible, Equatable, Hashable, Sendable { + public private(set) var description: String + + public init(_ description: String) { + self.description = description + } +} + +extension SemanticVersion: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + "\(literal: description)" + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/SourceControlURL.swift b/Sources/SwiftRefactor/PackageManifest/SourceControlURL.swift new file mode 100644 index 00000000000..70b31ae9109 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/SourceControlURL.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift 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 http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// Syntactic wrapper type that describes a source control URL for refactoring +/// purposes but does not interpret its contents. +public struct SourceControlURL: CustomStringConvertible, Equatable, Hashable, Sendable { + public private(set) var description: String + + public init(_ description: String) { + self.description = description + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift new file mode 100644 index 00000000000..2a00e894629 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift @@ -0,0 +1,517 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift 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 http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftBasicFormat +import SwiftParser +import SwiftSyntax + +/// Default indent when we have to introduce indentation but have no context +/// to get it right. +let defaultIndent = TriviaPiece.spaces(4) + +extension Trivia { + /// Determine whether this trivia has newlines or not. + var hasNewlines: Bool { + contains(where: \.isNewline) + } + + /// Produce trivia from the last newline to the end, dropping anything + /// prior to that. + func onlyLastLine() -> Trivia { + guard let lastNewline = pieces.lastIndex(where: { $0.isNewline }) else { + return self + } + + return Trivia(pieces: pieces[lastNewline...]) + } +} + +/// Syntax walker to find the first occurrence of a given node kind that +/// matches a specific predicate. +private class FirstNodeFinder: SyntaxAnyVisitor { + var predicate: (Node) -> Bool + var found: Node? = nil + + init(predicate: @escaping (Node) -> Bool) { + self.predicate = predicate + super.init(viewMode: .sourceAccurate) + } + + override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind { + if found != nil { + return .skipChildren + } + + if let matchedNode = node.as(Node.self), predicate(matchedNode) { + found = matchedNode + return .skipChildren + } + + return .visitChildren + } +} + +extension SyntaxProtocol { + /// Find the first node of the Self type that matches the given predicate. + static func findFirst( + in node: some SyntaxProtocol, + matching predicate: (Self) -> Bool + ) -> Self? { + withoutActuallyEscaping(predicate) { escapingPredicate in + let visitor = FirstNodeFinder(predicate: escapingPredicate) + visitor.walk(node) + return visitor.found + } + } +} + +extension FunctionCallExprSyntax { + /// Check whether this call expression has a callee that is a reference + /// to a declaration with the given name. + func hasCallee(named name: String) -> Bool { + guard let calleeDeclRef = calledExpression.as(DeclReferenceExprSyntax.self) else { + return false + } + + return calleeDeclRef.baseName.text == name + } + + /// Find a call argument based on its label. + func findArgument(labeled label: String) -> LabeledExprSyntax? { + arguments.first { $0.label?.text == label } + } + + /// Find a call argument index based on its label. + func findArgumentIndex(labeled label: String) -> LabeledExprListSyntax.Index? { + arguments.firstIndex { $0.label?.text == label } + } +} + +extension LabeledExprListSyntax { + /// Find the index at which the one would insert a new argument given + /// the set of argument labels that could come after the argument we + /// want to insert. + func findArgumentInsertionPosition( + labelsAfter: Set + ) -> SyntaxChildrenIndex { + firstIndex { + guard let label = $0.label else { + return false + } + + return labelsAfter.contains(label.text) + } ?? endIndex + } + + /// Form a new argument list that inserts a new argument at the specified + /// position in this argument list. + /// + /// This operation will attempt to introduce trivia to match the + /// surrounding context where possible. The actual argument will be + /// created by the `generator` function, which is provided with leading + /// trivia and trailing comma it should use to match the surrounding + /// context. + func insertingArgument( + at position: SyntaxChildrenIndex, + generator: (Trivia, TokenSyntax?) -> LabeledExprSyntax + ) -> LabeledExprListSyntax { + // Turn the arguments into an array so we can manipulate them. + var arguments = Array(self) + + let positionIdx = distance(from: startIndex, to: position) + + let commaToken = TokenSyntax.commaToken() + + // Figure out leading trivia and adjust the prior argument (if there is + // one) by adding a comma, if necessary. + let leadingTrivia: Trivia + if position > startIndex { + let priorArgument = arguments[positionIdx - 1] + + // Our leading trivia will be based on the prior argument's leading + // trivia. + leadingTrivia = priorArgument.leadingTrivia + + // If the prior argument is missing a trailing comma, add one. + if priorArgument.trailingComma == nil { + arguments[positionIdx - 1].trailingComma = commaToken + } + } else if positionIdx + 1 < count { + leadingTrivia = arguments[positionIdx + 1].leadingTrivia + } else { + leadingTrivia = Trivia() + } + + // Determine whether we need a trailing comma on this argument. + let trailingComma: TokenSyntax? + if position < endIndex { + trailingComma = commaToken + } else { + trailingComma = nil + } + + // Create the argument and insert it into the argument list. + let argument = generator(leadingTrivia, trailingComma) + arguments.insert(argument, at: positionIdx) + + return LabeledExprListSyntax(arguments) + } +} + +extension SyntaxProtocol { + /// Look for a call expression to a callee with the given name. + func findCall(calleeName: String) -> FunctionCallExprSyntax? { + return FunctionCallExprSyntax.findFirst(in: self) { call in + return call.hasCallee(named: calleeName) + } + } +} + +extension ArrayExprSyntax { + /// Produce a new array literal expression that appends the given + /// element, while trying to maintain similar indentation. + func appending( + element: ExprSyntax, + outerLeadingTrivia: Trivia + ) -> ArrayExprSyntax { + var elements = self.elements + + let commaToken = TokenSyntax.commaToken() + + // If there are already elements, tack it on. + let leadingTrivia: Trivia + let trailingTrivia: Trivia + let leftSquareTrailingTrivia: Trivia + if let last = elements.last { + // The leading trivia of the new element should match that of the + // last element. + leadingTrivia = last.leadingTrivia.onlyLastLine() + + // Add a trailing comma to the last element if it isn't already + // there. + if last.trailingComma == nil { + var newElements = Array(elements) + newElements[newElements.count - 1].trailingComma = commaToken + newElements[newElements.count - 1].expression.trailingTrivia = + Trivia() + newElements[newElements.count - 1].trailingTrivia = last.trailingTrivia + elements = ArrayElementListSyntax(newElements) + } + + trailingTrivia = Trivia() + leftSquareTrailingTrivia = leftSquare.trailingTrivia + } else { + leadingTrivia = outerLeadingTrivia.appending(defaultIndent) + trailingTrivia = outerLeadingTrivia + if leftSquare.trailingTrivia.hasNewlines { + leftSquareTrailingTrivia = leftSquare.trailingTrivia + } else { + leftSquareTrailingTrivia = Trivia() + } + } + + elements.append( + ArrayElementSyntax( + expression: element.with(\.leadingTrivia, leadingTrivia), + trailingComma: commaToken.with(\.trailingTrivia, trailingTrivia) + ) + ) + + let newLeftSquare = leftSquare.with( + \.trailingTrivia, + leftSquareTrailingTrivia + ) + + return with(\.elements, elements).with(\.leftSquare, newLeftSquare) + } +} + +extension ExprSyntax { + /// Find an array argument either at the top level or within a sequence + /// expression. + func findArrayArgument() -> ArrayExprSyntax? { + if let arrayExpr = self.as(ArrayExprSyntax.self) { + return arrayExpr + } + + if let sequenceExpr = self.as(SequenceExprSyntax.self) { + return sequenceExpr.elements.lazy.compactMap { + $0.findArrayArgument() + }.first + } + + return nil + } +} + +// MARK: Utilities to oeprate on arrays of array literal elements. +extension Array { + /// Append a new argument expression. + mutating func append(expression: ExprSyntax) { + // Add a comma on the prior expression, if there is one. + let leadingTrivia: Trivia? + if count > 0 { + self[count - 1].trailingComma = TokenSyntax.commaToken() + leadingTrivia = .newline + + // Adjust the first element to start with a newline + if count == 1 { + self[0].leadingTrivia = .newline + } + } else { + leadingTrivia = nil + } + + append( + ArrayElementSyntax( + leadingTrivia: leadingTrivia, + expression: expression + ) + ) + } +} + +// MARK: Utilities to operate on arrays of call arguments. + +extension Array { + /// Append a potentially labeled argument with the argument expression. + mutating func append(label: String?, expression: ExprSyntax) { + // Add a comma on the prior expression, if there is one. + let leadingTrivia: Trivia + if count > 0 { + self[count - 1].trailingComma = TokenSyntax.commaToken() + leadingTrivia = .newline + + // Adjust the first element to start with a newline + if count == 1 { + self[0].leadingTrivia = .newline + } + } else { + leadingTrivia = Trivia() + } + + // Add the new expression. + append( + LabeledExprSyntax( + label: label, + expression: expression + ).with(\.leadingTrivia, leadingTrivia) + ) + } + + /// Append a potentially labeled argument with a string literal. + mutating func append(label: String?, stringLiteral: String) { + append(label: label, expression: "\(literal: stringLiteral)") + } + + /// Append a potentially labeled argument with a string literal, but only + /// when the string literal is not nil. + mutating func appendIf(label: String?, stringLiteral: String?) { + if let stringLiteral { + append(label: label, stringLiteral: stringLiteral) + } + } + + /// Append an array literal containing elements that can be rendered + /// into expression syntax nodes. + mutating func append( + label: String?, + arrayLiteral: [T] + ) where T: ManifestSyntaxRepresentable, T.PreferredSyntax == ExprSyntax { + var elements: [ArrayElementSyntax] = [] + for element in arrayLiteral { + elements.append(expression: element.asSyntax()) + } + + // Figure out the trivia for the left and right square + let leftSquareTrailingTrivia: Trivia + let rightSquareLeadingTrivia: Trivia + switch elements.count { + case 0: + // Put a single space between the square brackets. + leftSquareTrailingTrivia = Trivia() + rightSquareLeadingTrivia = .space + + case 1: + // Put spaces around the single element + leftSquareTrailingTrivia = .space + rightSquareLeadingTrivia = .space + + default: + // Each of the elements will have a leading newline. Add a leading + // newline before the close bracket. + leftSquareTrailingTrivia = Trivia() + rightSquareLeadingTrivia = .newline + } + + let array = ArrayExprSyntax( + leftSquare: .leftSquareToken( + trailingTrivia: leftSquareTrailingTrivia + ), + elements: ArrayElementListSyntax(elements), + rightSquare: .rightSquareToken( + leadingTrivia: rightSquareLeadingTrivia + ) + ) + append(label: label, expression: ExprSyntax(array)) + } + + /// Append an array literal containing elements that can be rendered + /// into expression syntax nodes. + mutating func appendIf( + label: String?, + arrayLiteral: [T]? + ) where T: ManifestSyntaxRepresentable, T.PreferredSyntax == ExprSyntax { + guard let arrayLiteral else { return } + append(label: label, arrayLiteral: arrayLiteral) + } + + /// Append an array literal containing elements that can be rendered + /// into expression syntax nodes, but only if it's not empty. + mutating func appendIfNonEmpty( + label: String?, + arrayLiteral: [T] + ) where T: ManifestSyntaxRepresentable, T.PreferredSyntax == ExprSyntax { + if arrayLiteral.isEmpty { return } + + append(label: label, arrayLiteral: arrayLiteral) + } +} + +// MARK: Utilities for adding arguments into calls. +fileprivate class ReplacingRewriter: SyntaxRewriter { + let childNode: Syntax + let newChildNode: Syntax + + init(childNode: Syntax, newChildNode: Syntax) { + self.childNode = childNode + self.newChildNode = newChildNode + super.init() + } + + override func visitAny(_ node: Syntax) -> Syntax? { + if node == childNode { + return newChildNode + } + + return nil + } +} + +fileprivate extension SyntaxProtocol { + /// Replace the given child with a new child node. + func replacingChild(_ childNode: Syntax, with newChildNode: Syntax) -> Self { + return ReplacingRewriter( + childNode: childNode, + newChildNode: newChildNode + ).rewrite(self).cast(Self.self) + } +} + +extension FunctionCallExprSyntax { + /// Produce source edits that will add the given new element to the + /// array for an argument with the given label (if there is one), or + /// introduce a new argument with an array literal containing only the + /// new element. + /// + /// - Parameters: + /// - label: The argument label for the argument whose array will be + /// added or modified. + /// - trailingLabels: The argument labels that could follow the label, + /// which helps determine where the argument should be inserted if + /// it doesn't exist yet. + /// - newElement: The new element. + /// - Returns: the function call after making this change. + func appendingToArrayArgument( + label: String, + trailingLabels: Set, + newElement: ExprSyntax + ) throws -> FunctionCallExprSyntax { + // If there is already an argument with this name, append to the array + // literal in there. + if let arg = findArgument(labeled: label) { + guard let argArray = arg.expression.findArrayArgument() else { + throw ManifestEditError.cannotFindArrayLiteralArgument( + argumentName: label, + node: Syntax(arg.expression) + ) + } + + // Format the element appropriately for the context. + let indentation = Trivia( + pieces: arg.leadingTrivia.filter { $0.isSpaceOrTab } + ) + let format = BasicFormat( + indentationWidth: [defaultIndent], + initialIndentation: indentation.appending(defaultIndent) + ) + let formattedElement = newElement.formatted(using: format) + .cast(ExprSyntax.self) + + let updatedArgArray = argArray.appending( + element: formattedElement, + outerLeadingTrivia: arg.leadingTrivia + ) + + return replacingChild(Syntax(argArray), with: Syntax(updatedArgArray)) + } + + // There was no argument, so we need to create one. + + // Insert the new argument at the appropriate place in the call. + let insertionPos = arguments.findArgumentInsertionPosition( + labelsAfter: trailingLabels + ) + let newArguments = arguments.insertingArgument( + at: insertionPos + ) { (leadingTrivia, trailingComma) in + // Format the element appropriately for the context. + let indentation = Trivia(pieces: leadingTrivia.filter { $0.isSpaceOrTab }) + let format = BasicFormat( + indentationWidth: [defaultIndent], + initialIndentation: indentation.appending(defaultIndent) + ) + let formattedElement = newElement.formatted(using: format) + .cast(ExprSyntax.self) + + // Form the array. + let newArgument = ArrayExprSyntax( + leadingTrivia: .space, + leftSquare: .leftSquareToken( + trailingTrivia: .newline + ), + elements: ArrayElementListSyntax( + [ + ArrayElementSyntax( + expression: formattedElement, + trailingComma: .commaToken() + ) + ] + ), + rightSquare: .rightSquareToken( + leadingTrivia: leadingTrivia + ) + ) + + // Create the labeled argument for the array. + return LabeledExprSyntax( + leadingTrivia: leadingTrivia, + label: "\(raw: label)", + colon: .colonToken(), + expression: ExprSyntax(newArgument), + trailingComma: trailingComma + ) + } + + return with(\.arguments, newArguments) + } +} diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift new file mode 100644 index 00000000000..1375ffbbd3d --- /dev/null +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -0,0 +1,370 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +@_spi(FixItApplier) import SwiftIDEUtils +import SwiftParser +import SwiftRefactor +import SwiftSyntax +import SwiftSyntaxBuilder +import XCTest +import _SwiftSyntaxTestSupport + +final class ManifestEditTests: XCTestCase { + static let swiftSystemURL: SourceControlURL = SourceControlURL( + "https://github.com/apple/swift-system.git" + ) + + static let swiftSystemPackageDependency: PackageDependency = .sourceControl( + .init( + identity: PackageIdentity("swift-system"), + location: .remote(swiftSystemURL), + requirement: .branch("main") + ) + ) + + func testAddPackageDependencyExistingComma() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1"), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1"), + .package(url: "https://github.com/apple/swift-system.git", branch: "main"), + ] + ) + """, + provider: AddPackageDependency.self, + context: .init( + dependency: .sourceControl( + .init( + identity: PackageIdentity("swift-system"), + location: .remote(Self.swiftSystemURL), + requirement: .branch("main") + ) + ) + ) + ) + } + + func testAddPackageDependencyExistingNoComma() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1") + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1"), + .package(url: "https://github.com/apple/swift-system.git", exact: "510.0.0"), + ] + ) + """, + provider: AddPackageDependency.self, + context: .init( + dependency: .sourceControl( + .init( + identity: PackageIdentity("swift-system"), + location: .remote(Self.swiftSystemURL), + requirement: .exact(SemanticVersion("510.0.0")) + ) + ) + ) + ) + } + + func testAddPackageDependencyExistingAppended() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1") + ] + [] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1"), + .package(url: "https://github.com/apple/swift-system.git", from: "510.0.0"), + ] + [] + ) + """, + provider: AddPackageDependency.self, + context: .init( + dependency: .sourceControl( + .init( + identity: PackageIdentity("swift-system"), + location: .remote(Self.swiftSystemURL), + requirement: .rangeFrom(SemanticVersion("510.0.0")) + ) + ) + ) + ) + } + + func testAddPackageDependencyExistingOneLine() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1") ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1"), .package(url: "https://github.com/apple/swift-system.git", from: "510.0.0"),] + ) + """, + provider: AddPackageDependency.self, + context: .init( + dependency: .sourceControl( + .init( + identity: PackageIdentity("swift-system"), + location: .remote(Self.swiftSystemURL), + requirement: .rangeFrom(SemanticVersion("510.0.0")) + ) + ) + ) + ) + } + + func testAddPackageDependencyExistingEmpty() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-system.git", "508.0.0" ..< "510.0.0"), + ] + ) + """, + provider: AddPackageDependency.self, + context: .init( + dependency: .sourceControl( + .init( + identity: PackageIdentity("swift-system"), + location: .remote(Self.swiftSystemURL), + requirement: .range(lowerBound: SemanticVersion("508.0.0"), upperBound: SemanticVersion("510.0.0")) + ) + ) + ) + ) + } + + func testAddPackageDependencyNoExistingAtEnd() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages" + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-system.git", branch: "main"), + ] + ) + """, + provider: AddPackageDependency.self, + context: .init( + dependency: Self.swiftSystemPackageDependency + ) + ) + } + + func testAddPackageDependencyNoExistingMiddle() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + targets: [] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-system.git", branch: "main"), + ], + targets: [] + ) + """, + provider: AddPackageDependency.self, + context: .init( + dependency: Self.swiftSystemPackageDependency + ) + ) + } + + func testAddPackageDependencyErrors() { + XCTAssertThrows( + try AddPackageDependency.manifestRefactor( + syntax: """ + // swift-tools-version: 5.5 + let package: Package = .init( + name: "packages" + ) + """, + in: .init(dependency: Self.swiftSystemPackageDependency) + ) + ) { (error: ManifestEditError) in + if case .cannotFindPackage = error { + return true + } else { + return false + } + } + + XCTAssertThrows( + try AddPackageDependency.manifestRefactor( + syntax: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: blah + ) + """, + in: .init(dependency: Self.swiftSystemPackageDependency) + ) + ) { (error: ManifestEditError) in + if case .cannotFindArrayLiteralArgument(argumentName: "dependencies", node: _) = error { + return true + } else { + return false + } + } + } +} + +/// Assert that applying the given edit/refactor operation to the manifest +/// produces the expected manifest source file and the expected auxiliary +/// files. +func assertManifestRefactor( + _ originalManifest: SourceFileSyntax, + expectedManifest: SourceFileSyntax, + expectedAuxiliarySources: [RelativePath: SourceFileSyntax] = [:], + provider: Provider.Type, + context: Provider.Context, + file: StaticString = #filePath, + line: UInt = #line +) throws { + return try assertManifestRefactor( + originalManifest, + expectedManifest: expectedManifest, + expectedAuxiliarySources: expectedAuxiliarySources, + file: file, + line: line + ) { (manifest) in + try provider.manifestRefactor(syntax: manifest, in: context) + } +} + +/// Assert that applying the given edit/refactor operation to the manifest +/// produces the expected manifest source file and the expected auxiliary +/// files. +func assertManifestRefactor( + _ originalManifest: SourceFileSyntax, + expectedManifest: SourceFileSyntax, + expectedAuxiliarySources: [RelativePath: SourceFileSyntax] = [:], + file: StaticString = #filePath, + line: UInt = #line, + operation: (SourceFileSyntax) throws -> PackageEditResult +) rethrows { + let edits = try operation(originalManifest) + let editedManifestSource = FixItApplier.apply( + edits: edits.manifestEdits, + to: originalManifest + ) + + let editedManifest = Parser.parse(source: editedManifestSource) + assertStringsEqualWithDiff( + editedManifest.description, + expectedManifest.description, + file: file, + line: line + ) + + // Check all of the auxiliary sources. + for (auxSourcePath, auxSourceSyntax) in edits.auxiliaryFiles { + guard let expectedSyntax = expectedAuxiliarySources[auxSourcePath] else { + XCTFail("unexpected auxiliary source file \(auxSourcePath)") + return + } + + assertStringsEqualWithDiff( + auxSourceSyntax.description, + expectedSyntax.description, + file: file, + line: line + ) + } + + XCTAssertEqual( + edits.auxiliaryFiles.count, + expectedAuxiliarySources.count, + "didn't get all of the auxiliary files we expected" + ) +} + +func XCTAssertThrows( + _ expression: @autoclosure () throws -> Ignore, + file: StaticString = #filePath, + line: UInt = #line, + _ errorHandler: (T) -> Bool +) { + do { + let result = try expression() + XCTFail("body completed successfully: \(result)", file: file, line: line) + } catch let error as T { + XCTAssertTrue(errorHandler(error), "Error handler returned false", file: file, line: line) + } catch { + XCTFail("unexpected error thrown: \(error)", file: file, line: line) + } +} From 4a6e7bd9fedea7bb7965af34cc5aaba4f6222816 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 1 Dec 2024 08:55:11 -0800 Subject: [PATCH 02/30] FIXUP for adding package dependency --- Sources/SwiftRefactor/PackageManifest/PackageDependency.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift index 6b9c9ac6337..83980005201 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift @@ -12,6 +12,7 @@ import SwiftParser import SwiftSyntax +import SwiftSyntaxBuilder /// Describes a package dependency for refactoring purposes. This is a syntactic /// subset of the full package manifest's description of a package dependency. From 86581a08c3f07f409e47d212d5e9fa59f8332766 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 30 Nov 2024 08:32:30 -0800 Subject: [PATCH 03/30] Port "Add target dependency" package manifest editing action to SwiftRefactor This manifest editing action introduces a new dependency to an existing target --- Sources/SwiftRefactor/CMakeLists.txt | 2 + .../PackageManifest/AddTargetDependency.swift | 114 ++++++++++++++++++ .../PackageManifest/TargetDescription.swift | 39 ++++++ .../SwiftRefactorTest/ManifestEditTests.swift | 42 +++++++ 4 files changed, 197 insertions(+) create mode 100644 Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/TargetDescription.swift diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index e1cc6a7d8b8..07b1251074d 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -25,6 +25,7 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/AbsolutePath.swift PackageManifest/AddPackageDependency.swift + PackageManifest/AddTargetDependency.swift PackageManifest/ManifestEditError.swift PackageManifest/ManifestEditRefactoringProvider.swift PackageManifest/ManifestSyntaxRepresentable.swift @@ -35,6 +36,7 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/SemanticVersion.swift PackageManifest/SourceControlURL.swift PackageManifest/SyntaxEditUtils.swift + PackageManifest/TargetDescription.swift ) target_link_swift_syntax_libraries(SwiftRefactor PUBLIC diff --git a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift new file mode 100644 index 00000000000..ced093c0faf --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift @@ -0,0 +1,114 @@ +//===----------------------------------------------------------------------===// +// +// 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 target dependency to a manifest's source code. +public struct AddTargetDependency: ManifestEditRefactoringProvider { + public struct Context { + /// The dependency to add. + public var dependency: TargetDescription.Dependency + + /// The name of the target to which the dependency will be added. + public var targetName: String + + public init(dependency: TargetDescription.Dependency, targetName: String) { + self.dependency = dependency + self.targetName = targetName + } + } + + /// The set of argument labels that can occur after the "dependencies" + /// argument in the various target initializers. + /// + /// TODO: Could we generate this from the the PackageDescription module, so + /// we don't have keep it up-to-date manually? + private static let argumentLabelsAfterDependencies: Set = [ + "path", + "exclude", + "sources", + "resources", + "publicHeadersPath", + "packageAccess", + "cSettings", + "cxxSettings", + "swiftSettings", + "linkerSettings", + "plugins", + ] + + /// Produce the set of source edits needed to add the given target + /// dependency to the given manifest file. + public static func manifestRefactor( + syntax manifest: SourceFileSyntax, + in context: Context + ) throws -> PackageEditResult { + let dependency = context.dependency + let targetName = context.targetName + + guard let packageCall = manifest.findCall(calleeName: "Package") else { + 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) + } + + let newTargetCall = try addTargetDependencyLocal( + dependency, + to: targetCall + ) + + return PackageEditResult( + manifestEdits: [ + .replace(targetCall, with: newTargetCall.description) + ] + ) + } + + /// Implementation of adding a target dependency to an existing call. + static func addTargetDependencyLocal( + _ dependency: TargetDescription.Dependency, + to targetCall: FunctionCallExprSyntax + ) throws -> FunctionCallExprSyntax { + try targetCall.appendingToArrayArgument( + label: "dependencies", + trailingLabels: Self.argumentLabelsAfterDependencies, + newElement: dependency.asSyntax() + ) + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift b/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift new file mode 100644 index 00000000000..23b1f423c1e --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftSyntax + +/// Syntactic wrapper type that describes a target for refactoring +/// purposes but does not interpret its contents. +public struct TargetDescription { + public let name: String + + public enum Dependency { + case target(name: String) + case product(name: String, package: String?) + } +} + +extension TargetDescription.Dependency: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + switch self { + case .target(name: let name): + ".target(name: \(literal: name))" + + case .product(name: let name, package: nil): + ".product(name: \(literal: name))" + + case .product(name: let name, package: let package): + ".product(name: \(literal: name), package: \(literal: package))" + } + } +} diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 1375ffbbd3d..948a3946f8e 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -281,6 +281,48 @@ final class ManifestEditTests: XCTestCase { } } } + + func testAddTargetDependency() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-example.git", from: "1.2.3"), + ], + targets: [ + .testTarget( + name: "MyTest" + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-example.git", from: "1.2.3"), + ], + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + .product(name: "SomethingOrOther", package: "swift-example"), + ] + ), + ] + ) + """, + provider: AddTargetDependency.self, + context: .init( + dependency: .product(name: "SomethingOrOther", package: "swift-example"), + targetName: "MyTest" + ) + ) + } + } /// Assert that applying the given edit/refactor operation to the manifest From 1b624fc86c464f79fbeab0281434609b17f528f2 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 30 Nov 2024 17:00:40 -0800 Subject: [PATCH 04/30] Port "Add target" from the Swift Package Manager code base to SwiftRefactor --- Sources/SwiftRefactor/CMakeLists.txt | 2 + .../PackageManifest/AddTarget.swift | 390 ++++++++++++++++++ .../PackageManifest/RelativePath.swift | 14 + .../PackageManifest/StringUtils.swift | 223 ++++++++++ .../PackageManifest/TargetDescription.swift | 78 +++- .../SwiftRefactorTest/ManifestEditTests.swift | 243 +++++++++++ 6 files changed, 949 insertions(+), 1 deletion(-) create mode 100644 Sources/SwiftRefactor/PackageManifest/AddTarget.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/StringUtils.swift diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index 07b1251074d..94190e74182 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -25,6 +25,7 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/AbsolutePath.swift PackageManifest/AddPackageDependency.swift + PackageManifest/AddTarget.swift PackageManifest/AddTargetDependency.swift PackageManifest/ManifestEditError.swift PackageManifest/ManifestEditRefactoringProvider.swift @@ -35,6 +36,7 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/RelativePath.swift PackageManifest/SemanticVersion.swift PackageManifest/SourceControlURL.swift + PackageManifest/StringUtils.swift PackageManifest/SyntaxEditUtils.swift PackageManifest/TargetDescription.swift ) diff --git a/Sources/SwiftRefactor/PackageManifest/AddTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddTarget.swift new file mode 100644 index 00000000000..1bf9b125606 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/AddTarget.swift @@ -0,0 +1,390 @@ +//===----------------------------------------------------------------------===// +// +// 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 target to a manifest's source code. +public struct AddTarget: ManifestEditRefactoringProvider { + public struct Context { + public let target: TargetDescription + public let configuration: Configuration + + public init(target: TargetDescription, configuration: Configuration = .init()) { + self.target = target + self.configuration = configuration + } + } + + /// The set of argument labels that can occur after the "targets" + /// argument in the Package initializers. + /// + /// TODO: Could we generate this from the the PackageDescription module, so + /// we don't have keep it up-to-date manually? + private static let argumentLabelsAfterTargets: Set = [ + "swiftLanguageVersions", + "cLanguageStandard", + "cxxLanguageStandard", + ] + + /// The kind of test harness to use. This isn't part of the manifest + /// itself, but is used to guide the generation process. + public enum TestHarness: String, Codable, Sendable { + /// Don't use any library + case none + + /// Create a test using the XCTest library. + case xctest + + /// Create a test using the swift-testing package. + case swiftTesting = "swift-testing" + + /// The default testing library to use. + public static let `default`: TestHarness = .xctest + } + + /// Additional configuration information to guide the package editing + /// process. + public struct Configuration { + /// The test harness to use. + public var testHarness: TestHarness + + public let swiftSyntaxVersion: SemanticVersion + public let swiftTestingVersion: SemanticVersion + + public init( + testHarness: TestHarness = .default, + swiftSyntaxVersion: SemanticVersion = SemanticVersion("600.0.0-latest"), + swiftTestingVersion: SemanticVersion = SemanticVersion("0.8.0") + ) { + self.testHarness = testHarness + self.swiftSyntaxVersion = swiftSyntaxVersion + self.swiftTestingVersion = swiftTestingVersion + } + } + + /// Add the given target to the manifest, producing a set of edit results + /// that updates the manifest and adds some source files to stub out the + /// new target. + public static func manifestRefactor( + syntax manifest: SourceFileSyntax, + in context: Context + ) throws -> PackageEditResult { + let configuration = context.configuration + guard let packageCall = manifest.findCall(calleeName: "Package") else { + throw ManifestEditError.cannotFindPackage + } + + // Create a mutable version of target to which we can add more + // content when needed. + var target = context.target + + // Add dependencies needed for various targets. + switch target.type { + case .macro: + // Macro targets need to depend on a couple of libraries from + // SwiftSyntax. + target.dependencies.append(contentsOf: macroTargetDependencies) + + default: + break; + } + + var newPackageCall = try packageCall.appendingToArrayArgument( + label: "targets", + trailingLabels: Self.argumentLabelsAfterTargets, + newElement: target.asSyntax() + ) + + let outerDirectory: String? + switch target.type { + case .binary, .plugin, .system: outerDirectory = nil + case .executable, .library, .macro: outerDirectory = "Sources" + case .test: outerDirectory = "Tests" + } + + guard let outerDirectory else { + return PackageEditResult( + manifestEdits: [ + .replace(packageCall, with: newPackageCall.description) + ] + ) + } + + let outerPath = RelativePath(outerDirectory) + + /// The set of auxiliary files this refactoring will create. + var auxiliaryFiles: AuxiliaryFiles = [] + + // Add the primary source file. Every target type has this. + addPrimarySourceFile( + outerPath: outerPath, + target: target, + configuration: configuration, + to: &auxiliaryFiles + ) + + // Perform any other actions that are needed for this target type. + var extraManifestEdits: [SourceEdit] = [] + switch target.type { + case .macro: + addProvidedMacrosSourceFile( + outerPath: outerPath, + target: target, + to: &auxiliaryFiles + ) + + if #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) { + if manifest.description.firstRange(of: "swift-syntax") == nil { + newPackageCall = + try AddPackageDependency + .addPackageDependencyLocal( + .swiftSyntax( + version: configuration.swiftSyntaxVersion + ), + to: newPackageCall + ) + + // Look for the first import declaration and insert an + // import of `CompilerPluginSupport` there. + let newImport = "import CompilerPluginSupport\n" + for node in manifest.statements { + if let importDecl = node.item.as(ImportDeclSyntax.self) { + let insertPos = importDecl + .positionAfterSkippingLeadingTrivia + extraManifestEdits.append( + SourceEdit( + range: insertPos.. ExpressionMacro + /// @attached(member) macro --> MemberMacro + } + """ + + case .test: + switch configuration.testHarness { + case .none: + sourceFileText = """ + \(imports) + // Test code here + """ + + case .xctest: + sourceFileText = """ + \(imports) + class \(raw: target.sanitizedName)Tests: XCTestCase { + func test\(raw: target.sanitizedName)() { + XCTAssertEqual(42, 17 + 25) + } + } + """ + + case .swiftTesting: + sourceFileText = """ + \(imports) + @Suite + struct \(raw: target.sanitizedName)Tests { + @Test("\(raw: target.sanitizedName) tests") + func example() { + #expect(42 == 17 + 25) + } + } + """ + } + + case .library: + sourceFileText = """ + \(imports) + """ + + case .executable: + sourceFileText = """ + \(imports) + @main + struct \(raw: target.sanitizedName)Main { + static func main() { + print("Hello, world") + } + } + """ + } + + auxiliaryFiles.addSourceFile( + path: sourceFilePath, + sourceCode: sourceFileText + ) + } + + /// Add a file that introduces the main entrypoint and provided macros + /// for a macro target. + fileprivate static func addProvidedMacrosSourceFile( + outerPath: RelativePath, + target: TargetDescription, + to auxiliaryFiles: inout AuxiliaryFiles + ) { + auxiliaryFiles.addSourceFile( + path: outerPath.appending( + components: [target.name, "ProvidedMacros.swift"] + ), + sourceCode: """ + import SwiftCompilerPlugin + + @main + struct \(raw: target.sanitizedName)Macros: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + \(raw: target.sanitizedName).self, + ] + } + """ + ) + } +} + +fileprivate extension TargetDescription.Dependency { + /// Retrieve the name of the dependency + var name: String { + switch self { + case .target(name: let name), + .byName(name: let name), + .product(name: let name, package: _): + name + } + } +} + +/// The array of auxiliary files that can be added by a package editing +/// operation. +fileprivate typealias AuxiliaryFiles = [(RelativePath, SourceFileSyntax)] + +fileprivate extension AuxiliaryFiles { + /// Add a source file to the list of auxiliary files. + mutating func addSourceFile( + path: RelativePath, + sourceCode: SourceFileSyntax + ) { + self.append((path, sourceCode)) + } +} + +/// The set of dependencies we need to introduce to a newly-created macro +/// target. +fileprivate let macroTargetDependencies: [TargetDescription.Dependency] = [ + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), +] + +/// The package dependency for swift-syntax, for use in macros. +fileprivate extension PackageDependency { + /// Source control URL for the swift-syntax package. + static var swiftSyntaxURL: SourceControlURL { + .init("https://github.com/swiftlang/swift-syntax.git") + } + + /// Package dependency on the swift-syntax package. + static func swiftSyntax( + version: SemanticVersion + ) -> PackageDependency { + return .sourceControl( + .init( + identity: PackageIdentity("swift-syntax"), + location: .remote(swiftSyntaxURL), + requirement: .rangeFrom(version) + ) + ) + } +} + +fileprivate extension TargetDescription { + var sanitizedName: String { + name + .mangledToC99ExtendedIdentifier() + .localizedFirstWordCapitalized() + } +} + +fileprivate extension String { + func localizedFirstWordCapitalized() -> String { prefix(1).uppercased() + dropFirst() } +} diff --git a/Sources/SwiftRefactor/PackageManifest/RelativePath.swift b/Sources/SwiftRefactor/PackageManifest/RelativePath.swift index c7e07451636..1eb2a46e4e3 100644 --- a/Sources/SwiftRefactor/PackageManifest/RelativePath.swift +++ b/Sources/SwiftRefactor/PackageManifest/RelativePath.swift @@ -18,4 +18,18 @@ public struct RelativePath: CustomStringConvertible, Equatable, Hashable, Sendab public init(_ description: String) { self.description = description } + + #if os(Windows) + static let pathSeparator: Character = "\\" + #else + static let pathSeparator: Character = "/" + #endif + + public func appending(_ component: String) -> Self { + Self(description + String(RelativePath.pathSeparator) + component) + } + + public func appending(components: [String]) -> Self { + appending(components.joined(separator: String(RelativePath.pathSeparator))) + } } diff --git a/Sources/SwiftRefactor/PackageManifest/StringUtils.swift b/Sources/SwiftRefactor/PackageManifest/StringUtils.swift new file mode 100644 index 00000000000..ebc2878284c --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/StringUtils.swift @@ -0,0 +1,223 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +extension String { + /// Returns a form of the string that is valid C99 Extended Identifier (by + /// replacing any invalid characters in an unspecified but consistent way). + /// The output string is guaranteed to be non-empty as long as the input + /// string is non-empty. + func mangledToC99ExtendedIdentifier() -> String { + // Map invalid C99-invalid Unicode scalars to a replacement character. + let replacementUnichar: UnicodeScalar = "_" + var mangledUnichars: [UnicodeScalar] = self.unicodeScalars.map({ + switch $0.value { + case // A-Z + 0x0041...0x005A, + // a-z + 0x0061...0x007A, + // 0-9 + 0x0030...0x0039, + // _ + 0x005F, + // Latin (1) + 0x00AA...0x00AA, + // Special characters (1) + 0x00B5...0x00B5, 0x00B7...0x00B7, + // Latin (2) + 0x00BA...0x00BA, 0x00C0...0x00D6, 0x00D8...0x00F6, + 0x00F8...0x01F5, 0x01FA...0x0217, 0x0250...0x02A8, + // Special characters (2) + 0x02B0...0x02B8, 0x02BB...0x02BB, 0x02BD...0x02C1, + 0x02D0...0x02D1, 0x02E0...0x02E4, 0x037A...0x037A, + // Greek (1) + 0x0386...0x0386, 0x0388...0x038A, 0x038C...0x038C, + 0x038E...0x03A1, 0x03A3...0x03CE, 0x03D0...0x03D6, + 0x03DA...0x03DA, 0x03DC...0x03DC, 0x03DE...0x03DE, + 0x03E0...0x03E0, 0x03E2...0x03F3, + // Cyrillic + 0x0401...0x040C, 0x040E...0x044F, 0x0451...0x045C, + 0x045E...0x0481, 0x0490...0x04C4, 0x04C7...0x04C8, + 0x04CB...0x04CC, 0x04D0...0x04EB, 0x04EE...0x04F5, + 0x04F8...0x04F9, + // Armenian (1) + 0x0531...0x0556, + // Special characters (3) + 0x0559...0x0559, + // Armenian (2) + 0x0561...0x0587, + // Hebrew + 0x05B0...0x05B9, 0x05BB...0x05BD, 0x05BF...0x05BF, + 0x05C1...0x05C2, 0x05D0...0x05EA, 0x05F0...0x05F2, + // Arabic (1) + 0x0621...0x063A, 0x0640...0x0652, + // Digits (1) + 0x0660...0x0669, + // Arabic (2) + 0x0670...0x06B7, 0x06BA...0x06BE, 0x06C0...0x06CE, + 0x06D0...0x06DC, 0x06E5...0x06E8, 0x06EA...0x06ED, + // Digits (2) + 0x06F0...0x06F9, + // Devanagari and Special character 0x093D. + 0x0901...0x0903, 0x0905...0x0939, 0x093D...0x094D, + 0x0950...0x0952, 0x0958...0x0963, + // Digits (3) + 0x0966...0x096F, + // Bengali (1) + 0x0981...0x0983, 0x0985...0x098C, 0x098F...0x0990, + 0x0993...0x09A8, 0x09AA...0x09B0, 0x09B2...0x09B2, + 0x09B6...0x09B9, 0x09BE...0x09C4, 0x09C7...0x09C8, + 0x09CB...0x09CD, 0x09DC...0x09DD, 0x09DF...0x09E3, + // Digits (4) + 0x09E6...0x09EF, + // Bengali (2) + 0x09F0...0x09F1, + // Gurmukhi (1) + 0x0A02...0x0A02, 0x0A05...0x0A0A, 0x0A0F...0x0A10, + 0x0A13...0x0A28, 0x0A2A...0x0A30, 0x0A32...0x0A33, + 0x0A35...0x0A36, 0x0A38...0x0A39, 0x0A3E...0x0A42, + 0x0A47...0x0A48, 0x0A4B...0x0A4D, 0x0A59...0x0A5C, + 0x0A5E...0x0A5E, + // Digits (5) + 0x0A66...0x0A6F, + // Gurmukhi (2) + 0x0A74...0x0A74, + // Gujarti + 0x0A81...0x0A83, 0x0A85...0x0A8B, 0x0A8D...0x0A8D, + 0x0A8F...0x0A91, 0x0A93...0x0AA8, 0x0AAA...0x0AB0, + 0x0AB2...0x0AB3, 0x0AB5...0x0AB9, 0x0ABD...0x0AC5, + 0x0AC7...0x0AC9, 0x0ACB...0x0ACD, 0x0AD0...0x0AD0, + 0x0AE0...0x0AE0, + // Digits (6) + 0x0AE6...0x0AEF, + // Oriya and Special character 0x0B3D + 0x0B01...0x0B03, 0x0B05...0x0B0C, 0x0B0F...0x0B10, + 0x0B13...0x0B28, 0x0B2A...0x0B30, 0x0B32...0x0B33, + 0x0B36...0x0B39, 0x0B3D...0x0B43, 0x0B47...0x0B48, + 0x0B4B...0x0B4D, 0x0B5C...0x0B5D, 0x0B5F...0x0B61, + // Digits (7) + 0x0B66...0x0B6F, + // Tamil + 0x0B82...0x0B83, 0x0B85...0x0B8A, 0x0B8E...0x0B90, + 0x0B92...0x0B95, 0x0B99...0x0B9A, 0x0B9C...0x0B9C, + 0x0B9E...0x0B9F, 0x0BA3...0x0BA4, 0x0BA8...0x0BAA, + 0x0BAE...0x0BB5, 0x0BB7...0x0BB9, 0x0BBE...0x0BC2, + 0x0BC6...0x0BC8, 0x0BCA...0x0BCD, + // Digits (8) + 0x0BE7...0x0BEF, + // Telugu + 0x0C01...0x0C03, 0x0C05...0x0C0C, 0x0C0E...0x0C10, + 0x0C12...0x0C28, 0x0C2A...0x0C33, 0x0C35...0x0C39, + 0x0C3E...0x0C44, 0x0C46...0x0C48, 0x0C4A...0x0C4D, + 0x0C60...0x0C61, + // Digits (9) + 0x0C66...0x0C6F, + // Kannada + 0x0C82...0x0C83, 0x0C85...0x0C8C, 0x0C8E...0x0C90, + 0x0C92...0x0CA8, 0x0CAA...0x0CB3, 0x0CB5...0x0CB9, + 0x0CBE...0x0CC4, 0x0CC6...0x0CC8, 0x0CCA...0x0CCD, + 0x0CDE...0x0CDE, 0x0CE0...0x0CE1, + // Digits (10) + 0x0CE6...0x0CEF, + // Malayam + 0x0D02...0x0D03, 0x0D05...0x0D0C, 0x0D0E...0x0D10, + 0x0D12...0x0D28, 0x0D2A...0x0D39, 0x0D3E...0x0D43, + 0x0D46...0x0D48, 0x0D4A...0x0D4D, 0x0D60...0x0D61, + // Digits (11) + 0x0D66...0x0D6F, + // Thai...including Digits 0x0E50...0x0E59 } + 0x0E01...0x0E3A, 0x0E40...0x0E5B, + // Lao (1) + 0x0E81...0x0E82, 0x0E84...0x0E84, 0x0E87...0x0E88, + 0x0E8A...0x0E8A, 0x0E8D...0x0E8D, 0x0E94...0x0E97, + 0x0E99...0x0E9F, 0x0EA1...0x0EA3, 0x0EA5...0x0EA5, + 0x0EA7...0x0EA7, 0x0EAA...0x0EAB, 0x0EAD...0x0EAE, + 0x0EB0...0x0EB9, 0x0EBB...0x0EBD, 0x0EC0...0x0EC4, + 0x0EC6...0x0EC6, 0x0EC8...0x0ECD, + // Digits (12) + 0x0ED0...0x0ED9, + // Lao (2) + 0x0EDC...0x0EDD, + // Tibetan (1) + 0x0F00...0x0F00, 0x0F18...0x0F19, + // Digits (13) + 0x0F20...0x0F33, + // Tibetan (2) + 0x0F35...0x0F35, 0x0F37...0x0F37, 0x0F39...0x0F39, + 0x0F3E...0x0F47, 0x0F49...0x0F69, 0x0F71...0x0F84, + 0x0F86...0x0F8B, 0x0F90...0x0F95, 0x0F97...0x0F97, + 0x0F99...0x0FAD, 0x0FB1...0x0FB7, 0x0FB9...0x0FB9, + // Georgian + 0x10A0...0x10C5, 0x10D0...0x10F6, + // Latin (3) + 0x1E00...0x1E9B, 0x1EA0...0x1EF9, + // Greek (2) + 0x1F00...0x1F15, 0x1F18...0x1F1D, 0x1F20...0x1F45, + 0x1F48...0x1F4D, 0x1F50...0x1F57, 0x1F59...0x1F59, + 0x1F5B...0x1F5B, 0x1F5D...0x1F5D, 0x1F5F...0x1F7D, + 0x1F80...0x1FB4, 0x1FB6...0x1FBC, + // Special characters (4) + 0x1FBE...0x1FBE, + // Greek (3) + 0x1FC2...0x1FC4, 0x1FC6...0x1FCC, 0x1FD0...0x1FD3, + 0x1FD6...0x1FDB, 0x1FE0...0x1FEC, 0x1FF2...0x1FF4, + 0x1FF6...0x1FFC, + // Special characters (5) + 0x203F...0x2040, + // Latin (4) + 0x207F...0x207F, + // Special characters (6) + 0x2102...0x2102, 0x2107...0x2107, 0x210A...0x2113, + 0x2115...0x2115, 0x2118...0x211D, 0x2124...0x2124, + 0x2126...0x2126, 0x2128...0x2128, 0x212A...0x2131, + 0x2133...0x2138, 0x2160...0x2182, 0x3005...0x3007, + 0x3021...0x3029, + // Hiragana + 0x3041...0x3093, 0x309B...0x309C, + // Katakana + 0x30A1...0x30F6, 0x30FB...0x30FC, + // Bopmofo [sic] + 0x3105...0x312C, + // CJK Unified Ideographs + 0x4E00...0x9FA5, + // Hangul, + 0xAC00...0xD7A3: + return $0 + default: + return replacementUnichar + } + }) + + // Apply further restrictions to the prefix. + loop: for (idx, c) in mangledUnichars.enumerated() { + switch c.value { + case // 0-9 + 0x0030...0x0039, + // Annex D. + 0x0660...0x0669, 0x06F0...0x06F9, 0x0966...0x096F, + 0x09E6...0x09EF, 0x0A66...0x0A6F, 0x0AE6...0x0AEF, + 0x0B66...0x0B6F, 0x0BE7...0x0BEF, 0x0C66...0x0C6F, + 0x0CE6...0x0CEF, 0x0D66...0x0D6F, 0x0E50...0x0E59, + 0x0ED0...0x0ED9, 0x0F20...0x0F33: + mangledUnichars[idx] = replacementUnichar + break loop + default: + break loop + } + } + + // Combine the characters as a string again and return it. + // FIXME: We should only construct a new string if anything changed. + // FIXME: There doesn't seem to be a way to create a string from an + // array of Unicode scalars; but there must be a better way. + return mangledUnichars.reduce("") { $0 + String($1) } + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift b/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift index 23b1f423c1e..d5cb089ed47 100644 --- a/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift +++ b/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift @@ -17,15 +17,91 @@ import SwiftSyntax public struct TargetDescription { public let name: String - public enum Dependency { + /// The type of target. + public let type: TargetKind + + public internal(set) var dependencies: [Dependency] + + public let path: String? + + public let url: String? + + public let checksum: String? + + public enum TargetKind: String { + case binary + case executable + case library + case macro + case plugin + case system + case test + } + + public enum Dependency: Sendable { + case byName(name: String) 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 + ) { + self.name = name + self.type = type + self.dependencies = dependencies + self.path = path + self.url = url + self.checksum = checksum + } +} + +extension TargetDescription: ManifestSyntaxRepresentable { + /// The function name in the package manifest. + private var functionName: String { + switch type { + case .binary: "binaryTarget" + case .executable: "executableTarget" + case .library: "target" + case .macro: "macro" + case .plugin: "plugin" + case .system: "systemLibrary" + case .test: "testTarget" + } + } + + func asSyntax() -> ExprSyntax { + var arguments: [LabeledExprSyntax] = [] + arguments.append(label: "name", stringLiteral: name) + // FIXME: pluginCapability + + arguments.appendIfNonEmpty( + label: "dependencies", + arrayLiteral: dependencies + ) + + arguments.appendIf(label: "path", stringLiteral: path) + arguments.appendIf(label: "url", stringLiteral: url) + + // Only for plugins + arguments.appendIf(label: "checksum", stringLiteral: checksum) + + let separateParen: String = arguments.count > 1 ? "\n" : "" + let argumentsSyntax = LabeledExprListSyntax(arguments) + return ".\(raw: functionName)(\(argumentsSyntax)\(raw: separateParen))" + } } extension TargetDescription.Dependency: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { switch self { + case .byName(name: let name): + "\(literal: name)" + case .target(name: let name): ".target(name: \(literal: name))" diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 948a3946f8e..e60c5617c24 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -282,6 +282,249 @@ final class ManifestEditTests: XCTestCase { } } + func testAddLibraryTarget() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages" + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + targets: [ + .target(name: "MyLib"), + ] + ) + """, + expectedAuxiliarySources: [ + RelativePath("Sources/MyLib/MyLib.swift"): """ + + """ + ], + provider: AddTarget.self, + context: .init( + target: TargetDescription(name: "MyLib") + ) + ) + } + + func testAddLibraryTargetWithDependencies() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages" + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + targets: [ + .target( + name: "MyLib", + dependencies: [ + "OtherLib", + .product(name: "SwiftSyntax", package: "swift-syntax"), + .target(name: "TargetLib") + ] + ), + ] + ) + """, + expectedAuxiliarySources: [ + RelativePath("Sources/MyLib/MyLib.swift"): """ + import OtherLib + import SwiftSyntax + import TargetLib + + """ + ], + provider: AddTarget.self, + context: .init( + target: TargetDescription( + name: "MyLib", + dependencies: [ + .byName(name: "OtherLib"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .target(name: "TargetLib"), + ] + ) + ) + ) + } + + func testAddExecutableTargetWithDependencies() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + targets: [ + // These are the targets + .target(name: "MyLib") + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + targets: [ + // These are the targets + .target(name: "MyLib"), + .executableTarget( + name: "MyProgram target-name", + dependencies: [ + .product(name: "SwiftSyntax", package: "swift-syntax"), + .target(name: "TargetLib"), + "MyLib" + ] + ), + ] + ) + """, + expectedAuxiliarySources: [ + RelativePath("Sources/MyProgram target-name/MyProgram target-name.swift"): """ + import MyLib + import SwiftSyntax + import TargetLib + + @main + struct MyProgram_target_nameMain { + static func main() { + print("Hello, world") + } + } + """ + ], + provider: AddTarget.self, + context: .init( + target: TargetDescription( + name: "MyProgram target-name", + type: .executable, + dependencies: [ + .product(name: "SwiftSyntax", package: "swift-syntax"), + .target(name: "TargetLib"), + .byName(name: "MyLib"), + ] + ), + ) + ) + } + + func testAddMacroTarget() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + import PackageDescription + + let package = Package( + name: "packages" + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + import CompilerPluginSupport + import PackageDescription + + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0-latest"), + ], + targets: [ + .macro( + name: "MyMacro target-name", + dependencies: [ + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + .product(name: "SwiftSyntaxMacros", package: "swift-syntax") + ] + ), + ] + ) + """, + expectedAuxiliarySources: [ + RelativePath("Sources/MyMacro target-name/MyMacro target-name.swift"): """ + import SwiftCompilerPlugin + import SwiftSyntaxMacros + + struct MyMacro_target_name: Macro { + /// TODO: Implement one or more of the protocols that inherit + /// from Macro. The appropriate macro protocol is determined + /// by the "macro" declaration that MyMacro_target_name implements. + /// Examples include: + /// @freestanding(expression) macro --> ExpressionMacro + /// @attached(member) macro --> MemberMacro + } + """, + RelativePath("Sources/MyMacro target-name/ProvidedMacros.swift"): """ + import SwiftCompilerPlugin + + @main + struct MyMacro_target_nameMacros: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + MyMacro_target_name.self, + ] + } + """, + ], + provider: AddTarget.self, + context: .init( + target: TargetDescription( + name: "MyMacro target-name", + type: .macro + ) + ) + ) + } + + func testAddSwiftTestingTestTarget() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages" + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + targets: [ + .testTarget(name: "MyTest target-name"), + ] + ) + """, + expectedAuxiliarySources: [ + RelativePath("Tests/MyTest target-name/MyTest target-name.swift"): """ + import Testing + + @Suite + struct MyTest_target_nameTests { + @Test("MyTest_target_name tests") + func example() { + #expect(42 == 17 + 25) + } + } + """ + ], + provider: AddTarget.self, + context: .init( + target: TargetDescription( + name: "MyTest target-name", + type: .test + ), + configuration: .init( + testHarness: .swiftTesting + ) + ) + ) + } + func testAddTargetDependency() throws { try assertManifestRefactor( """ From d308aca23709ababcf0e16ab8fd7f6631c0381cf Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 30 Nov 2024 17:10:38 -0800 Subject: [PATCH 05/30] Port "Add Product" manifest edit refactor over from SwiftPM --- Sources/SwiftRefactor/CMakeLists.txt | 3 + .../PackageManifest/AddProduct.swift | 63 +++++++++++++++ .../PackageManifest/ProductDescription.swift | 81 +++++++++++++++++++ .../PackageManifest/ProductType.swift | 46 +++++++++++ .../PackageManifest/StringUtils.swift | 4 +- .../SwiftRefactorTest/ManifestEditTests.swift | 39 +++++++++ 6 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 Sources/SwiftRefactor/PackageManifest/AddProduct.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/ProductDescription.swift create mode 100644 Sources/SwiftRefactor/PackageManifest/ProductType.swift diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index 94190e74182..cb161920762 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -25,6 +25,7 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/AbsolutePath.swift PackageManifest/AddPackageDependency.swift + PackageManifest/AddProduct.swift PackageManifest/AddTarget.swift PackageManifest/AddTargetDependency.swift PackageManifest/ManifestEditError.swift @@ -33,6 +34,8 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/PackageDependency.swift PackageManifest/PackageEditResult.swift PackageManifest/PackageIdentity.swift + PackageManifest/ProductDescription.swift + PackageManifest/ProductType.swift PackageManifest/RelativePath.swift PackageManifest/SemanticVersion.swift PackageManifest/SourceControlURL.swift diff --git a/Sources/SwiftRefactor/PackageManifest/AddProduct.swift b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift new file mode 100644 index 00000000000..7bef553ab34 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// 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 product to the manifest's source code. +public struct AddProduct: ManifestEditRefactoringProvider { + public struct Context { + public let product: ProductDescription + + public init(product: ProductDescription) { + self.product = product + } + } + /// The set of argument labels that can occur after the "products" + /// argument in the Package initializers. + /// + /// TODO: Could we generate this from the the PackageDescription module, so + /// we don't have keep it up-to-date manually? + private static let argumentLabelsAfterProducts: Set = [ + "dependencies", + "targets", + "swiftLanguageVersions", + "cLanguageStandard", + "cxxLanguageStandard", + ] + + /// 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 product = context.product + + guard let packageCall = manifest.findCall(calleeName: "Package") else { + throw ManifestEditError.cannotFindPackage + } + + let newPackageCall = try packageCall.appendingToArrayArgument( + label: "products", + trailingLabels: argumentLabelsAfterProducts, + newElement: product.asSyntax() + ) + + return PackageEditResult( + manifestEdits: [ + .replace(packageCall, with: newPackageCall.description) + ] + ) + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift new file mode 100644 index 00000000000..d7db1762536 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2020 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 SwiftSyntax + +/// Syntactic wrapper type that describes a product for refactoring +/// purposes but does not interpret its contents. +public struct ProductDescription { + /// The name of the product. + public let name: String + + /// The targets in the product. + public let targets: [String] + + /// The type of product. + public let type: ProductType + + public init( + name: String, + type: ProductType, + targets: [String] + ) { + self.name = name + self.type = type + self.targets = targets + } +} + +extension ProductDescription: ManifestSyntaxRepresentable { + /// The function name in the package manifest. + /// + /// Some of these are actually invalid, but it's up to the caller + /// to check the precondition. + private var functionName: String { + switch type { + case .executable: "executable" + case .library(_): "library" + case .macro: "macro" + case .plugin: "plugin" + case .snippet: "snippet" + case .test: "test" + } + } + + func asSyntax() -> ExprSyntax { + var arguments: [LabeledExprSyntax] = [] + arguments.append(label: "name", stringLiteral: name) + + // Libraries have a type. + if case .library(let libraryType) = type { + switch libraryType { + case .automatic: + break + + case .dynamic, .static: + arguments.append( + label: "type", + expression: ".\(raw: libraryType.rawValue)" + ) + } + } + + arguments.appendIfNonEmpty( + label: "targets", + arrayLiteral: targets + ) + + let separateParen: String = arguments.count > 1 ? "\n" : "" + let argumentsSyntax = LabeledExprListSyntax(arguments) + return ".\(raw: functionName)(\(argumentsSyntax)\(raw: separateParen))" + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/ProductType.swift b/Sources/SwiftRefactor/PackageManifest/ProductType.swift new file mode 100644 index 00000000000..31e0c3acb7d --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/ProductType.swift @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2020 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 +// +//===----------------------------------------------------------------------===// + +/// Syntactic wrapper type that describes a product type for refactoring +/// purposes but does not interpret its contents. +public enum ProductType { + /// The type of library. + public enum LibraryType: String, Codable, Sendable { + + /// Static library. + case `static` + + /// Dynamic library. + case `dynamic` + + /// The type of library is unspecified and should be decided by package manager. + case automatic + } + + /// A library product. + case library(LibraryType) + + /// An executable product. + case executable + + /// An executable code snippet. + case snippet + + /// An plugin product. + case plugin + + /// A test product. + case test + + /// A macro product. + case `macro` +} diff --git a/Sources/SwiftRefactor/PackageManifest/StringUtils.swift b/Sources/SwiftRefactor/PackageManifest/StringUtils.swift index ebc2878284c..ff412739ca8 100644 --- a/Sources/SwiftRefactor/PackageManifest/StringUtils.swift +++ b/Sources/SwiftRefactor/PackageManifest/StringUtils.swift @@ -20,7 +20,7 @@ extension String { let replacementUnichar: UnicodeScalar = "_" var mangledUnichars: [UnicodeScalar] = self.unicodeScalars.map({ switch $0.value { - case // A-Z + case // A-Z 0x0041...0x005A, // a-z 0x0061...0x007A, @@ -199,7 +199,7 @@ extension String { // Apply further restrictions to the prefix. loop: for (idx, c) in mangledUnichars.enumerated() { switch c.value { - case // 0-9 + case // 0-9 0x0030...0x0039, // Annex D. 0x0660...0x0669, 0x06F0...0x06F9, 0x0966...0x096F, diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index e60c5617c24..a9788e84a18 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -282,6 +282,45 @@ final class ManifestEditTests: XCTestCase { } } + func testAddLibraryProduct() throws { + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + targets: [ + .target(name: "MyLib"), + ], + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + products: [ + .library( + name: "MyLib", + type: .dynamic, + targets: [ "MyLib" ] + ), + ], + targets: [ + .target(name: "MyLib"), + ], + ) + """, + provider: AddProduct.self, + context: .init( + product: + ProductDescription( + name: "MyLib", + type: .library(.dynamic), + targets: ["MyLib"] + ) + ) + ) + } + func testAddLibraryTarget() throws { try assertManifestRefactor( """ From 11649fd77e4ac95aa1754610096bcde2a276744d Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 30 Nov 2024 17:36:40 -0800 Subject: [PATCH 06/30] Add a package manifest edit refactor to introduce a new plugin usage 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. --- Sources/SwiftRefactor/CMakeLists.txt | 1 + .../PackageManifest/AddPluginUsage.swift | 65 +++++++++++++++++++ .../PackageManifest/AddTargetDependency.swift | 28 +------- .../PackageManifest/SyntaxEditUtils.swift | 35 ++++++++++ .../PackageManifest/TargetDescription.swift | 29 ++++++++- .../SwiftRefactorTest/ManifestEditTests.swift | 34 ++++++++++ 6 files changed, 165 insertions(+), 27 deletions(-) create mode 100644 Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index cb161920762..0b247dd25e8 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -25,6 +25,7 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/AbsolutePath.swift PackageManifest/AddPackageDependency.swift + PackageManifest/AddPluginUsage.swift PackageManifest/AddProduct.swift PackageManifest/AddTarget.swift PackageManifest/AddTargetDependency.swift diff --git a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift new file mode 100644 index 00000000000..7a6e14f9d04 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift @@ -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 = [] + + /// 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) + ] + ) + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift index ced093c0faf..6e5d398a52a 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift @@ -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, diff --git a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift index 2a00e894629..a5fcc20eb9d 100644 --- a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift +++ b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift @@ -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. diff --git a/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift b/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift index d5cb089ed47..d0a6852663c 100644 --- a/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift +++ b/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift @@ -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 @@ -43,13 +51,15 @@ 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 @@ -57,6 +67,7 @@ public struct TargetDescription { self.path = path self.url = url self.checksum = checksum + self.pluginUsages = pluginUsages } } @@ -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))" @@ -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))" + } + } +} diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index a9788e84a18..5df32bc70bf 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -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 From 1aa14e6d7f8bb8e17b3a0a3aea59d51a0cc816ee Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 1 Dec 2024 11:06:27 -0800 Subject: [PATCH 07/30] Implement a better "contains string literal" check for a source file --- .../PackageManifest/AddTarget.swift | 78 +++++++++++++------ 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/AddTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddTarget.swift index 1bf9b125606..800e47a5214 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTarget.swift @@ -144,32 +144,30 @@ public struct AddTarget: ManifestEditRefactoringProvider { to: &auxiliaryFiles ) - if #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) { - if manifest.description.firstRange(of: "swift-syntax") == nil { - newPackageCall = - try AddPackageDependency - .addPackageDependencyLocal( - .swiftSyntax( - version: configuration.swiftSyntaxVersion - ), - to: newPackageCall - ) - - // Look for the first import declaration and insert an - // import of `CompilerPluginSupport` there. - let newImport = "import CompilerPluginSupport\n" - for node in manifest.statements { - if let importDecl = node.item.as(ImportDeclSyntax.self) { - let insertPos = importDecl - .positionAfterSkippingLeadingTrivia - extraManifestEdits.append( - SourceEdit( - range: insertPos.. String { prefix(1).uppercased() + dropFirst() } } + +extension SourceFileSyntax { + private class ContainsLiteralVisitor: SyntaxVisitor { + let string: String + var found: Bool = false + + init(string: String) { + self.string = string + super.init(viewMode: .sourceAccurate) + } + + override func visit(_ node: StringLiteralExprSyntax) -> SyntaxVisitorContinueKind { + if let representedLiteralValue = node.representedLiteralValue, + representedLiteralValue == string + { + found = true + } + + return .skipChildren + } + } + + /// Determine whether this source file contains a string literal + /// matching the given contents. + fileprivate func containsStringLiteral(_ contents: String) -> Bool { + let visitor = ContainsLiteralVisitor(string: contents) + visitor.walk(self) + return visitor.found + } +} From 89c2854eab31594b89130b0932ba80b369e51e18 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 1 Dec 2024 11:08:23 -0800 Subject: [PATCH 08/30] Remove more uses of switch expressions --- .../PackageManifest/PackageDependency.swift | 18 +++++++++--------- .../PackageManifest/TargetDescription.swift | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift index 83980005201..e1fd8af46bc 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift @@ -89,7 +89,7 @@ extension PackageDependency.SourceControl: ManifestSyntaxRepresentable { case .local: fatalError() case .remote(let url): - ".package(url: \(literal: url.description), \(requirement.asSyntax()))" + return ".package(url: \(literal: url.description), \(requirement.asSyntax()))" } } } @@ -104,30 +104,30 @@ extension PackageDependency.SourceControl.Requirement: ManifestSyntaxRepresentab func asSyntax() -> LabeledExprSyntax { switch self { case .exact(let version): - LabeledExprSyntax( + return LabeledExprSyntax( label: "exact", expression: version.asSyntax() ) case .rangeFrom(let range): - LabeledExprSyntax( + return LabeledExprSyntax( label: "from", expression: range.asSyntax() ) case .range(let lowerBound, let upperBound): - LabeledExprSyntax( + return LabeledExprSyntax( expression: "\(lowerBound.asSyntax())..<\(upperBound.asSyntax())" as ExprSyntax ) case .revision(let revision): - LabeledExprSyntax( + return LabeledExprSyntax( label: "revision", expression: "\(literal: revision)" as ExprSyntax ) case .branch(let branch): - LabeledExprSyntax( + return LabeledExprSyntax( label: "branch", expression: "\(literal: branch)" as ExprSyntax ) @@ -139,19 +139,19 @@ extension PackageDependency.Registry.Requirement: ManifestSyntaxRepresentable { func asSyntax() -> LabeledExprSyntax { switch self { case .exact(let version): - LabeledExprSyntax( + return LabeledExprSyntax( label: "exact", expression: version.asSyntax() ) case .rangeFrom(let range): - LabeledExprSyntax( + return LabeledExprSyntax( label: "from", expression: range.asSyntax() ) case .range(let lowerBound, let upperBound): - LabeledExprSyntax( + return LabeledExprSyntax( expression: "\(lowerBound.asSyntax())..<\(upperBound.asSyntax())" as ExprSyntax ) } diff --git a/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift b/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift index d0a6852663c..f949d820725 100644 --- a/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift +++ b/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift @@ -115,16 +115,16 @@ extension TargetDescription.Dependency: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { switch self { case .byName(name: let name): - "\(literal: name)" + return "\(literal: name)" case .target(name: let name): - ".target(name: \(literal: name))" + return ".target(name: \(literal: name))" case .product(name: let name, package: nil): - ".product(name: \(literal: name))" + return ".product(name: \(literal: name))" case .product(name: let name, package: let package): - ".product(name: \(literal: name), package: \(literal: package))" + return ".product(name: \(literal: name), package: \(literal: package))" } } } @@ -133,10 +133,10 @@ extension TargetDescription.PluginUsage: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { switch self { case .plugin(name: let name, package: nil): - ".plugin(name: \(literal: name))" + return ".plugin(name: \(literal: name))" case .plugin(name: let name, package: let package): - ".plugin(name: \(literal: name), package: \(literal: package))" + return ".plugin(name: \(literal: name), package: \(literal: package))" } } } From ba0ac8ad9cb557566bbc0e52da4bed88b256656c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 1 Dec 2024 22:59:26 -0800 Subject: [PATCH 09/30] Remove yet more uses of switch expressions --- .../SwiftRefactor/PackageManifest/AddTarget.swift | 2 +- .../PackageManifest/ManifestEditError.swift | 8 ++++---- .../PackageManifest/PackageDependency.swift | 6 +++--- .../PackageManifest/ProductDescription.swift | 12 ++++++------ .../PackageManifest/TargetDescription.swift | 14 +++++++------- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/AddTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddTarget.swift index 800e47a5214..46522104666 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTarget.swift @@ -328,7 +328,7 @@ fileprivate extension TargetDescription.Dependency { case .target(name: let name), .byName(name: let name), .product(name: let name, package: _): - name + return name } } } diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift index 3c600ddd9c0..17ad41df4ae 100644 --- a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift @@ -25,13 +25,13 @@ extension ManifestEditError: CustomStringConvertible { public var description: String { switch self { case .cannotFindPackage: - "invalid manifest: unable to find 'Package' declaration" + return "invalid manifest: unable to find 'Package' declaration" case .cannotFindTargets: - "unable to find package targets in manifest" + return "unable to find package targets in manifest" case .cannotFindTarget(targetName: let name): - "unable to find target named '\(name)' in package" + return "unable to find target named '\(name)' in package" case .cannotFindArrayLiteralArgument(argumentName: let name, node: _): - "unable to find array literal for '\(name)' argument" + return "unable to find array literal for '\(name)' argument" } } } diff --git a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift index e1fd8af46bc..eb2a62f1944 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift @@ -68,9 +68,9 @@ public enum PackageDependency: Sendable { extension PackageDependency: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { switch self { - case .fileSystem(let filesystem): filesystem.asSyntax() - case .sourceControl(let sourceControl): sourceControl.asSyntax() - case .registry(let registry): registry.asSyntax() + case .fileSystem(let filesystem): return filesystem.asSyntax() + case .sourceControl(let sourceControl): return sourceControl.asSyntax() + case .registry(let registry): return registry.asSyntax() } } } diff --git a/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift index d7db1762536..c000f864fcc 100644 --- a/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift +++ b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift @@ -42,12 +42,12 @@ extension ProductDescription: ManifestSyntaxRepresentable { /// to check the precondition. private var functionName: String { switch type { - case .executable: "executable" - case .library(_): "library" - case .macro: "macro" - case .plugin: "plugin" - case .snippet: "snippet" - case .test: "test" + case .executable: return "executable" + case .library(_): return "library" + case .macro: return "macro" + case .plugin: return "plugin" + case .snippet: return "snippet" + case .test: return "test" } } diff --git a/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift b/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift index f949d820725..848f3c06d93 100644 --- a/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift +++ b/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift @@ -75,13 +75,13 @@ extension TargetDescription: ManifestSyntaxRepresentable { /// The function name in the package manifest. private var functionName: String { switch type { - case .binary: "binaryTarget" - case .executable: "executableTarget" - case .library: "target" - case .macro: "macro" - case .plugin: "plugin" - case .system: "systemLibrary" - case .test: "testTarget" + case .binary: return "binaryTarget" + case .executable: return "executableTarget" + case .library: return "target" + case .macro: return "macro" + case .plugin: return "plugin" + case .system: return "systemLibrary" + case .test: return "testTarget" } } From 18c9f0efc3de2233f3691eca9826204214c89c3f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 1 Dec 2024 23:05:13 -0800 Subject: [PATCH 10/30] Rename PackageEditResult -> PackageEdit This removes a conflict with the SwiftPM version. --- Sources/SwiftRefactor/CMakeLists.txt | 2 +- .../PackageManifest/AddPackageDependency.swift | 4 ++-- Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift | 4 ++-- Sources/SwiftRefactor/PackageManifest/AddProduct.swift | 4 ++-- Sources/SwiftRefactor/PackageManifest/AddTarget.swift | 6 +++--- .../SwiftRefactor/PackageManifest/AddTargetDependency.swift | 4 ++-- .../PackageManifest/ManifestEditRefactoringProvider.swift | 2 +- .../{PackageEditResult.swift => PackageEdit.swift} | 2 +- Tests/SwiftRefactorTest/ManifestEditTests.swift | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) rename Sources/SwiftRefactor/PackageManifest/{PackageEditResult.swift => PackageEdit.swift} (96%) diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index 0b247dd25e8..403686bf70f 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -33,7 +33,7 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/ManifestEditRefactoringProvider.swift PackageManifest/ManifestSyntaxRepresentable.swift PackageManifest/PackageDependency.swift - PackageManifest/PackageEditResult.swift + PackageManifest/PackageEdit.swift PackageManifest/PackageIdentity.swift PackageManifest/ProductDescription.swift PackageManifest/ProductType.swift diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift index 2d5cc6c4bba..80db72a0e3d 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift @@ -41,7 +41,7 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider { public static func manifestRefactor( syntax manifest: SourceFileSyntax, in context: Context - ) throws -> PackageEditResult { + ) throws -> PackageEdit { let dependency = context.dependency guard let packageCall = manifest.findCall(calleeName: "Package") else { throw ManifestEditError.cannotFindPackage @@ -52,7 +52,7 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider { to: packageCall ) - return PackageEditResult( + return PackageEdit( manifestEdits: [ .replace(packageCall, with: newPackageCall.description) ] diff --git a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift index 7a6e14f9d04..6741ba32da5 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift @@ -39,7 +39,7 @@ public struct AddPluginUsage: ManifestEditRefactoringProvider { public static func manifestRefactor( syntax manifest: SourceFileSyntax, in context: Context - ) throws -> PackageEditResult { + ) throws -> PackageEdit { let targetName = context.targetName let pluginUsage = context.pluginUsage @@ -56,7 +56,7 @@ public struct AddPluginUsage: ManifestEditRefactoringProvider { newElement: pluginUsage.asSyntax() ) - return PackageEditResult( + return PackageEdit( manifestEdits: [ .replace(targetCall, with: newTargetCall.description) ] diff --git a/Sources/SwiftRefactor/PackageManifest/AddProduct.swift b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift index 7bef553ab34..ad862d004a5 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddProduct.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift @@ -41,7 +41,7 @@ public struct AddProduct: ManifestEditRefactoringProvider { public static func manifestRefactor( syntax manifest: SourceFileSyntax, in context: Context - ) throws -> PackageEditResult { + ) throws -> PackageEdit { let product = context.product guard let packageCall = manifest.findCall(calleeName: "Package") else { @@ -54,7 +54,7 @@ public struct AddProduct: ManifestEditRefactoringProvider { newElement: product.asSyntax() ) - return PackageEditResult( + return PackageEdit( manifestEdits: [ .replace(packageCall, with: newPackageCall.description) ] diff --git a/Sources/SwiftRefactor/PackageManifest/AddTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddTarget.swift index 46522104666..145b266e8ce 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTarget.swift @@ -79,7 +79,7 @@ public struct AddTarget: ManifestEditRefactoringProvider { public static func manifestRefactor( syntax manifest: SourceFileSyntax, in context: Context - ) throws -> PackageEditResult { + ) throws -> PackageEdit { let configuration = context.configuration guard let packageCall = manifest.findCall(calleeName: "Package") else { throw ManifestEditError.cannotFindPackage @@ -114,7 +114,7 @@ public struct AddTarget: ManifestEditRefactoringProvider { } guard let outerDirectory else { - return PackageEditResult( + return PackageEdit( manifestEdits: [ .replace(packageCall, with: newPackageCall.description) ] @@ -175,7 +175,7 @@ public struct AddTarget: ManifestEditRefactoringProvider { default: break; } - return PackageEditResult( + return PackageEdit( manifestEdits: [ .replace(packageCall, with: newPackageCall.description) ] + extraManifestEdits, diff --git a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift index 6e5d398a52a..74a560ebeef 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift @@ -53,7 +53,7 @@ public struct AddTargetDependency: ManifestEditRefactoringProvider { public static func manifestRefactor( syntax manifest: SourceFileSyntax, in context: Context - ) throws -> PackageEditResult { + ) throws -> PackageEdit { let dependency = context.dependency let targetName = context.targetName @@ -69,7 +69,7 @@ public struct AddTargetDependency: ManifestEditRefactoringProvider { to: targetCall ) - return PackageEditResult( + return PackageEdit( manifestEdits: [ .replace(targetCall, with: newTargetCall.description) ] diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift index e6532ec7979..6e47412ffd3 100644 --- a/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift @@ -15,7 +15,7 @@ import SwiftSyntax public protocol ManifestEditRefactoringProvider: EditRefactoringProvider where Self.Input == SourceFileSyntax { - static func manifestRefactor(syntax: SourceFileSyntax, in context: Context) throws -> PackageEditResult + static func manifestRefactor(syntax: SourceFileSyntax, in context: Context) throws -> PackageEdit } extension EditRefactoringProvider where Self: ManifestEditRefactoringProvider { diff --git a/Sources/SwiftRefactor/PackageManifest/PackageEditResult.swift b/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift similarity index 96% rename from Sources/SwiftRefactor/PackageManifest/PackageEditResult.swift rename to Sources/SwiftRefactor/PackageManifest/PackageEdit.swift index b3dd59b83da..875a8fc66aa 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageEditResult.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift @@ -14,7 +14,7 @@ import SwiftSyntax /// The result of editing a package, including any edits to the package /// manifest and any new files that are introduced. -public struct PackageEditResult { +public struct PackageEdit { /// Edits to perform to the package manifest. public var manifestEdits: [SourceEdit] = [] diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 5df32bc70bf..c6196a3b970 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -673,7 +673,7 @@ func assertManifestRefactor( expectedAuxiliarySources: [RelativePath: SourceFileSyntax] = [:], file: StaticString = #filePath, line: UInt = #line, - operation: (SourceFileSyntax) throws -> PackageEditResult + operation: (SourceFileSyntax) throws -> PackageEdit ) rethrows { let edits = try operation(originalManifest) let editedManifestSource = FixItApplier.apply( From 4b2073f04d815c4db3d9a86a7e4d0d26cf6c15b4 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 1 Dec 2024 23:09:06 -0800 Subject: [PATCH 11/30] Rename TargetDescription -> Target --- Sources/SwiftRefactor/CMakeLists.txt | 2 +- .../PackageManifest/AddPluginUsage.swift | 4 ++-- .../SwiftRefactor/PackageManifest/AddTarget.swift | 14 +++++++------- .../PackageManifest/AddTargetDependency.swift | 6 +++--- .../{TargetDescription.swift => Target.swift} | 8 ++++---- Tests/SwiftRefactorTest/ManifestEditTests.swift | 10 +++++----- 6 files changed, 22 insertions(+), 22 deletions(-) rename Sources/SwiftRefactor/PackageManifest/{TargetDescription.swift => Target.swift} (94%) diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index 403686bf70f..d8ec8702132 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -42,7 +42,7 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/SourceControlURL.swift PackageManifest/StringUtils.swift PackageManifest/SyntaxEditUtils.swift - PackageManifest/TargetDescription.swift + PackageManifest/Target.swift ) target_link_swift_syntax_libraries(SwiftRefactor PUBLIC diff --git a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift index 6741ba32da5..8a93dd2adf3 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift @@ -19,9 +19,9 @@ import SwiftSyntaxBuilder public struct AddPluginUsage: ManifestEditRefactoringProvider { public struct Context { public let targetName: String - public let pluginUsage: TargetDescription.PluginUsage + public let pluginUsage: Target.PluginUsage - public init(targetName: String, pluginUsage: TargetDescription.PluginUsage) { + public init(targetName: String, pluginUsage: Target.PluginUsage) { self.targetName = targetName self.pluginUsage = pluginUsage } diff --git a/Sources/SwiftRefactor/PackageManifest/AddTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddTarget.swift index 145b266e8ce..667687d8d42 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTarget.swift @@ -17,10 +17,10 @@ import SwiftSyntaxBuilder /// Add a target to a manifest's source code. public struct AddTarget: ManifestEditRefactoringProvider { public struct Context { - public let target: TargetDescription + public let target: Target public let configuration: Configuration - public init(target: TargetDescription, configuration: Configuration = .init()) { + public init(target: Target, configuration: Configuration = .init()) { self.target = target self.configuration = configuration } @@ -187,7 +187,7 @@ public struct AddTarget: ManifestEditRefactoringProvider { /// source files. fileprivate static func addPrimarySourceFile( outerPath: RelativePath, - target: TargetDescription, + target: Target, configuration: Configuration, to auxiliaryFiles: inout AuxiliaryFiles ) { @@ -300,7 +300,7 @@ public struct AddTarget: ManifestEditRefactoringProvider { /// for a macro target. fileprivate static func addProvidedMacrosSourceFile( outerPath: RelativePath, - target: TargetDescription, + target: Target, to auxiliaryFiles: inout AuxiliaryFiles ) { auxiliaryFiles.addSourceFile( @@ -321,7 +321,7 @@ public struct AddTarget: ManifestEditRefactoringProvider { } } -fileprivate extension TargetDescription.Dependency { +fileprivate extension Target.Dependency { /// Retrieve the name of the dependency var name: String { switch self { @@ -349,7 +349,7 @@ fileprivate extension AuxiliaryFiles { /// The set of dependencies we need to introduce to a newly-created macro /// target. -fileprivate let macroTargetDependencies: [TargetDescription.Dependency] = [ +fileprivate let macroTargetDependencies: [Target.Dependency] = [ .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), ] @@ -375,7 +375,7 @@ fileprivate extension PackageDependency { } } -fileprivate extension TargetDescription { +fileprivate extension Target { var sanitizedName: String { name .mangledToC99ExtendedIdentifier() diff --git a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift index 74a560ebeef..ab822131bc8 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift @@ -18,12 +18,12 @@ import SwiftSyntaxBuilder public struct AddTargetDependency: ManifestEditRefactoringProvider { public struct Context { /// The dependency to add. - public var dependency: TargetDescription.Dependency + public var dependency: Target.Dependency /// The name of the target to which the dependency will be added. public var targetName: String - public init(dependency: TargetDescription.Dependency, targetName: String) { + public init(dependency: Target.Dependency, targetName: String) { self.dependency = dependency self.targetName = targetName } @@ -78,7 +78,7 @@ public struct AddTargetDependency: ManifestEditRefactoringProvider { /// Implementation of adding a target dependency to an existing call. static func addTargetDependencyLocal( - _ dependency: TargetDescription.Dependency, + _ dependency: Target.Dependency, to targetCall: FunctionCallExprSyntax ) throws -> FunctionCallExprSyntax { try targetCall.appendingToArrayArgument( diff --git a/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift b/Sources/SwiftRefactor/PackageManifest/Target.swift similarity index 94% rename from Sources/SwiftRefactor/PackageManifest/TargetDescription.swift rename to Sources/SwiftRefactor/PackageManifest/Target.swift index 848f3c06d93..2da65957b09 100644 --- a/Sources/SwiftRefactor/PackageManifest/TargetDescription.swift +++ b/Sources/SwiftRefactor/PackageManifest/Target.swift @@ -14,7 +14,7 @@ import SwiftSyntax /// Syntactic wrapper type that describes a target for refactoring /// purposes but does not interpret its contents. -public struct TargetDescription { +public struct Target { public let name: String /// The type of target. @@ -71,7 +71,7 @@ public struct TargetDescription { } } -extension TargetDescription: ManifestSyntaxRepresentable { +extension Target: ManifestSyntaxRepresentable { /// The function name in the package manifest. private var functionName: String { switch type { @@ -111,7 +111,7 @@ extension TargetDescription: ManifestSyntaxRepresentable { } } -extension TargetDescription.Dependency: ManifestSyntaxRepresentable { +extension Target.Dependency: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { switch self { case .byName(name: let name): @@ -129,7 +129,7 @@ extension TargetDescription.Dependency: ManifestSyntaxRepresentable { } } -extension TargetDescription.PluginUsage: ManifestSyntaxRepresentable { +extension Target.PluginUsage: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { switch self { case .plugin(name: let name, package: nil): diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index c6196a3b970..9982c90f623 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -345,7 +345,7 @@ final class ManifestEditTests: XCTestCase { ], provider: AddTarget.self, context: .init( - target: TargetDescription(name: "MyLib") + target: Target(name: "MyLib") ) ) } @@ -384,7 +384,7 @@ final class ManifestEditTests: XCTestCase { ], provider: AddTarget.self, context: .init( - target: TargetDescription( + target: Target( name: "MyLib", dependencies: [ .byName(name: "OtherLib"), @@ -442,7 +442,7 @@ final class ManifestEditTests: XCTestCase { ], provider: AddTarget.self, context: .init( - target: TargetDescription( + target: Target( name: "MyProgram target-name", type: .executable, dependencies: [ @@ -513,7 +513,7 @@ final class ManifestEditTests: XCTestCase { ], provider: AddTarget.self, context: .init( - target: TargetDescription( + target: Target( name: "MyMacro target-name", type: .macro ) @@ -553,7 +553,7 @@ final class ManifestEditTests: XCTestCase { ], provider: AddTarget.self, context: .init( - target: TargetDescription( + target: Target( name: "MyTest target-name", type: .test ), From 7293a798e5c80b06c6407768187dc752ce067b64 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 1 Dec 2024 23:10:47 -0800 Subject: [PATCH 12/30] Sink ProductType into ProductDescription --- Sources/SwiftRefactor/CMakeLists.txt | 1 - .../PackageManifest/ProductDescription.swift | 33 +++++++++++++ .../PackageManifest/ProductType.swift | 46 ------------------- 3 files changed, 33 insertions(+), 47 deletions(-) delete mode 100644 Sources/SwiftRefactor/PackageManifest/ProductType.swift diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index d8ec8702132..4f422e0d000 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -36,7 +36,6 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/PackageEdit.swift PackageManifest/PackageIdentity.swift PackageManifest/ProductDescription.swift - PackageManifest/ProductType.swift PackageManifest/RelativePath.swift PackageManifest/SemanticVersion.swift PackageManifest/SourceControlURL.swift diff --git a/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift index c000f864fcc..640075f6473 100644 --- a/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift +++ b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift @@ -24,6 +24,39 @@ public struct ProductDescription { /// The type of product. public let type: ProductType + public enum ProductType { + /// The type of library. + public enum LibraryType: String, Codable, Sendable { + + /// Static library. + case `static` + + /// Dynamic library. + case `dynamic` + + /// The type of library is unspecified and should be decided by package manager. + case automatic + } + + /// A library product. + case library(LibraryType) + + /// An executable product. + case executable + + /// An executable code snippet. + case snippet + + /// An plugin product. + case plugin + + /// A test product. + case test + + /// A macro product. + case `macro` + } + public init( name: String, type: ProductType, diff --git a/Sources/SwiftRefactor/PackageManifest/ProductType.swift b/Sources/SwiftRefactor/PackageManifest/ProductType.swift deleted file mode 100644 index 31e0c3acb7d..00000000000 --- a/Sources/SwiftRefactor/PackageManifest/ProductType.swift +++ /dev/null @@ -1,46 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2014-2020 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 -// -//===----------------------------------------------------------------------===// - -/// Syntactic wrapper type that describes a product type for refactoring -/// purposes but does not interpret its contents. -public enum ProductType { - /// The type of library. - public enum LibraryType: String, Codable, Sendable { - - /// Static library. - case `static` - - /// Dynamic library. - case `dynamic` - - /// The type of library is unspecified and should be decided by package manager. - case automatic - } - - /// A library product. - case library(LibraryType) - - /// An executable product. - case executable - - /// An executable code snippet. - case snippet - - /// An plugin product. - case plugin - - /// A test product. - case test - - /// A macro product. - case `macro` -} From e6bd57710b05a72f63392c8b4316657b78c72806 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 1 Dec 2024 23:11:51 -0800 Subject: [PATCH 13/30] Rename AddTarget -> AddPackageTarget --- Sources/SwiftRefactor/CMakeLists.txt | 2 +- .../{AddTarget.swift => AddPackageTarget.swift} | 2 +- Tests/SwiftRefactorTest/ManifestEditTests.swift | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) rename Sources/SwiftRefactor/PackageManifest/{AddTarget.swift => AddPackageTarget.swift} (99%) diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index 4f422e0d000..f9e6667a322 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -25,9 +25,9 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/AbsolutePath.swift PackageManifest/AddPackageDependency.swift + PackageManifest/AddPackageTarget.swift PackageManifest/AddPluginUsage.swift PackageManifest/AddProduct.swift - PackageManifest/AddTarget.swift PackageManifest/AddTargetDependency.swift PackageManifest/ManifestEditError.swift PackageManifest/ManifestEditRefactoringProvider.swift diff --git a/Sources/SwiftRefactor/PackageManifest/AddTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift similarity index 99% rename from Sources/SwiftRefactor/PackageManifest/AddTarget.swift rename to Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index 667687d8d42..8ca778f7205 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -15,7 +15,7 @@ import SwiftSyntax import SwiftSyntaxBuilder /// Add a target to a manifest's source code. -public struct AddTarget: ManifestEditRefactoringProvider { +public struct AddPackageTarget: ManifestEditRefactoringProvider { public struct Context { public let target: Target public let configuration: Configuration diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 9982c90f623..234ce2aade5 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -343,7 +343,7 @@ final class ManifestEditTests: XCTestCase { """ ], - provider: AddTarget.self, + provider: AddPackageTarget.self, context: .init( target: Target(name: "MyLib") ) @@ -382,7 +382,7 @@ final class ManifestEditTests: XCTestCase { """ ], - provider: AddTarget.self, + provider: AddPackageTarget.self, context: .init( target: Target( name: "MyLib", @@ -440,7 +440,7 @@ final class ManifestEditTests: XCTestCase { } """ ], - provider: AddTarget.self, + provider: AddPackageTarget.self, context: .init( target: Target( name: "MyProgram target-name", @@ -511,7 +511,7 @@ final class ManifestEditTests: XCTestCase { } """, ], - provider: AddTarget.self, + provider: AddPackageTarget.self, context: .init( target: Target( name: "MyMacro target-name", @@ -551,7 +551,7 @@ final class ManifestEditTests: XCTestCase { } """ ], - provider: AddTarget.self, + provider: AddPackageTarget.self, context: .init( target: Target( name: "MyTest target-name", From a1ac7ca21f7ac13d62fd7996b4d3f01d8045d043 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Mon, 4 Aug 2025 17:54:49 -0700 Subject: [PATCH 14/30] [PackageManifest] Address NFC review feedback Addresses minor comments like indentation, naming, and code style. --- .../AddPackageDependency.swift | 2 +- .../PackageManifest/AddPackageTarget.swift | 6 +- .../PackageManifest/AddPluginUsage.swift | 2 +- .../PackageManifest/AddProduct.swift | 2 +- .../PackageManifest/AddTargetDependency.swift | 2 +- .../PackageManifest/SyntaxEditUtils.swift | 62 ++++++++----------- 6 files changed, 32 insertions(+), 44 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift index 80db72a0e3d..7725ca749f1 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift @@ -66,7 +66,7 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider { ) throws -> FunctionCallExprSyntax { try packageCall.appendingToArrayArgument( label: "dependencies", - trailingLabels: Self.argumentLabelsAfterDependencies, + labelsAfter: Self.argumentLabelsAfterDependencies, newElement: dependency.asSyntax() ) } diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index 8ca778f7205..ddf07ffe0ef 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -97,12 +97,12 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { target.dependencies.append(contentsOf: macroTargetDependencies) default: - break; + break } var newPackageCall = try packageCall.appendingToArrayArgument( label: "targets", - trailingLabels: Self.argumentLabelsAfterTargets, + labelsAfter: Self.argumentLabelsAfterTargets, newElement: target.asSyntax() ) @@ -172,7 +172,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { } } - default: break; + default: break } return PackageEdit( diff --git a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift index 8a93dd2adf3..80edc0b34a8 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift @@ -52,7 +52,7 @@ public struct AddPluginUsage: ManifestEditRefactoringProvider { let newTargetCall = try targetCall.appendingToArrayArgument( label: "plugins", - trailingLabels: Self.argumentLabelsAfterPluginUsages, + labelsAfter: Self.argumentLabelsAfterPluginUsages, newElement: pluginUsage.asSyntax() ) diff --git a/Sources/SwiftRefactor/PackageManifest/AddProduct.swift b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift index ad862d004a5..3ed54d37c56 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddProduct.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift @@ -50,7 +50,7 @@ public struct AddProduct: ManifestEditRefactoringProvider { let newPackageCall = try packageCall.appendingToArrayArgument( label: "products", - trailingLabels: argumentLabelsAfterProducts, + labelsAfter: argumentLabelsAfterProducts, newElement: product.asSyntax() ) diff --git a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift index ab822131bc8..0a1910b9bf5 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift @@ -83,7 +83,7 @@ public struct AddTargetDependency: ManifestEditRefactoringProvider { ) throws -> FunctionCallExprSyntax { try targetCall.appendingToArrayArgument( label: "dependencies", - trailingLabels: Self.argumentLabelsAfterDependencies, + labelsAfter: Self.argumentLabelsAfterDependencies, newElement: dependency.asSyntax() ) } diff --git a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift index a5fcc20eb9d..2c3c4872a85 100644 --- a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift +++ b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift @@ -26,7 +26,7 @@ extension Trivia { /// Produce trivia from the last newline to the end, dropping anything /// prior to that. - func onlyLastLine() -> Trivia { + var onlyLastLine: Trivia { guard let lastNewline = pieces.lastIndex(where: { $0.isNewline }) else { return self } @@ -122,15 +122,13 @@ extension LabeledExprListSyntax { /// context. func insertingArgument( at position: SyntaxChildrenIndex, - generator: (Trivia, TokenSyntax?) -> LabeledExprSyntax + generator: (_ leadingTrivia: Trivia, _ trailingComma: TokenSyntax?) -> LabeledExprSyntax ) -> LabeledExprListSyntax { // Turn the arguments into an array so we can manipulate them. var arguments = Array(self) let positionIdx = distance(from: startIndex, to: position) - let commaToken = TokenSyntax.commaToken() - // Figure out leading trivia and adjust the prior argument (if there is // one) by adding a comma, if necessary. let leadingTrivia: Trivia @@ -143,7 +141,7 @@ extension LabeledExprListSyntax { // If the prior argument is missing a trailing comma, add one. if priorArgument.trailingComma == nil { - arguments[positionIdx - 1].trailingComma = commaToken + arguments[positionIdx - 1].trailingComma = .commaToken() } } else if positionIdx + 1 < count { leadingTrivia = arguments[positionIdx + 1].leadingTrivia @@ -154,7 +152,7 @@ extension LabeledExprListSyntax { // Determine whether we need a trailing comma on this argument. let trailingComma: TokenSyntax? if position < endIndex { - trailingComma = commaToken + trailingComma = .commaToken() } else { trailingComma = nil } @@ -194,9 +192,7 @@ extension FunctionCallExprSyntax { return false } - guard let stringLiteral = nameArgument.expression.as(StringLiteralExprSyntax.self), - let literalValue = stringLiteral.representedLiteralValue - else { + guard let literalValue = nameArgument.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue else { return false } @@ -220,8 +216,6 @@ extension ArrayExprSyntax { ) -> ArrayExprSyntax { var elements = self.elements - let commaToken = TokenSyntax.commaToken() - // If there are already elements, tack it on. let leadingTrivia: Trivia let trailingTrivia: Trivia @@ -229,15 +223,14 @@ extension ArrayExprSyntax { if let last = elements.last { // The leading trivia of the new element should match that of the // last element. - leadingTrivia = last.leadingTrivia.onlyLastLine() + leadingTrivia = last.leadingTrivia.onlyLastLine // Add a trailing comma to the last element if it isn't already // there. if last.trailingComma == nil { var newElements = Array(elements) - newElements[newElements.count - 1].trailingComma = commaToken - newElements[newElements.count - 1].expression.trailingTrivia = - Trivia() + newElements[newElements.count - 1].trailingComma = .commaToken() + newElements[newElements.count - 1].expression.trailingTrivia = Trivia() newElements[newElements.count - 1].trailingTrivia = last.trailingTrivia elements = ArrayElementListSyntax(newElements) } @@ -257,7 +250,7 @@ extension ArrayExprSyntax { elements.append( ArrayElementSyntax( expression: element.with(\.leadingTrivia, leadingTrivia), - trailingComma: commaToken.with(\.trailingTrivia, trailingTrivia) + trailingComma: .commaToken().with(\.trailingTrivia, trailingTrivia) ) ) @@ -351,17 +344,16 @@ extension Array { /// Append a potentially labeled argument with a string literal, but only /// when the string literal is not nil. mutating func appendIf(label: String?, stringLiteral: String?) { - if let stringLiteral { - append(label: label, stringLiteral: stringLiteral) - } + guard let stringLiteral else { return } + append(label: label, stringLiteral: stringLiteral) } /// Append an array literal containing elements that can be rendered /// into expression syntax nodes. - mutating func append( + mutating func append( label: String?, arrayLiteral: [T] - ) where T: ManifestSyntaxRepresentable, T.PreferredSyntax == ExprSyntax { + ) where T.PreferredSyntax == ExprSyntax { var elements: [ArrayElementSyntax] = [] for element in arrayLiteral { elements.append(expression: element.asSyntax()) @@ -402,20 +394,20 @@ extension Array { /// Append an array literal containing elements that can be rendered /// into expression syntax nodes. - mutating func appendIf( + mutating func appendIf( label: String?, arrayLiteral: [T]? - ) where T: ManifestSyntaxRepresentable, T.PreferredSyntax == ExprSyntax { + ) where T.PreferredSyntax == ExprSyntax { guard let arrayLiteral else { return } append(label: label, arrayLiteral: arrayLiteral) } /// Append an array literal containing elements that can be rendered /// into expression syntax nodes, but only if it's not empty. - mutating func appendIfNonEmpty( + mutating func appendIfNonEmpty( label: String?, arrayLiteral: [T] - ) where T: ManifestSyntaxRepresentable, T.PreferredSyntax == ExprSyntax { + ) where T.PreferredSyntax == ExprSyntax { if arrayLiteral.isEmpty { return } append(label: label, arrayLiteral: arrayLiteral) @@ -453,7 +445,7 @@ fileprivate extension SyntaxProtocol { } extension FunctionCallExprSyntax { - /// Produce source edits that will add the given new element to the + /// Perform source edits that will add the given new element to the /// array for an argument with the given label (if there is one), or /// introduce a new argument with an array literal containing only the /// new element. @@ -461,14 +453,14 @@ extension FunctionCallExprSyntax { /// - Parameters: /// - label: The argument label for the argument whose array will be /// added or modified. - /// - trailingLabels: The argument labels that could follow the label, + /// - labelsAfter: The argument labels that could follow the label, /// which helps determine where the argument should be inserted if /// it doesn't exist yet. /// - newElement: The new element. /// - Returns: the function call after making this change. func appendingToArrayArgument( label: String, - trailingLabels: Set, + labelsAfter: Set, newElement: ExprSyntax ) throws -> FunctionCallExprSyntax { // If there is already an argument with this name, append to the array @@ -483,7 +475,7 @@ extension FunctionCallExprSyntax { // Format the element appropriately for the context. let indentation = Trivia( - pieces: arg.leadingTrivia.filter { $0.isSpaceOrTab } + pieces: arg.leadingTrivia.filter(\.isSpaceOrTab) ) let format = BasicFormat( indentationWidth: [defaultIndent], @@ -504,13 +496,13 @@ extension FunctionCallExprSyntax { // Insert the new argument at the appropriate place in the call. let insertionPos = arguments.findArgumentInsertionPosition( - labelsAfter: trailingLabels + labelsAfter: labelsAfter ) let newArguments = arguments.insertingArgument( at: insertionPos ) { (leadingTrivia, trailingComma) in // Format the element appropriately for the context. - let indentation = Trivia(pieces: leadingTrivia.filter { $0.isSpaceOrTab }) + let indentation = Trivia(pieces: leadingTrivia.filter(\.isSpaceOrTab)) let format = BasicFormat( indentationWidth: [defaultIndent], initialIndentation: indentation.appending(defaultIndent) @@ -521,9 +513,7 @@ extension FunctionCallExprSyntax { // Form the array. let newArgument = ArrayExprSyntax( leadingTrivia: .space, - leftSquare: .leftSquareToken( - trailingTrivia: .newline - ), + leftSquare: .leftSquareToken(trailingTrivia: .newline), elements: ArrayElementListSyntax( [ ArrayElementSyntax( @@ -532,9 +522,7 @@ extension FunctionCallExprSyntax { ) ] ), - rightSquare: .rightSquareToken( - leadingTrivia: leadingTrivia - ) + rightSquare: .rightSquareToken(leadingTrivia: leadingTrivia) ) // Create the labeled argument for the array. From 9370e43c6845731a7a3aa7d76ee990feac4258d0 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 5 Aug 2025 00:19:42 -0700 Subject: [PATCH 15/30] [SwiftRefactor] Replace custom `XCTAssertThrows` with `XCTAssertThrowsError` Removes `XCTAssertThrows` in favor of XCTest standard way of testing throwing functions. --- .../PackageManifest/ManifestEditError.swift | 6 +-- .../PackageManifest/SyntaxEditUtils.swift | 3 +- .../SwiftRefactorTest/ManifestEditTests.swift | 42 ++++++------------- 3 files changed, 16 insertions(+), 35 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift index 17ad41df4ae..92d354ebc14 100644 --- a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift @@ -14,11 +14,11 @@ import SwiftSyntax /// An error describing problems that can occur when attempting to edit a /// package manifest programattically. -public enum ManifestEditError: Error { +public enum ManifestEditError: Error, Equatable { case cannotFindPackage case cannotFindTargets case cannotFindTarget(targetName: String) - case cannotFindArrayLiteralArgument(argumentName: String, node: Syntax) + case cannotFindArrayLiteralArgument(argumentName: String) } extension ManifestEditError: CustomStringConvertible { @@ -30,7 +30,7 @@ extension ManifestEditError: CustomStringConvertible { return "unable to find package targets in manifest" case .cannotFindTarget(targetName: let name): return "unable to find target named '\(name)' in package" - case .cannotFindArrayLiteralArgument(argumentName: let name, node: _): + case .cannotFindArrayLiteralArgument(argumentName: let name): return "unable to find array literal for '\(name)' argument" } } diff --git a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift index 2c3c4872a85..d95d01f5825 100644 --- a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift +++ b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift @@ -468,8 +468,7 @@ extension FunctionCallExprSyntax { if let arg = findArgument(labeled: label) { guard let argArray = arg.expression.findArrayArgument() else { throw ManifestEditError.cannotFindArrayLiteralArgument( - argumentName: label, - node: Syntax(arg.expression) + argumentName: label ) } diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 234ce2aade5..8c226585fed 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -244,7 +244,7 @@ final class ManifestEditTests: XCTestCase { } func testAddPackageDependencyErrors() { - XCTAssertThrows( + XCTAssertThrowsError( try AddPackageDependency.manifestRefactor( syntax: """ // swift-tools-version: 5.5 @@ -254,15 +254,14 @@ final class ManifestEditTests: XCTestCase { """, in: .init(dependency: Self.swiftSystemPackageDependency) ) - ) { (error: ManifestEditError) in - if case .cannotFindPackage = error { - return true - } else { - return false - } + ) { error in + XCTAssertEqual( + error as? ManifestEditError, + .cannotFindPackage + ) } - XCTAssertThrows( + XCTAssertThrowsError( try AddPackageDependency.manifestRefactor( syntax: """ // swift-tools-version: 5.5 @@ -273,12 +272,11 @@ final class ManifestEditTests: XCTestCase { """, in: .init(dependency: Self.swiftSystemPackageDependency) ) - ) { (error: ManifestEditError) in - if case .cannotFindArrayLiteralArgument(argumentName: "dependencies", node: _) = error { - return true - } else { - return false - } + ) { (error: any Error) in + XCTAssertEqual( + error as? ManifestEditError, + .cannotFindArrayLiteralArgument(argumentName: "dependencies") + ) } } @@ -710,19 +708,3 @@ func assertManifestRefactor( "didn't get all of the auxiliary files we expected" ) } - -func XCTAssertThrows( - _ expression: @autoclosure () throws -> Ignore, - file: StaticString = #filePath, - line: UInt = #line, - _ errorHandler: (T) -> Bool -) { - do { - let result = try expression() - XCTFail("body completed successfully: \(result)", file: file, line: line) - } catch let error as T { - XCTAssertTrue(errorHandler(error), "Error handler returned false", file: file, line: line) - } catch { - XCTFail("unexpected error thrown: \(error)", file: file, line: line) - } -} From 902b53ef4a676688fede0a1a78e05528da4a9a47 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 5 Aug 2025 23:16:00 -0700 Subject: [PATCH 16/30] [SwiftRefactor] Fix handling of leading trivia for new elements/arguments When inserting a new element to an array or new argument to a call let's only copy the indentation of the previous element instead of whole leading trivia that includes comments. --- .../PackageManifest/SyntaxEditUtils.swift | 17 +++- .../SwiftRefactorTest/ManifestEditTests.swift | 95 ++++++++++++++++++- 2 files changed, 106 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift index d95d01f5825..f620a7ce63c 100644 --- a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift +++ b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift @@ -135,9 +135,12 @@ extension LabeledExprListSyntax { if position > startIndex { let priorArgument = arguments[positionIdx - 1] - // Our leading trivia will be based on the prior argument's leading - // trivia. - leadingTrivia = priorArgument.leadingTrivia + // Our indentation will be based on the prior argument's. + if priorArgument.leadingTrivia.hasNewlines { + leadingTrivia = .newline + (priorArgument.firstToken(viewMode: .sourceAccurate)?.indentationOfLine ?? Trivia()) + } else { + leadingTrivia = priorArgument.leadingTrivia.indentation(isOnNewline: false) ?? Trivia() + } // If the prior argument is missing a trailing comma, add one. if priorArgument.trailingComma == nil { @@ -221,9 +224,13 @@ extension ArrayExprSyntax { let trailingTrivia: Trivia let leftSquareTrailingTrivia: Trivia if let last = elements.last { - // The leading trivia of the new element should match that of the + // The leading indentation of the new element should match that of the // last element. - leadingTrivia = last.leadingTrivia.onlyLastLine + if last.leadingTrivia.hasNewlines { + leadingTrivia = .newline + (last.firstToken(viewMode: .sourceAccurate)?.indentationOfLine ?? Trivia()) + } else { + leadingTrivia = last.leadingTrivia.indentation(isOnNewline: false) ?? Trivia() + } // Add a trailing comma to the last element if it isn't already // there. diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 8c226585fed..c1729a547e8 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -97,6 +97,72 @@ final class ManifestEditTests: XCTestCase { ) ) ) + + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + /* test */ .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1") + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + /* test */ .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1"), + .package(url: "https://github.com/apple/swift-system.git", exact: "510.0.0"), + ] + ) + """, + provider: AddPackageDependency.self, + context: .init( + dependency: .sourceControl( + .init( + identity: PackageIdentity("swift-system"), + location: .remote(Self.swiftSystemURL), + requirement: .exact(SemanticVersion("510.0.0")) + ) + ) + ) + ) + + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + /* test */ + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1") + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + /* test */ + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1"), + .package(url: "https://github.com/apple/swift-system.git", exact: "510.0.0"), + ] + ) + """, + provider: AddPackageDependency.self, + context: .init( + dependency: .sourceControl( + .init( + identity: PackageIdentity("swift-system"), + location: .remote(Self.swiftSystemURL), + requirement: .exact(SemanticVersion("510.0.0")) + ) + ) + ) + ) } func testAddPackageDependencyExistingAppended() throws { @@ -160,6 +226,33 @@ final class ManifestEditTests: XCTestCase { ) ) ) + + try assertManifestRefactor( + """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ /*test*/ .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1") ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ /*test*/ .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.1"), .package(url: "https://github.com/apple/swift-system.git", from: "510.0.0"),] + ) + """, + provider: AddPackageDependency.self, + context: .init( + dependency: .sourceControl( + .init( + identity: PackageIdentity("swift-system"), + location: .remote(Self.swiftSystemURL), + requirement: .rangeFrom(SemanticVersion("510.0.0")) + ) + ) + ) + ) } func testAddPackageDependencyExistingEmpty() throws { @@ -448,7 +541,7 @@ final class ManifestEditTests: XCTestCase { .target(name: "TargetLib"), .byName(name: "MyLib"), ] - ), + ) ) ) } From 95190dc2a0c622c1e230f0ffab7ff3d8e92914fb Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 6 Aug 2025 00:20:19 -0700 Subject: [PATCH 17/30] [Tests] Add more information to unexpected aux files failure to diagnose Windows failures --- Tests/SwiftRefactorTest/ManifestEditTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index c1729a547e8..9bd5636e492 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -783,7 +783,7 @@ func assertManifestRefactor( // Check all of the auxiliary sources. for (auxSourcePath, auxSourceSyntax) in edits.auxiliaryFiles { guard let expectedSyntax = expectedAuxiliarySources[auxSourcePath] else { - XCTFail("unexpected auxiliary source file \(auxSourcePath)") + XCTFail("unexpected auxiliary source file '\(auxSourcePath)' in \(expectedAuxiliarySources)") return } From de9afa3420f3c26d0f87709d7ec56f8a60bbfa43 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 6 Aug 2025 10:31:29 -0700 Subject: [PATCH 18/30] [SwiftRefactor] PackageManifest: Rename `Target` to `PackageTarget` --- Sources/SwiftRefactor/CMakeLists.txt | 2 +- .../PackageManifest/AddPackageTarget.swift | 14 +++++++------- .../PackageManifest/AddPluginUsage.swift | 4 ++-- .../PackageManifest/AddTargetDependency.swift | 6 +++--- .../{Target.swift => PackageTarget.swift} | 8 ++++---- Tests/SwiftRefactorTest/ManifestEditTests.swift | 10 +++++----- 6 files changed, 22 insertions(+), 22 deletions(-) rename Sources/SwiftRefactor/PackageManifest/{Target.swift => PackageTarget.swift} (94%) diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index f9e6667a322..43b6dae44ed 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -35,13 +35,13 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/PackageDependency.swift PackageManifest/PackageEdit.swift PackageManifest/PackageIdentity.swift + PackageManifest/PackageTarget.swift PackageManifest/ProductDescription.swift PackageManifest/RelativePath.swift PackageManifest/SemanticVersion.swift PackageManifest/SourceControlURL.swift PackageManifest/StringUtils.swift PackageManifest/SyntaxEditUtils.swift - PackageManifest/Target.swift ) target_link_swift_syntax_libraries(SwiftRefactor PUBLIC diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index ddf07ffe0ef..8740b6f8ec9 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -17,10 +17,10 @@ import SwiftSyntaxBuilder /// Add a target to a manifest's source code. public struct AddPackageTarget: ManifestEditRefactoringProvider { public struct Context { - public let target: Target + public let target: PackageTarget public let configuration: Configuration - public init(target: Target, configuration: Configuration = .init()) { + public init(target: PackageTarget, configuration: Configuration = .init()) { self.target = target self.configuration = configuration } @@ -187,7 +187,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { /// source files. fileprivate static func addPrimarySourceFile( outerPath: RelativePath, - target: Target, + target: PackageTarget, configuration: Configuration, to auxiliaryFiles: inout AuxiliaryFiles ) { @@ -300,7 +300,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { /// for a macro target. fileprivate static func addProvidedMacrosSourceFile( outerPath: RelativePath, - target: Target, + target: PackageTarget, to auxiliaryFiles: inout AuxiliaryFiles ) { auxiliaryFiles.addSourceFile( @@ -321,7 +321,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { } } -fileprivate extension Target.Dependency { +fileprivate extension PackageTarget.Dependency { /// Retrieve the name of the dependency var name: String { switch self { @@ -349,7 +349,7 @@ fileprivate extension AuxiliaryFiles { /// The set of dependencies we need to introduce to a newly-created macro /// target. -fileprivate let macroTargetDependencies: [Target.Dependency] = [ +fileprivate let macroTargetDependencies: [PackageTarget.Dependency] = [ .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), ] @@ -375,7 +375,7 @@ fileprivate extension PackageDependency { } } -fileprivate extension Target { +fileprivate extension PackageTarget { var sanitizedName: String { name .mangledToC99ExtendedIdentifier() diff --git a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift index 80edc0b34a8..5cd20f25b92 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift @@ -19,9 +19,9 @@ import SwiftSyntaxBuilder public struct AddPluginUsage: ManifestEditRefactoringProvider { public struct Context { public let targetName: String - public let pluginUsage: Target.PluginUsage + public let pluginUsage: PackageTarget.PluginUsage - public init(targetName: String, pluginUsage: Target.PluginUsage) { + public init(targetName: String, pluginUsage: PackageTarget.PluginUsage) { self.targetName = targetName self.pluginUsage = pluginUsage } diff --git a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift index 0a1910b9bf5..3dbd17f4c48 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift @@ -18,12 +18,12 @@ import SwiftSyntaxBuilder public struct AddTargetDependency: ManifestEditRefactoringProvider { public struct Context { /// The dependency to add. - public var dependency: Target.Dependency + public var dependency: PackageTarget.Dependency /// The name of the target to which the dependency will be added. public var targetName: String - public init(dependency: Target.Dependency, targetName: String) { + public init(dependency: PackageTarget.Dependency, targetName: String) { self.dependency = dependency self.targetName = targetName } @@ -78,7 +78,7 @@ public struct AddTargetDependency: ManifestEditRefactoringProvider { /// Implementation of adding a target dependency to an existing call. static func addTargetDependencyLocal( - _ dependency: Target.Dependency, + _ dependency: PackageTarget.Dependency, to targetCall: FunctionCallExprSyntax ) throws -> FunctionCallExprSyntax { try targetCall.appendingToArrayArgument( diff --git a/Sources/SwiftRefactor/PackageManifest/Target.swift b/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift similarity index 94% rename from Sources/SwiftRefactor/PackageManifest/Target.swift rename to Sources/SwiftRefactor/PackageManifest/PackageTarget.swift index 2da65957b09..c1ef821c9d3 100644 --- a/Sources/SwiftRefactor/PackageManifest/Target.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift @@ -14,7 +14,7 @@ import SwiftSyntax /// Syntactic wrapper type that describes a target for refactoring /// purposes but does not interpret its contents. -public struct Target { +public struct PackageTarget { public let name: String /// The type of target. @@ -71,7 +71,7 @@ public struct Target { } } -extension Target: ManifestSyntaxRepresentable { +extension PackageTarget: ManifestSyntaxRepresentable { /// The function name in the package manifest. private var functionName: String { switch type { @@ -111,7 +111,7 @@ extension Target: ManifestSyntaxRepresentable { } } -extension Target.Dependency: ManifestSyntaxRepresentable { +extension PackageTarget.Dependency: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { switch self { case .byName(name: let name): @@ -129,7 +129,7 @@ extension Target.Dependency: ManifestSyntaxRepresentable { } } -extension Target.PluginUsage: ManifestSyntaxRepresentable { +extension PackageTarget.PluginUsage: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { switch self { case .plugin(name: let name, package: nil): diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 9bd5636e492..f4a216833f8 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -436,7 +436,7 @@ final class ManifestEditTests: XCTestCase { ], provider: AddPackageTarget.self, context: .init( - target: Target(name: "MyLib") + target: PackageTarget(name: "MyLib") ) ) } @@ -475,7 +475,7 @@ final class ManifestEditTests: XCTestCase { ], provider: AddPackageTarget.self, context: .init( - target: Target( + target: PackageTarget( name: "MyLib", dependencies: [ .byName(name: "OtherLib"), @@ -533,7 +533,7 @@ final class ManifestEditTests: XCTestCase { ], provider: AddPackageTarget.self, context: .init( - target: Target( + target: PackageTarget( name: "MyProgram target-name", type: .executable, dependencies: [ @@ -604,7 +604,7 @@ final class ManifestEditTests: XCTestCase { ], provider: AddPackageTarget.self, context: .init( - target: Target( + target: PackageTarget( name: "MyMacro target-name", type: .macro ) @@ -644,7 +644,7 @@ final class ManifestEditTests: XCTestCase { ], provider: AddPackageTarget.self, context: .init( - target: Target( + target: PackageTarget( name: "MyTest target-name", type: .test ), From cd7758ec1a7126c5e87e9847806b3b5538ef683d Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 6 Aug 2025 14:17:09 -0700 Subject: [PATCH 19/30] [SwiftRefactor] PackageManifest: Replace custom types with a simple `String` All of these types are simple placeholders already with one exception `RelativePath` which used to differentiate path separators on Windows and other platforms, which is not strictly necessary any longer. --- Sources/SwiftRefactor/CMakeLists.txt | 4 -- .../PackageManifest/AbsolutePath.swift | 21 ---------- .../PackageManifest/AddPackageTarget.swift | 24 +++++------- .../PackageManifest/PackageDependency.swift | 14 +++---- .../PackageManifest/PackageEdit.swift | 2 +- .../PackageManifest/PackageIdentity.swift | 21 ---------- .../PackageManifest/RelativePath.swift | 35 ----------------- .../PackageManifest/SourceControlURL.swift | 21 ---------- .../SwiftRefactorTest/ManifestEditTests.swift | 38 +++++++++---------- 9 files changed, 36 insertions(+), 144 deletions(-) delete mode 100644 Sources/SwiftRefactor/PackageManifest/AbsolutePath.swift delete mode 100644 Sources/SwiftRefactor/PackageManifest/PackageIdentity.swift delete mode 100644 Sources/SwiftRefactor/PackageManifest/RelativePath.swift delete mode 100644 Sources/SwiftRefactor/PackageManifest/SourceControlURL.swift diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index 43b6dae44ed..df869e48af4 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -23,7 +23,6 @@ add_swift_syntax_library(SwiftRefactor RemoveSeparatorsFromIntegerLiteral.swift SyntaxUtils.swift - PackageManifest/AbsolutePath.swift PackageManifest/AddPackageDependency.swift PackageManifest/AddPackageTarget.swift PackageManifest/AddPluginUsage.swift @@ -34,12 +33,9 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/ManifestSyntaxRepresentable.swift PackageManifest/PackageDependency.swift PackageManifest/PackageEdit.swift - PackageManifest/PackageIdentity.swift PackageManifest/PackageTarget.swift PackageManifest/ProductDescription.swift - PackageManifest/RelativePath.swift PackageManifest/SemanticVersion.swift - PackageManifest/SourceControlURL.swift PackageManifest/StringUtils.swift PackageManifest/SyntaxEditUtils.swift ) diff --git a/Sources/SwiftRefactor/PackageManifest/AbsolutePath.swift b/Sources/SwiftRefactor/PackageManifest/AbsolutePath.swift deleted file mode 100644 index d6ed26d345e..00000000000 --- a/Sources/SwiftRefactor/PackageManifest/AbsolutePath.swift +++ /dev/null @@ -1,21 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -/// Syntactic wrapper type that describes an absolute path for refactoring -/// purposes but does not interpret its contents. -public struct AbsolutePath: CustomStringConvertible, Equatable, Hashable, Sendable { - public private(set) var description: String - - public init(_ description: String) { - self.description = description - } -} diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index 8740b6f8ec9..85356b43fef 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -121,7 +121,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { ) } - let outerPath = RelativePath(outerDirectory) + let outerPath = outerDirectory /// The set of auxiliary files this refactoring will create. var auxiliaryFiles: AuxiliaryFiles = [] @@ -186,14 +186,12 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { /// Add the primary source file for a target to the list of auxiliary /// source files. fileprivate static func addPrimarySourceFile( - outerPath: RelativePath, + outerPath: String, target: PackageTarget, configuration: Configuration, to auxiliaryFiles: inout AuxiliaryFiles ) { - let sourceFilePath = outerPath.appending( - components: [target.name, "\(target.name).swift"] - ) + let sourceFilePath = "\(outerPath)/\(target.name)/\(target.name).swift" // Introduce imports for each of the dependencies that were specified. var importModuleNames = target.dependencies.map { @@ -299,14 +297,12 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { /// Add a file that introduces the main entrypoint and provided macros /// for a macro target. fileprivate static func addProvidedMacrosSourceFile( - outerPath: RelativePath, + outerPath: String, target: PackageTarget, to auxiliaryFiles: inout AuxiliaryFiles ) { auxiliaryFiles.addSourceFile( - path: outerPath.appending( - components: [target.name, "ProvidedMacros.swift"] - ), + path: "\(outerPath)/\(target.name)/ProvidedMacros.swift", sourceCode: """ import SwiftCompilerPlugin @@ -335,12 +331,12 @@ fileprivate extension PackageTarget.Dependency { /// The array of auxiliary files that can be added by a package editing /// operation. -fileprivate typealias AuxiliaryFiles = [(RelativePath, SourceFileSyntax)] +fileprivate typealias AuxiliaryFiles = [(String, SourceFileSyntax)] fileprivate extension AuxiliaryFiles { /// Add a source file to the list of auxiliary files. mutating func addSourceFile( - path: RelativePath, + path: String, sourceCode: SourceFileSyntax ) { self.append((path, sourceCode)) @@ -357,8 +353,8 @@ fileprivate let macroTargetDependencies: [PackageTarget.Dependency] = [ /// The package dependency for swift-syntax, for use in macros. fileprivate extension PackageDependency { /// Source control URL for the swift-syntax package. - static var swiftSyntaxURL: SourceControlURL { - .init("https://github.com/swiftlang/swift-syntax.git") + static var swiftSyntaxURL: String { + "https://github.com/swiftlang/swift-syntax.git" } /// Package dependency on the swift-syntax package. @@ -367,7 +363,7 @@ fileprivate extension PackageDependency { ) -> PackageDependency { return .sourceControl( .init( - identity: PackageIdentity("swift-syntax"), + identity: "swift-syntax", location: .remote(swiftSyntaxURL), requirement: .rangeFrom(version) ) diff --git a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift index eb2a62f1944..ce11fdc41e8 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift @@ -22,17 +22,17 @@ public enum PackageDependency: Sendable { case registry(Registry) public struct FileSystem: Sendable { - public let identity: PackageIdentity + public let identity: String public let nameForTargetDependencyResolutionOnly: String? - public let path: AbsolutePath + public let path: String } public struct SourceControl: Sendable { - public let identity: PackageIdentity + public let identity: String public let location: Location public let requirement: Requirement - public init(identity: PackageIdentity, location: Location, requirement: Requirement) { + public init(identity: String, location: Location, requirement: Requirement) { self.identity = identity self.location = location self.requirement = requirement @@ -47,13 +47,13 @@ public enum PackageDependency: Sendable { } public enum Location: Sendable { - case local(AbsolutePath) - case remote(SourceControlURL) + case local(String) + case remote(String) } } public struct Registry: Sendable { - public let identity: PackageIdentity + public let identity: String public let requirement: Requirement /// The dependency requirement. diff --git a/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift b/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift index 875a8fc66aa..5f93b3d40db 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift @@ -19,5 +19,5 @@ public struct PackageEdit { public var manifestEdits: [SourceEdit] = [] /// Auxiliary files to write. - public var auxiliaryFiles: [(RelativePath, SourceFileSyntax)] = [] + public var auxiliaryFiles: [(String, SourceFileSyntax)] = [] } diff --git a/Sources/SwiftRefactor/PackageManifest/PackageIdentity.swift b/Sources/SwiftRefactor/PackageManifest/PackageIdentity.swift deleted file mode 100644 index d51561b33f9..00000000000 --- a/Sources/SwiftRefactor/PackageManifest/PackageIdentity.swift +++ /dev/null @@ -1,21 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift 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 http://swift.org/LICENSE.txt for license information -// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -/// Describes a package identity for refactoring purposes. This is a syntactic -/// subset of the full package manifest's notion of package identity. -public struct PackageIdentity: CustomStringConvertible, Equatable, Hashable, Sendable { - public private(set) var description: String - - public init(_ description: String) { - self.description = description - } -} diff --git a/Sources/SwiftRefactor/PackageManifest/RelativePath.swift b/Sources/SwiftRefactor/PackageManifest/RelativePath.swift deleted file mode 100644 index 1eb2a46e4e3..00000000000 --- a/Sources/SwiftRefactor/PackageManifest/RelativePath.swift +++ /dev/null @@ -1,35 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift 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 http://swift.org/LICENSE.txt for license information -// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -/// Syntactic wrapper type that describes a relative path for refactoring -/// purposes but does not interpret its contents. -public struct RelativePath: CustomStringConvertible, Equatable, Hashable, Sendable { - public private(set) var description: String - - public init(_ description: String) { - self.description = description - } - - #if os(Windows) - static let pathSeparator: Character = "\\" - #else - static let pathSeparator: Character = "/" - #endif - - public func appending(_ component: String) -> Self { - Self(description + String(RelativePath.pathSeparator) + component) - } - - public func appending(components: [String]) -> Self { - appending(components.joined(separator: String(RelativePath.pathSeparator))) - } -} diff --git a/Sources/SwiftRefactor/PackageManifest/SourceControlURL.swift b/Sources/SwiftRefactor/PackageManifest/SourceControlURL.swift deleted file mode 100644 index 70b31ae9109..00000000000 --- a/Sources/SwiftRefactor/PackageManifest/SourceControlURL.swift +++ /dev/null @@ -1,21 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift 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 http://swift.org/LICENSE.txt for license information -// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -/// Syntactic wrapper type that describes a source control URL for refactoring -/// purposes but does not interpret its contents. -public struct SourceControlURL: CustomStringConvertible, Equatable, Hashable, Sendable { - public private(set) var description: String - - public init(_ description: String) { - self.description = description - } -} diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index f4a216833f8..01a9375323a 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -19,13 +19,11 @@ import XCTest import _SwiftSyntaxTestSupport final class ManifestEditTests: XCTestCase { - static let swiftSystemURL: SourceControlURL = SourceControlURL( - "https://github.com/apple/swift-system.git" - ) + static let swiftSystemURL: String = "https://github.com/apple/swift-system.git" static let swiftSystemPackageDependency: PackageDependency = .sourceControl( .init( - identity: PackageIdentity("swift-system"), + identity: "swift-system", location: .remote(swiftSystemURL), requirement: .branch("main") ) @@ -56,7 +54,7 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: PackageIdentity("swift-system"), + identity: "swift-system", location: .remote(Self.swiftSystemURL), requirement: .branch("main") ) @@ -90,7 +88,7 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: PackageIdentity("swift-system"), + identity: "swift-system", location: .remote(Self.swiftSystemURL), requirement: .exact(SemanticVersion("510.0.0")) ) @@ -122,7 +120,7 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: PackageIdentity("swift-system"), + identity: "swift-system", location: .remote(Self.swiftSystemURL), requirement: .exact(SemanticVersion("510.0.0")) ) @@ -156,7 +154,7 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: PackageIdentity("swift-system"), + identity: "swift-system", location: .remote(Self.swiftSystemURL), requirement: .exact(SemanticVersion("510.0.0")) ) @@ -190,7 +188,7 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: PackageIdentity("swift-system"), + identity: "swift-system", location: .remote(Self.swiftSystemURL), requirement: .rangeFrom(SemanticVersion("510.0.0")) ) @@ -219,7 +217,7 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: PackageIdentity("swift-system"), + identity: "swift-system", location: .remote(Self.swiftSystemURL), requirement: .rangeFrom(SemanticVersion("510.0.0")) ) @@ -246,7 +244,7 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: PackageIdentity("swift-system"), + identity: "swift-system", location: .remote(Self.swiftSystemURL), requirement: .rangeFrom(SemanticVersion("510.0.0")) ) @@ -277,7 +275,7 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: PackageIdentity("swift-system"), + identity: "swift-system", location: .remote(Self.swiftSystemURL), requirement: .range(lowerBound: SemanticVersion("508.0.0"), upperBound: SemanticVersion("510.0.0")) ) @@ -430,7 +428,7 @@ final class ManifestEditTests: XCTestCase { ) """, expectedAuxiliarySources: [ - RelativePath("Sources/MyLib/MyLib.swift"): """ + "Sources/MyLib/MyLib.swift": """ """ ], @@ -466,7 +464,7 @@ final class ManifestEditTests: XCTestCase { ) """, expectedAuxiliarySources: [ - RelativePath("Sources/MyLib/MyLib.swift"): """ + "Sources/MyLib/MyLib.swift": """ import OtherLib import SwiftSyntax import TargetLib @@ -518,7 +516,7 @@ final class ManifestEditTests: XCTestCase { ) """, expectedAuxiliarySources: [ - RelativePath("Sources/MyProgram target-name/MyProgram target-name.swift"): """ + "Sources/MyProgram target-name/MyProgram target-name.swift": """ import MyLib import SwiftSyntax import TargetLib @@ -578,7 +576,7 @@ final class ManifestEditTests: XCTestCase { ) """, expectedAuxiliarySources: [ - RelativePath("Sources/MyMacro target-name/MyMacro target-name.swift"): """ + "Sources/MyMacro target-name/MyMacro target-name.swift": """ import SwiftCompilerPlugin import SwiftSyntaxMacros @@ -591,7 +589,7 @@ final class ManifestEditTests: XCTestCase { /// @attached(member) macro --> MemberMacro } """, - RelativePath("Sources/MyMacro target-name/ProvidedMacros.swift"): """ + "Sources/MyMacro target-name/ProvidedMacros.swift": """ import SwiftCompilerPlugin @main @@ -630,7 +628,7 @@ final class ManifestEditTests: XCTestCase { ) """, expectedAuxiliarySources: [ - RelativePath("Tests/MyTest target-name/MyTest target-name.swift"): """ + "Tests/MyTest target-name/MyTest target-name.swift": """ import Testing @Suite @@ -738,7 +736,7 @@ final class ManifestEditTests: XCTestCase { func assertManifestRefactor( _ originalManifest: SourceFileSyntax, expectedManifest: SourceFileSyntax, - expectedAuxiliarySources: [RelativePath: SourceFileSyntax] = [:], + expectedAuxiliarySources: [String: SourceFileSyntax] = [:], provider: Provider.Type, context: Provider.Context, file: StaticString = #filePath, @@ -761,7 +759,7 @@ func assertManifestRefactor( func assertManifestRefactor( _ originalManifest: SourceFileSyntax, expectedManifest: SourceFileSyntax, - expectedAuxiliarySources: [RelativePath: SourceFileSyntax] = [:], + expectedAuxiliarySources: [String: SourceFileSyntax] = [:], file: StaticString = #filePath, line: UInt = #line, operation: (SourceFileSyntax) throws -> PackageEdit From b44a72fcb091df0dfd36ccc421cc63eabd2190b3 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 6 Aug 2025 15:31:44 -0700 Subject: [PATCH 20/30] [SwiftRefactor] PackageManifest: Replace `SemanticVersion` with a `String` This removes final stub type that could simply be a string and instead of hard-coding a version of swift-syntax library for new dependencies, let's use a placeholder. --- Sources/SwiftRefactor/CMakeLists.txt | 1 - .../PackageManifest/AddPackageTarget.swift | 21 ++++---------- .../PackageManifest/PackageDependency.swift | 12 ++++---- .../PackageManifest/SemanticVersion.swift | 29 ------------------- .../SwiftRefactorTest/ManifestEditTests.swift | 16 +++++----- 5 files changed, 19 insertions(+), 60 deletions(-) delete mode 100644 Sources/SwiftRefactor/PackageManifest/SemanticVersion.swift diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index df869e48af4..53bb41fc527 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -35,7 +35,6 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/PackageEdit.swift PackageManifest/PackageTarget.swift PackageManifest/ProductDescription.swift - PackageManifest/SemanticVersion.swift PackageManifest/StringUtils.swift PackageManifest/SyntaxEditUtils.swift ) diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index 85356b43fef..f6864d72318 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -50,7 +50,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { case swiftTesting = "swift-testing" /// The default testing library to use. - public static let `default`: TestHarness = .xctest + public static let `default`: TestHarness = .swiftTesting } /// Additional configuration information to guide the package editing @@ -59,17 +59,10 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { /// The test harness to use. public var testHarness: TestHarness - public let swiftSyntaxVersion: SemanticVersion - public let swiftTestingVersion: SemanticVersion - public init( - testHarness: TestHarness = .default, - swiftSyntaxVersion: SemanticVersion = SemanticVersion("600.0.0-latest"), - swiftTestingVersion: SemanticVersion = SemanticVersion("0.8.0") + testHarness: TestHarness = .default ) { self.testHarness = testHarness - self.swiftSyntaxVersion = swiftSyntaxVersion - self.swiftTestingVersion = swiftTestingVersion } } @@ -148,9 +141,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { newPackageCall = try AddPackageDependency .addPackageDependencyLocal( - .swiftSyntax( - version: configuration.swiftSyntaxVersion - ), + .swiftSyntax(from: "<#version#>"), to: newPackageCall ) @@ -357,10 +348,8 @@ fileprivate extension PackageDependency { "https://github.com/swiftlang/swift-syntax.git" } - /// Package dependency on the swift-syntax package. - static func swiftSyntax( - version: SemanticVersion - ) -> PackageDependency { + /// Package dependency on the swift-syntax package starting from a partial version. + static func swiftSyntax(from version: String) -> PackageDependency { return .sourceControl( .init( identity: "swift-syntax", diff --git a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift index ce11fdc41e8..0b0812693b9 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift @@ -39,9 +39,9 @@ public enum PackageDependency: Sendable { } public enum Requirement: Sendable { - case exact(SemanticVersion) - case rangeFrom(SemanticVersion) - case range(lowerBound: SemanticVersion, upperBound: SemanticVersion) + case exact(String) + case rangeFrom(String) + case range(lowerBound: String, upperBound: String) case revision(String) case branch(String) } @@ -58,9 +58,9 @@ public enum PackageDependency: Sendable { /// The dependency requirement. public enum Requirement: Sendable { - case exact(SemanticVersion) - case rangeFrom(SemanticVersion) - case range(lowerBound: SemanticVersion, upperBound: SemanticVersion) + case exact(String) + case rangeFrom(String) + case range(lowerBound: String, upperBound: String) } } } diff --git a/Sources/SwiftRefactor/PackageManifest/SemanticVersion.swift b/Sources/SwiftRefactor/PackageManifest/SemanticVersion.swift deleted file mode 100644 index bcd819207c4..00000000000 --- a/Sources/SwiftRefactor/PackageManifest/SemanticVersion.swift +++ /dev/null @@ -1,29 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift 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 http://swift.org/LICENSE.txt for license information -// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -/// Syntactic wrapper type that describes a semantic version for refactoring -/// purposes but does not interpret its contents. -public struct SemanticVersion: CustomStringConvertible, Equatable, Hashable, Sendable { - public private(set) var description: String - - public init(_ description: String) { - self.description = description - } -} - -extension SemanticVersion: ManifestSyntaxRepresentable { - func asSyntax() -> ExprSyntax { - "\(literal: description)" - } -} diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 01a9375323a..19bfd62c1c0 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -90,7 +90,7 @@ final class ManifestEditTests: XCTestCase { .init( identity: "swift-system", location: .remote(Self.swiftSystemURL), - requirement: .exact(SemanticVersion("510.0.0")) + requirement: .exact("510.0.0") ) ) ) @@ -122,7 +122,7 @@ final class ManifestEditTests: XCTestCase { .init( identity: "swift-system", location: .remote(Self.swiftSystemURL), - requirement: .exact(SemanticVersion("510.0.0")) + requirement: .exact("510.0.0") ) ) ) @@ -156,7 +156,7 @@ final class ManifestEditTests: XCTestCase { .init( identity: "swift-system", location: .remote(Self.swiftSystemURL), - requirement: .exact(SemanticVersion("510.0.0")) + requirement: .exact("510.0.0") ) ) ) @@ -190,7 +190,7 @@ final class ManifestEditTests: XCTestCase { .init( identity: "swift-system", location: .remote(Self.swiftSystemURL), - requirement: .rangeFrom(SemanticVersion("510.0.0")) + requirement: .rangeFrom("510.0.0") ) ) ) @@ -219,7 +219,7 @@ final class ManifestEditTests: XCTestCase { .init( identity: "swift-system", location: .remote(Self.swiftSystemURL), - requirement: .rangeFrom(SemanticVersion("510.0.0")) + requirement: .rangeFrom("510.0.0") ) ) ) @@ -246,7 +246,7 @@ final class ManifestEditTests: XCTestCase { .init( identity: "swift-system", location: .remote(Self.swiftSystemURL), - requirement: .rangeFrom(SemanticVersion("510.0.0")) + requirement: .rangeFrom("510.0.0") ) ) ) @@ -277,7 +277,7 @@ final class ManifestEditTests: XCTestCase { .init( identity: "swift-system", location: .remote(Self.swiftSystemURL), - requirement: .range(lowerBound: SemanticVersion("508.0.0"), upperBound: SemanticVersion("510.0.0")) + requirement: .range(lowerBound: "508.0.0", upperBound: "510.0.0") ) ) ) @@ -562,7 +562,7 @@ final class ManifestEditTests: XCTestCase { let package = Package( name: "packages", dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0-latest"), + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "<#version#>"), ], targets: [ .macro( From 58a01d3e2dc9b3ff7c4b420af75915f527d5ca42 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 6 Aug 2025 15:36:57 -0700 Subject: [PATCH 21/30] [SwiftRefactor] PackageManifest: Fix formatting for license headers --- .../PackageManifest/AddPackageDependency.swift | 8 ++++---- .../SwiftRefactor/PackageManifest/AddPackageTarget.swift | 8 ++++---- .../SwiftRefactor/PackageManifest/AddPluginUsage.swift | 8 ++++---- Sources/SwiftRefactor/PackageManifest/AddProduct.swift | 8 ++++---- .../PackageManifest/AddTargetDependency.swift | 8 ++++---- .../SwiftRefactor/PackageManifest/ManifestEditError.swift | 8 ++++---- .../PackageManifest/ManifestEditRefactoringProvider.swift | 8 ++++---- .../PackageManifest/ManifestSyntaxRepresentable.swift | 8 ++++---- .../SwiftRefactor/PackageManifest/PackageDependency.swift | 8 ++++---- Sources/SwiftRefactor/PackageManifest/PackageEdit.swift | 8 ++++---- Sources/SwiftRefactor/PackageManifest/PackageTarget.swift | 8 ++++---- .../PackageManifest/ProductDescription.swift | 8 ++++---- Sources/SwiftRefactor/PackageManifest/StringUtils.swift | 2 +- .../SwiftRefactor/PackageManifest/SyntaxEditUtils.swift | 8 ++++---- Tests/SwiftRefactorTest/ManifestEditTests.swift | 2 +- 15 files changed, 54 insertions(+), 54 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift index 7725ca749f1..d401c597c93 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index f6864d72318..73d81781016 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift index 5cd20f25b92..c1623a62b8e 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/AddProduct.swift b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift index 3ed54d37c56..fc6d8601e8d 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddProduct.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift index 3dbd17f4c48..291b9712d72 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift index 92d354ebc14..b9838016aa5 100644 --- a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift index 6e47412ffd3..e34173e666e 100644 --- a/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestSyntaxRepresentable.swift b/Sources/SwiftRefactor/PackageManifest/ManifestSyntaxRepresentable.swift index e9899333fdb..00e35dd00ea 100644 --- a/Sources/SwiftRefactor/PackageManifest/ManifestSyntaxRepresentable.swift +++ b/Sources/SwiftRefactor/PackageManifest/ManifestSyntaxRepresentable.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift index 0b0812693b9..cf6a1bcbb41 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift b/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift index 5f93b3d40db..aa16eab091f 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift index c1ef821c9d3..e9fff2cf307 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift index 640075f6473..7f55382397f 100644 --- a/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift +++ b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2014-2020 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/SwiftRefactor/PackageManifest/StringUtils.swift b/Sources/SwiftRefactor/PackageManifest/StringUtils.swift index ff412739ca8..6f0941ea7c6 100644 --- a/Sources/SwiftRefactor/PackageManifest/StringUtils.swift +++ b/Sources/SwiftRefactor/PackageManifest/StringUtils.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 diff --git a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift index f620a7ce63c..1e31236e4d4 100644 --- a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift +++ b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift @@ -1,12 +1,12 @@ //===----------------------------------------------------------------------===// // -// This source file is part of the Swift open source project +// This source file is part of the Swift.org open source project // -// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 19bfd62c1c0..bbad2820595 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 From 621ae1387528d768675847c1b0c7e6651a9129b2 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 6 Aug 2025 16:09:38 -0700 Subject: [PATCH 22/30] [SwiftRefactor] PackageManifest: Add duplicate dependency checking to `AddPackageDependency` This was introduced by https://github.com/swiftlang/swift-package-manager/pull/8534 --- .../AddPackageDependency.swift | 68 +++++++++++++++++++ .../PackageManifest/ManifestEditError.swift | 6 ++ .../SwiftRefactorTest/ManifestEditTests.swift | 24 +++++++ 3 files changed, 98 insertions(+) diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift index d401c597c93..9089bb5a3ba 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift @@ -47,6 +47,15 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider { throw ManifestEditError.cannotFindPackage } + guard + try !dependencyAlreadyAdded( + dependency, + in: packageCall + ) + else { + return PackageEdit(manifestEdits: []) + } + let newPackageCall = try addPackageDependencyLocal( dependency, to: packageCall @@ -59,6 +68,52 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider { ) } + /// Return `true` if the dependency already exists in the manifest, otherwise return `false`. + /// Throws an error if a dependency already exists with the same id or url, but different arguments. + private static func dependencyAlreadyAdded( + _ dependency: PackageDependency, + in packageCall: FunctionCallExprSyntax + ) throws -> Bool { + let dependencySyntax = dependency.asSyntax() + guard let dependencyFnSyntax = dependencySyntax.as(FunctionCallExprSyntax.self) else { + throw ManifestEditError.cannotFindPackage + } + + guard + let id = dependencyFnSyntax.arguments.first(where: { + $0.label?.text == "url" || $0.label?.text == "id" || $0.label?.text == "path" + }) + else { + throw ManifestEditError.malformedManifest(error: "missing id or url argument in dependency syntax") + } + + if let existingDependencies = packageCall.findArgument(labeled: "dependencies") { + // If we have an existing dependencies array, we need to check if + if let expr = existingDependencies.expression.as(ArrayExprSyntax.self) { + // Iterate through existing dependencies and look for an argument that matches + // either the `id` or `url` argument of the new dependency. + let existingArgument = expr.elements.first { elem in + if let funcExpr = elem.expression.as(FunctionCallExprSyntax.self) { + return funcExpr.arguments.contains { + $0.trimmedDescription == id.trimmedDescription + } + } + return true + } + + if let existingArgument { + let normalizedExistingArgument = existingArgument.detached.with(\.trailingComma, nil) + // This exact dependency already exists, return false to indicate we should do nothing. + if normalizedExistingArgument.trimmedDescription == dependencySyntax.trimmedDescription { + return true + } + throw ManifestEditError.existingDependency(dependencyName: dependency.identifier) + } + } + } + return false + } + /// Implementation of adding a package dependency to an existing call. static func addPackageDependencyLocal( _ dependency: PackageDependency, @@ -71,3 +126,16 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider { ) } } + +fileprivate extension PackageDependency { + var identifier: String { + switch self { + case .sourceControl(let info): + return info.identity + case .fileSystem(let info): + return info.identity + case .registry(let info): + return info.identity + } + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift index b9838016aa5..ce7872ba169 100644 --- a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift @@ -19,6 +19,8 @@ public enum ManifestEditError: Error, Equatable { case cannotFindTargets case cannotFindTarget(targetName: String) case cannotFindArrayLiteralArgument(argumentName: String) + case existingDependency(dependencyName: String) + case malformedManifest(error: String) } extension ManifestEditError: CustomStringConvertible { @@ -32,6 +34,10 @@ extension ManifestEditError: CustomStringConvertible { return "unable to find target named '\(name)' in package" case .cannotFindArrayLiteralArgument(argumentName: let name): return "unable to find array literal for '\(name)' argument" + case .existingDependency(let name): + return "unable to add dependency '\(name)' because it already exists in the list of dependencies" + case .malformedManifest(let error): + return "invalid manifest: \(error)" } } } diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index bbad2820595..56684c28f65 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -163,6 +163,30 @@ final class ManifestEditTests: XCTestCase { ) } + func testAddPackageDependencyDuplicates() throws { + XCTAssertThrowsError( + try AddPackageDependency.manifestRefactor( + syntax: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-system.git", from: "510.0.1") + ] + ) + """, + in: .init(dependency: Self.swiftSystemPackageDependency) + ) + ) { (error: any Error) in + guard let error = error as? ManifestEditError, + case .existingDependency("swift-system") = error + else { + XCTFail("unexpected error thrown: \(error)") + return + } + } + } + func testAddPackageDependencyExistingAppended() throws { try assertManifestRefactor( """ From fb6939cec004ebe8439990319296f27f61743577 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 7 Aug 2025 11:42:38 -0700 Subject: [PATCH 23/30] [SwiftRefactor] PackageManifest: Add refactoring to add introduce swift settings Allows to add `enable{Upcoming, Experimental}Feature`, `swiftLanguageMode`, `strictMemorySafety` as well as custom ones. --- Sources/SwiftRefactor/CMakeLists.txt | 1 + .../PackageManifest/AddSwiftSetting.swift | 162 ++++++++++ .../PackageManifest/ManifestEditError.swift | 3 + .../SwiftRefactorTest/ManifestEditTests.swift | 289 ++++++++++++++++++ 4 files changed, 455 insertions(+) create mode 100644 Sources/SwiftRefactor/PackageManifest/AddSwiftSetting.swift diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index 53bb41fc527..11594aa9f5d 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -27,6 +27,7 @@ add_swift_syntax_library(SwiftRefactor PackageManifest/AddPackageTarget.swift PackageManifest/AddPluginUsage.swift PackageManifest/AddProduct.swift + PackageManifest/AddSwiftSetting.swift PackageManifest/AddTargetDependency.swift PackageManifest/ManifestEditError.swift PackageManifest/ManifestEditRefactoringProvider.swift diff --git a/Sources/SwiftRefactor/PackageManifest/AddSwiftSetting.swift b/Sources/SwiftRefactor/PackageManifest/AddSwiftSetting.swift new file mode 100644 index 00000000000..9209d26f341 --- /dev/null +++ b/Sources/SwiftRefactor/PackageManifest/AddSwiftSetting.swift @@ -0,0 +1,162 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 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 SwiftParser +import SwiftSyntax +import SwiftSyntaxBuilder + +/// Add a swift setting to a manifest's source code. +public struct AddSwiftSetting: ManifestEditRefactoringProvider { + public struct Context { + let target: String + let setting: String + let value: ExprSyntax? + } + + /// The set of argument labels that can occur after the "targets" + /// argument in the Package initializers. + private static let argumentLabelsAfterSwiftSettings: Set = [ + "linkerSettings", + "plugins", + ] + + public static func upcomingFeature( + to target: String, + name: String, + manifest: SourceFileSyntax + ) throws -> PackageEdit { + try manifestRefactor( + syntax: manifest, + in: .init( + target: target, + setting: "enableUpcomingFeature", + value: name.asSyntax() + ) + ) + } + + public static func experimentalFeature( + to target: String, + name: String, + manifest: SourceFileSyntax + ) throws -> PackageEdit { + try manifestRefactor( + syntax: manifest, + in: .init( + target: target, + setting: "enableExperimentalFeature", + value: name.asSyntax() + ) + ) + } + + public static func languageMode( + to target: String, + mode rawMode: String, + manifest: SourceFileSyntax + ) throws -> PackageEdit { + let mode: String + switch rawMode { + case "3", "4", "5", "6": + mode = ".v\(rawMode)" + case "4.2": + mode = ".v4_2" + default: + mode = ".version(\"\(rawMode)\")" + } + + return try manifestRefactor( + syntax: manifest, + in: .init( + target: target, + setting: "swiftLanguageMode", + value: "\(raw: mode)" + ) + ) + } + + public static func strictMemorySafety( + to target: String, + manifest: SourceFileSyntax + ) throws -> PackageEdit { + try manifestRefactor( + syntax: manifest, + in: .init( + target: target, + setting: "strictMemorySafety()", + value: .none + ) + ) + } + + public static func manifestRefactor( + syntax manifest: SourceFileSyntax, + in context: Context + ) throws -> PackageEdit { + guard let packageCall = manifest.findCall(calleeName: "Package") else { + throw ManifestEditError.cannotFindPackage + } + + guard let targetsArgument = packageCall.findArgument(labeled: "targets"), + let targetArray = targetsArgument.expression.findArrayArgument() + else { + throw ManifestEditError.cannotFindTargets + } + + let targetCall = targetArray + .elements + .lazy + .compactMap { + $0.expression.as(FunctionCallExprSyntax.self) + }.first { targetCall in + if let nameArgument = targetCall.findArgument(labeled: "name"), + let nameLiteral = nameArgument.expression.as(StringLiteralExprSyntax.self), + nameLiteral.representedLiteralValue == context.target + { + return true + } + + return false + } + + guard let targetCall else { + throw ManifestEditError.cannotFindTarget(targetName: context.target) + } + + if let memberRef = targetCall.calledExpression.as(MemberAccessExprSyntax.self), + memberRef.declName.baseName.text == "plugin" + { + throw ManifestEditError.cannotAddSettingsToPluginTarget + } + + let newTargetCall = + if let value = context.value { + try targetCall.appendingToArrayArgument( + label: "swiftSettings", + labelsAfter: self.argumentLabelsAfterSwiftSettings, + newElement: ".\(raw: context.setting)(\(value))" + ) + } else { + try targetCall.appendingToArrayArgument( + label: "swiftSettings", + labelsAfter: self.argumentLabelsAfterSwiftSettings, + newElement: ".\(raw: context.setting)" + ) + } + + return PackageEdit( + manifestEdits: [ + .replace(targetCall, with: newTargetCall.description) + ] + ) + } +} diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift index ce7872ba169..118479a6153 100644 --- a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift @@ -19,6 +19,7 @@ public enum ManifestEditError: Error, Equatable { case cannotFindTargets case cannotFindTarget(targetName: String) case cannotFindArrayLiteralArgument(argumentName: String) + case cannotAddSettingsToPluginTarget case existingDependency(dependencyName: String) case malformedManifest(error: String) } @@ -34,6 +35,8 @@ extension ManifestEditError: CustomStringConvertible { return "unable to find target named '\(name)' in package" case .cannotFindArrayLiteralArgument(argumentName: let name): return "unable to find array literal for '\(name)' argument" + case .cannotAddSettingsToPluginTarget: + return "plugin targets do not support settings" case .existingDependency(let name): return "unable to add dependency '\(name)' because it already exists in the list of dependencies" case .malformedManifest(let error): diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 56684c28f65..4545c8eb40d 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -752,6 +752,295 @@ final class ManifestEditTests: XCTestCase { ) ) } + + func testAddSwiftSettings() throws { + XCTAssertThrowsError( + try AddSwiftSetting.upcomingFeature( + to: "OtherTest", + name: "ExistentialAny", + manifest: """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .executableTarget( + name: "MyTest" + ) + ] + ) + """ + ) + ) { error in + XCTAssertEqual( + error as? ManifestEditError, + .cannotFindTarget(targetName: "OtherTest") + ) + } + + XCTAssertThrowsError( + try AddSwiftSetting.upcomingFeature( + to: "MyPlugin", + name: "ExistentialAny", + manifest: """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .plugin( + name: "MyPlugin", + capability: .buildTool + ) + ] + ) + """ + ) + ) { error in + XCTAssertEqual( + error as? ManifestEditError, + .cannotAddSettingsToPluginTarget + ) + } + + try assertManifestRefactor( + """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ] + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .enableUpcomingFeature("ExistentialAny:migratable"), + ] + ), + ] + ) + """ + ) { manifest in + try AddSwiftSetting.upcomingFeature( + to: "MyTest", + name: "ExistentialAny:migratable", + manifest: manifest + ) + } + + try assertManifestRefactor( + """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .enableExperimentalFeature("Extern") + ] + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .enableExperimentalFeature("Extern"), + .enableExperimentalFeature("TrailingComma"), + ] + ), + ] + ) + """ + ) { manifest in + try AddSwiftSetting.experimentalFeature( + to: "MyTest", + name: "TrailingComma", + manifest: manifest + ) + } + + try assertManifestRefactor( + """ + // swift-tools-version: 6.2 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ] + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 6.2 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .strictMemorySafety(), + ] + ), + ] + ) + """ + ) { manifest in + try AddSwiftSetting.strictMemorySafety( + to: "MyTest", + manifest: manifest + ) + } + + try assertManifestRefactor( + """ + // swift-tools-version: 6.0 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ] + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 6.0 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .swiftLanguageMode(.v5), + ] + ), + ] + ) + """ + ) { manifest in + try AddSwiftSetting.languageMode( + to: "MyTest", + mode: "5", + manifest: manifest + ) + } + + // Custom language mode + try assertManifestRefactor( + """ + // swift-tools-version: 6.0 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ] + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 6.0 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .swiftLanguageMode(.version("6.2")), + ] + ), + ] + ) + """ + ) { manifest in + try AddSwiftSetting.languageMode( + to: "MyTest", + mode: "6.2", + manifest: manifest + ) + } + + try assertManifestRefactor( + """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .target( + name: "MyTest", + dependencies: [ + .byName(name: "Dependency") + ] + ), + .target( + name: "Dependency" + ) + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .target( + name: "MyTest", + dependencies: [ + .byName(name: "Dependency") + ] + ), + .target( + name: "Dependency", + swiftSettings: [ + .enableUpcomingFeature("ExistentialAny"), + ] + ) + ] + ) + """ + ) { manifest in + try AddSwiftSetting.upcomingFeature( + to: "Dependency", + name: "ExistentialAny", + manifest: manifest + ) + } + } } /// Assert that applying the given edit/refactor operation to the manifest From 68432ea48a86be1419d1005409ded7e6228a0a55 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 7 Aug 2025 11:57:32 -0700 Subject: [PATCH 24/30] [SwiftRefactor] PackageManifest: Remove an outdated TODO that is no longer applicable --- .../SwiftRefactor/PackageManifest/AddPackageDependency.swift | 3 --- Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift | 3 --- Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift | 3 --- Sources/SwiftRefactor/PackageManifest/AddProduct.swift | 3 --- .../SwiftRefactor/PackageManifest/AddTargetDependency.swift | 3 --- 5 files changed, 15 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift index 9089bb5a3ba..f3c725204f3 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift @@ -26,9 +26,6 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider { /// The set of argument labels that can occur after the "dependencies" /// argument in the Package initializers. - /// - /// TODO: Could we generate this from the the PackageDescription module, so - /// we don't have keep it up-to-date manually? private static let argumentLabelsAfterDependencies: Set = [ "targets", "swiftLanguageVersions", diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index 73d81781016..dbe63640e02 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -28,9 +28,6 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { /// The set of argument labels that can occur after the "targets" /// argument in the Package initializers. - /// - /// TODO: Could we generate this from the the PackageDescription module, so - /// we don't have keep it up-to-date manually? private static let argumentLabelsAfterTargets: Set = [ "swiftLanguageVersions", "cLanguageStandard", diff --git a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift index c1623a62b8e..9f4fd999fde 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift @@ -29,9 +29,6 @@ public struct AddPluginUsage: ManifestEditRefactoringProvider { /// 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 = [] /// Produce the set of source edits needed to add the given package diff --git a/Sources/SwiftRefactor/PackageManifest/AddProduct.swift b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift index fc6d8601e8d..f2de6affbbc 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddProduct.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift @@ -25,9 +25,6 @@ public struct AddProduct: ManifestEditRefactoringProvider { } /// The set of argument labels that can occur after the "products" /// argument in the Package initializers. - /// - /// TODO: Could we generate this from the the PackageDescription module, so - /// we don't have keep it up-to-date manually? private static let argumentLabelsAfterProducts: Set = [ "dependencies", "targets", diff --git a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift index 291b9712d72..717c5b069c9 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift @@ -31,9 +31,6 @@ public struct AddTargetDependency: ManifestEditRefactoringProvider { /// The set of argument labels that can occur after the "dependencies" /// argument in the various target initializers. - /// - /// TODO: Could we generate this from the the PackageDescription module, so - /// we don't have keep it up-to-date manually? private static let argumentLabelsAfterDependencies: Set = [ "path", "exclude", From b89610955ae2964ee6a33b8b2a1295f10591f854 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 7 Aug 2025 13:27:42 -0700 Subject: [PATCH 25/30] [SwiftRefactor] PackageManifest: Change source control dependency to always accept a string This matches `PackageDescription` which explicitly states that all source control dependencies expect a URL. --- .../PackageManifest/AddPackageTarget.swift | 2 +- .../PackageManifest/PackageDependency.swift | 16 +++------------- .../SwiftRefactorTest/ManifestEditTests.swift | 18 +++++++++--------- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index dbe63640e02..068ca4245c8 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -350,7 +350,7 @@ fileprivate extension PackageDependency { return .sourceControl( .init( identity: "swift-syntax", - location: .remote(swiftSyntaxURL), + location: swiftSyntaxURL, requirement: .rangeFrom(version) ) ) diff --git a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift index cf6a1bcbb41..a1a099dd7b4 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift @@ -29,10 +29,10 @@ public enum PackageDependency: Sendable { public struct SourceControl: Sendable { public let identity: String - public let location: Location + public let location: String public let requirement: Requirement - public init(identity: String, location: Location, requirement: Requirement) { + public init(identity: String, location: String, requirement: Requirement) { self.identity = identity self.location = location self.requirement = requirement @@ -45,11 +45,6 @@ public enum PackageDependency: Sendable { case revision(String) case branch(String) } - - public enum Location: Sendable { - case local(String) - case remote(String) - } } public struct Registry: Sendable { @@ -85,12 +80,7 @@ extension PackageDependency.SourceControl: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { // TODO: Not handling identity, nameForTargetDependencyResolutionOnly, // or productFilter yet. - switch location { - case .local: - fatalError() - case .remote(let url): - return ".package(url: \(literal: url.description), \(requirement.asSyntax()))" - } + ".package(url: \(literal: location.description), \(requirement.asSyntax()))" } } diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 4545c8eb40d..5cae4f666bd 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -24,7 +24,7 @@ final class ManifestEditTests: XCTestCase { static let swiftSystemPackageDependency: PackageDependency = .sourceControl( .init( identity: "swift-system", - location: .remote(swiftSystemURL), + location: swiftSystemURL, requirement: .branch("main") ) ) @@ -55,7 +55,7 @@ final class ManifestEditTests: XCTestCase { dependency: .sourceControl( .init( identity: "swift-system", - location: .remote(Self.swiftSystemURL), + location: Self.swiftSystemURL, requirement: .branch("main") ) ) @@ -89,7 +89,7 @@ final class ManifestEditTests: XCTestCase { dependency: .sourceControl( .init( identity: "swift-system", - location: .remote(Self.swiftSystemURL), + location: Self.swiftSystemURL, requirement: .exact("510.0.0") ) ) @@ -121,7 +121,7 @@ final class ManifestEditTests: XCTestCase { dependency: .sourceControl( .init( identity: "swift-system", - location: .remote(Self.swiftSystemURL), + location: Self.swiftSystemURL, requirement: .exact("510.0.0") ) ) @@ -155,7 +155,7 @@ final class ManifestEditTests: XCTestCase { dependency: .sourceControl( .init( identity: "swift-system", - location: .remote(Self.swiftSystemURL), + location: Self.swiftSystemURL, requirement: .exact("510.0.0") ) ) @@ -213,7 +213,7 @@ final class ManifestEditTests: XCTestCase { dependency: .sourceControl( .init( identity: "swift-system", - location: .remote(Self.swiftSystemURL), + location: Self.swiftSystemURL, requirement: .rangeFrom("510.0.0") ) ) @@ -242,7 +242,7 @@ final class ManifestEditTests: XCTestCase { dependency: .sourceControl( .init( identity: "swift-system", - location: .remote(Self.swiftSystemURL), + location: Self.swiftSystemURL, requirement: .rangeFrom("510.0.0") ) ) @@ -269,7 +269,7 @@ final class ManifestEditTests: XCTestCase { dependency: .sourceControl( .init( identity: "swift-system", - location: .remote(Self.swiftSystemURL), + location: Self.swiftSystemURL, requirement: .rangeFrom("510.0.0") ) ) @@ -300,7 +300,7 @@ final class ManifestEditTests: XCTestCase { dependency: .sourceControl( .init( identity: "swift-system", - location: .remote(Self.swiftSystemURL), + location: Self.swiftSystemURL, requirement: .range(lowerBound: "508.0.0", upperBound: "510.0.0") ) ) From 966e0d4a8d862be0cb98080b0ee4004342891331 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 7 Aug 2025 22:56:22 -0700 Subject: [PATCH 26/30] [SwiftRefactor] PackageManifest: Remove identity from `FileSystem` and `SourceControl` Both types are identified by their path and location respectively. --- .../PackageManifest/AddPackageDependency.swift | 4 ++-- .../PackageManifest/AddPackageTarget.swift | 1 - .../PackageManifest/PackageDependency.swift | 5 +---- Tests/SwiftRefactorTest/ManifestEditTests.swift | 11 +---------- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift index f3c725204f3..00b3cf8c489 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift @@ -128,9 +128,9 @@ fileprivate extension PackageDependency { var identifier: String { switch self { case .sourceControl(let info): - return info.identity + return info.location case .fileSystem(let info): - return info.identity + return info.path case .registry(let info): return info.identity } diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index 068ca4245c8..f1969df92ad 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -349,7 +349,6 @@ fileprivate extension PackageDependency { static func swiftSyntax(from version: String) -> PackageDependency { return .sourceControl( .init( - identity: "swift-syntax", location: swiftSyntaxURL, requirement: .rangeFrom(version) ) diff --git a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift index a1a099dd7b4..00a400768fd 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift @@ -22,18 +22,15 @@ public enum PackageDependency: Sendable { case registry(Registry) public struct FileSystem: Sendable { - public let identity: String public let nameForTargetDependencyResolutionOnly: String? public let path: String } public struct SourceControl: Sendable { - public let identity: String public let location: String public let requirement: Requirement - public init(identity: String, location: String, requirement: Requirement) { - self.identity = identity + public init(location: String, requirement: Requirement) { self.location = location self.requirement = requirement } diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 5cae4f666bd..ef1810816fe 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -23,7 +23,6 @@ final class ManifestEditTests: XCTestCase { static let swiftSystemPackageDependency: PackageDependency = .sourceControl( .init( - identity: "swift-system", location: swiftSystemURL, requirement: .branch("main") ) @@ -54,7 +53,6 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: "swift-system", location: Self.swiftSystemURL, requirement: .branch("main") ) @@ -88,7 +86,6 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: "swift-system", location: Self.swiftSystemURL, requirement: .exact("510.0.0") ) @@ -120,7 +117,6 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: "swift-system", location: Self.swiftSystemURL, requirement: .exact("510.0.0") ) @@ -154,7 +150,6 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: "swift-system", location: Self.swiftSystemURL, requirement: .exact("510.0.0") ) @@ -179,7 +174,7 @@ final class ManifestEditTests: XCTestCase { ) ) { (error: any Error) in guard let error = error as? ManifestEditError, - case .existingDependency("swift-system") = error + case .existingDependency("https://github.com/apple/swift-system.git") = error else { XCTFail("unexpected error thrown: \(error)") return @@ -212,7 +207,6 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: "swift-system", location: Self.swiftSystemURL, requirement: .rangeFrom("510.0.0") ) @@ -241,7 +235,6 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: "swift-system", location: Self.swiftSystemURL, requirement: .rangeFrom("510.0.0") ) @@ -268,7 +261,6 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: "swift-system", location: Self.swiftSystemURL, requirement: .rangeFrom("510.0.0") ) @@ -299,7 +291,6 @@ final class ManifestEditTests: XCTestCase { context: .init( dependency: .sourceControl( .init( - identity: "swift-system", location: Self.swiftSystemURL, requirement: .range(lowerBound: "508.0.0", upperBound: "510.0.0") ) From db2b78fb24d7e89827fc6075c4732602c216e2a8 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 7 Aug 2025 22:59:02 -0700 Subject: [PATCH 27/30] [SwiftRefactor] PackageManifest: Remove unused property from `AddPluginUsage` --- .../PackageManifest/AddPackageDependency.swift | 1 + Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift index 00b3cf8c489..68eaad765d0 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift @@ -86,6 +86,7 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider { if let existingDependencies = packageCall.findArgument(labeled: "dependencies") { // If we have an existing dependencies array, we need to check if + // it's already added. if let expr = existingDependencies.expression.as(ArrayExprSyntax.self) { // Iterate through existing dependencies and look for an argument that matches // either the `id` or `url` argument of the new dependency. diff --git a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift index 9f4fd999fde..daf5220ed16 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift @@ -27,10 +27,6 @@ public struct AddPluginUsage: ManifestEditRefactoringProvider { } } - /// The set of argument labels that can occur after the "plugins" - /// argument in the Target initializers. (There aren't any right now) - private static let argumentLabelsAfterPluginUsages: Set = [] - /// Produce the set of source edits needed to add the given package /// dependency to the given manifest file. public static func manifestRefactor( @@ -49,7 +45,7 @@ public struct AddPluginUsage: ManifestEditRefactoringProvider { let newTargetCall = try targetCall.appendingToArrayArgument( label: "plugins", - labelsAfter: Self.argumentLabelsAfterPluginUsages, + labelsAfter: [], newElement: pluginUsage.asSyntax() ) From 6a98663a3706a40c692c95bbda923947fd8d757d Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 8 Aug 2025 00:04:09 -0700 Subject: [PATCH 28/30] [SwiftRefactor] PackageManifest: Inline `Configuration` into `AddPackageTarget.Context` --- .../PackageManifest/AddPackageTarget.swift | 31 ++++++------------- .../SwiftRefactorTest/ManifestEditTests.swift | 4 +-- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index f1969df92ad..e4b6efada0b 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -18,11 +18,14 @@ import SwiftSyntaxBuilder public struct AddPackageTarget: ManifestEditRefactoringProvider { public struct Context { public let target: PackageTarget - public let configuration: Configuration + public var testHarness: TestHarness - public init(target: PackageTarget, configuration: Configuration = .init()) { + public init( + target: PackageTarget, + testHarness: TestHarness = .default + ) { self.target = target - self.configuration = configuration + self.testHarness = testHarness } } @@ -50,19 +53,6 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { public static let `default`: TestHarness = .swiftTesting } - /// Additional configuration information to guide the package editing - /// process. - public struct Configuration { - /// The test harness to use. - public var testHarness: TestHarness - - public init( - testHarness: TestHarness = .default - ) { - self.testHarness = testHarness - } - } - /// Add the given target to the manifest, producing a set of edit results /// that updates the manifest and adds some source files to stub out the /// new target. @@ -70,7 +60,6 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { syntax manifest: SourceFileSyntax, in context: Context ) throws -> PackageEdit { - let configuration = context.configuration guard let packageCall = manifest.findCall(calleeName: "Package") else { throw ManifestEditError.cannotFindPackage } @@ -120,7 +109,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { addPrimarySourceFile( outerPath: outerPath, target: target, - configuration: configuration, + in: context, to: &auxiliaryFiles ) @@ -176,7 +165,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { fileprivate static func addPrimarySourceFile( outerPath: String, target: PackageTarget, - configuration: Configuration, + in context: Context, to auxiliaryFiles: inout AuxiliaryFiles ) { let sourceFilePath = "\(outerPath)/\(target.name)/\(target.name).swift" @@ -188,7 +177,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { // Add appropriate test module dependencies. if target.type == .test { - switch configuration.testHarness { + switch context.testHarness { case .none: break @@ -229,7 +218,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { """ case .test: - switch configuration.testHarness { + switch context.testHarness { case .none: sourceFileText = """ \(imports) diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index ef1810816fe..7319a9e5a9e 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -661,9 +661,7 @@ final class ManifestEditTests: XCTestCase { name: "MyTest target-name", type: .test ), - configuration: .init( - testHarness: .swiftTesting - ) + testHarness: .swiftTesting ) ) } From 9e08670b493039eb462ce26ec65820fefef5a853 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 8 Aug 2025 10:07:50 -0700 Subject: [PATCH 29/30] [SwiftRefactor] PackageManifest: Address stylistic review feedback --- .../PackageManifest/AddPackageTarget.swift | 12 ++++----- .../ManifestEditRefactoringProvider.swift | 2 +- .../PackageManifest/PackageDependency.swift | 5 +--- .../PackageManifest/PackageEdit.swift | 2 +- .../PackageManifest/PackageTarget.swift | 12 ++++----- .../PackageManifest/ProductDescription.swift | 2 +- .../PackageManifest/StringUtils.swift | 12 ++++----- .../PackageManifest/SyntaxEditUtils.swift | 27 ++++++------------- 8 files changed, 30 insertions(+), 44 deletions(-) diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index e4b6efada0b..22bf3f68d5c 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -190,7 +190,7 @@ public struct AddPackageTarget: ManifestEditRefactoringProvider { } let importDecls = importModuleNames.lazy.sorted().map { name in - DeclSyntax("import \(raw: name)").with(\.trailingTrivia, .newline) + DeclSyntax("import \(raw: name)\n") } let imports = CodeBlockItemListSyntax { @@ -298,9 +298,9 @@ fileprivate extension PackageTarget.Dependency { /// Retrieve the name of the dependency var name: String { switch self { - case .target(name: let name), - .byName(name: let name), - .product(name: let name, package: _): + case .target(let name), + .byName(let name), + .product(let name, package: _): return name } } @@ -308,7 +308,7 @@ fileprivate extension PackageTarget.Dependency { /// The array of auxiliary files that can be added by a package editing /// operation. -fileprivate typealias AuxiliaryFiles = [(String, SourceFileSyntax)] +private typealias AuxiliaryFiles = [(String, SourceFileSyntax)] fileprivate extension AuxiliaryFiles { /// Add a source file to the list of auxiliary files. @@ -322,7 +322,7 @@ fileprivate extension AuxiliaryFiles { /// The set of dependencies we need to introduce to a newly-created macro /// target. -fileprivate let macroTargetDependencies: [PackageTarget.Dependency] = [ +private let macroTargetDependencies: [PackageTarget.Dependency] = [ .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), ] diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift index e34173e666e..9c32cda991b 100644 --- a/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift @@ -18,7 +18,7 @@ where Self.Input == SourceFileSyntax { static func manifestRefactor(syntax: SourceFileSyntax, in context: Context) throws -> PackageEdit } -extension EditRefactoringProvider where Self: ManifestEditRefactoringProvider { +extension ManifestEditRefactoringProvider { public static func textRefactor(syntax: Input, in context: Context) -> [SourceEdit] { return (try? manifestRefactor(syntax: syntax, in: context).manifestEdits) ?? [] } diff --git a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift index 00a400768fd..a309f2f0ae9 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift @@ -22,7 +22,6 @@ public enum PackageDependency: Sendable { case registry(Registry) public struct FileSystem: Sendable { - public let nameForTargetDependencyResolutionOnly: String? public let path: String } @@ -75,8 +74,6 @@ extension PackageDependency.FileSystem: ManifestSyntaxRepresentable { extension PackageDependency.SourceControl: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { - // TODO: Not handling identity, nameForTargetDependencyResolutionOnly, - // or productFilter yet. ".package(url: \(literal: location.description), \(requirement.asSyntax()))" } } @@ -104,7 +101,7 @@ extension PackageDependency.SourceControl.Requirement: ManifestSyntaxRepresentab case .range(let lowerBound, let upperBound): return LabeledExprSyntax( - expression: "\(lowerBound.asSyntax())..<\(upperBound.asSyntax())" as ExprSyntax + expression: "\(literal: lowerBound)..<\(literal: upperBound)" as ExprSyntax ) case .revision(let revision): diff --git a/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift b/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift index aa16eab091f..1c9180a3e9e 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift @@ -19,5 +19,5 @@ public struct PackageEdit { public var manifestEdits: [SourceEdit] = [] /// Auxiliary files to write. - public var auxiliaryFiles: [(String, SourceFileSyntax)] = [] + public var auxiliaryFiles: [(relativePath: String, contents: SourceFileSyntax)] = [] } diff --git a/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift index e9fff2cf307..20109a8b40a 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift @@ -114,16 +114,16 @@ extension PackageTarget: ManifestSyntaxRepresentable { extension PackageTarget.Dependency: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { switch self { - case .byName(name: let name): + case .byName(let name): return "\(literal: name)" - case .target(name: let name): + case .target(let name): return ".target(name: \(literal: name))" - case .product(name: let name, package: nil): + case .product(let name, package: nil): return ".product(name: \(literal: name))" - case .product(name: let name, package: let package): + case .product(let name, let package): return ".product(name: \(literal: name), package: \(literal: package))" } } @@ -132,10 +132,10 @@ extension PackageTarget.Dependency: ManifestSyntaxRepresentable { extension PackageTarget.PluginUsage: ManifestSyntaxRepresentable { func asSyntax() -> ExprSyntax { switch self { - case .plugin(name: let name, package: nil): + case .plugin(let name, package: nil): return ".plugin(name: \(literal: name))" - case .plugin(name: let name, package: let package): + case .plugin(let name, let package): return ".plugin(name: \(literal: name), package: \(literal: package))" } } diff --git a/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift index 7f55382397f..76886655287 100644 --- a/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift +++ b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift @@ -76,7 +76,7 @@ extension ProductDescription: ManifestSyntaxRepresentable { private var functionName: String { switch type { case .executable: return "executable" - case .library(_): return "library" + case .library: return "library" case .macro: return "macro" case .plugin: return "plugin" case .snippet: return "snippet" diff --git a/Sources/SwiftRefactor/PackageManifest/StringUtils.swift b/Sources/SwiftRefactor/PackageManifest/StringUtils.swift index 6f0941ea7c6..0e713562bde 100644 --- a/Sources/SwiftRefactor/PackageManifest/StringUtils.swift +++ b/Sources/SwiftRefactor/PackageManifest/StringUtils.swift @@ -18,7 +18,7 @@ extension String { func mangledToC99ExtendedIdentifier() -> String { // Map invalid C99-invalid Unicode scalars to a replacement character. let replacementUnichar: UnicodeScalar = "_" - var mangledUnichars: [UnicodeScalar] = self.unicodeScalars.map({ + var mangledUnichars: [UnicodeScalar] = self.unicodeScalars.map { switch $0.value { case // A-Z 0x0041...0x005A, @@ -194,10 +194,10 @@ extension String { default: return replacementUnichar } - }) + } // Apply further restrictions to the prefix. - loop: for (idx, c) in mangledUnichars.enumerated() { + LOOP: for (idx, c) in mangledUnichars.enumerated() { switch c.value { case // 0-9 0x0030...0x0039, @@ -208,9 +208,9 @@ extension String { 0x0CE6...0x0CEF, 0x0D66...0x0D6F, 0x0E50...0x0E59, 0x0ED0...0x0ED9, 0x0F20...0x0F33: mangledUnichars[idx] = replacementUnichar - break loop + break LOOP default: - break loop + break LOOP } } @@ -218,6 +218,6 @@ extension String { // FIXME: We should only construct a new string if anything changed. // FIXME: There doesn't seem to be a way to create a string from an // array of Unicode scalars; but there must be a better way. - return mangledUnichars.reduce("") { $0 + String($1) } + return String(decoding: mangledUnichars.flatMap { $0.utf8 }, as: UTF8.self) } } diff --git a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift index 1e31236e4d4..ec493b9e7a8 100644 --- a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift +++ b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift @@ -23,22 +23,12 @@ extension Trivia { var hasNewlines: Bool { contains(where: \.isNewline) } - - /// Produce trivia from the last newline to the end, dropping anything - /// prior to that. - var onlyLastLine: Trivia { - guard let lastNewline = pieces.lastIndex(where: { $0.isNewline }) else { - return self - } - - return Trivia(pieces: pieces[lastNewline...]) - } } /// Syntax walker to find the first occurrence of a given node kind that /// matches a specific predicate. private class FirstNodeFinder: SyntaxAnyVisitor { - var predicate: (Node) -> Bool + let predicate: (Node) -> Bool var found: Node? = nil init(predicate: @escaping (Node) -> Bool) { @@ -100,7 +90,7 @@ extension LabeledExprListSyntax { /// Find the index at which the one would insert a new argument given /// the set of argument labels that could come after the argument we /// want to insert. - func findArgumentInsertionPosition( + fileprivate func findArgumentInsertionPosition( labelsAfter: Set ) -> SyntaxChildrenIndex { firstIndex { @@ -120,7 +110,7 @@ extension LabeledExprListSyntax { /// created by the `generator` function, which is provided with leading /// trivia and trailing comma it should use to match the surrounding /// context. - func insertingArgument( + fileprivate func insertingArgument( at position: SyntaxChildrenIndex, generator: (_ leadingTrivia: Trivia, _ trailingComma: TokenSyntax?) -> LabeledExprSyntax ) -> LabeledExprListSyntax { @@ -236,9 +226,8 @@ extension ArrayExprSyntax { // there. if last.trailingComma == nil { var newElements = Array(elements) - newElements[newElements.count - 1].trailingComma = .commaToken() + newElements[newElements.count - 1].trailingComma = .commaToken(trailingTrivia: last.expression.trailingTrivia) newElements[newElements.count - 1].expression.trailingTrivia = Trivia() - newElements[newElements.count - 1].trailingTrivia = last.trailingTrivia elements = ArrayElementListSyntax(newElements) } @@ -289,12 +278,12 @@ extension ExprSyntax { } // MARK: Utilities to oeprate on arrays of array literal elements. -extension Array { +extension [ArrayElementSyntax] { /// Append a new argument expression. mutating func append(expression: ExprSyntax) { // Add a comma on the prior expression, if there is one. let leadingTrivia: Trivia? - if count > 0 { + if !isEmpty { self[count - 1].trailingComma = TokenSyntax.commaToken() leadingTrivia = .newline @@ -317,7 +306,7 @@ extension Array { // MARK: Utilities to operate on arrays of call arguments. -extension Array { +extension [LabeledExprSyntax] { /// Append a potentially labeled argument with the argument expression. mutating func append(label: String?, expression: ExprSyntax) { // Add a comma on the prior expression, if there is one. @@ -422,7 +411,7 @@ extension Array { } // MARK: Utilities for adding arguments into calls. -fileprivate class ReplacingRewriter: SyntaxRewriter { +private class ReplacingRewriter: SyntaxRewriter { let childNode: Syntax let newChildNode: Syntax From 2f6937d85d80892834e412a9c1752597495792b8 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 8 Aug 2025 10:24:13 -0700 Subject: [PATCH 30/30] [SwiftRefactor] PackageManifest: Mark all of the refactorings and supporting types as SPI Avoid increasing public API surface of the swift-syntax for now until the factorings are fully evaluated. --- .../PackageManifest/AddPackageDependency.swift | 1 + .../PackageManifest/AddPackageTarget.swift | 1 + .../PackageManifest/AddPluginUsage.swift | 1 + .../SwiftRefactor/PackageManifest/AddProduct.swift | 1 + .../PackageManifest/AddSwiftSetting.swift | 1 + .../PackageManifest/AddTargetDependency.swift | 1 + .../PackageManifest/ManifestEditError.swift | 1 + .../ManifestEditRefactoringProvider.swift | 1 + .../PackageManifest/PackageDependency.swift | 1 + .../SwiftRefactor/PackageManifest/PackageEdit.swift | 1 + .../PackageManifest/PackageTarget.swift | 1 + .../PackageManifest/ProductDescription.swift | 1 + .../SwiftRefactor/PackageManifest/StringUtils.swift | 1 + .../PackageManifest/SyntaxEditUtils.swift | 12 ++++++++++++ Tests/SwiftRefactorTest/ManifestEditTests.swift | 2 +- 15 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift index 68eaad765d0..ec111d299c0 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageDependency.swift @@ -15,6 +15,7 @@ import SwiftSyntax import SwiftSyntaxBuilder /// Add a package dependency to a package manifest's source code. +@_spi(PackageRefactor) public struct AddPackageDependency: ManifestEditRefactoringProvider { public struct Context { public var dependency: PackageDependency diff --git a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift index 22bf3f68d5c..2b5b3b8cc9f 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPackageTarget.swift @@ -15,6 +15,7 @@ import SwiftSyntax import SwiftSyntaxBuilder /// Add a target to a manifest's source code. +@_spi(PackageRefactor) public struct AddPackageTarget: ManifestEditRefactoringProvider { public struct Context { public let target: PackageTarget diff --git a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift index daf5220ed16..4af17aa92a6 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddPluginUsage.swift @@ -16,6 +16,7 @@ import SwiftSyntaxBuilder /// Add a plugin usage to a particular target in the manifest's source /// code. +@_spi(PackageRefactor) public struct AddPluginUsage: ManifestEditRefactoringProvider { public struct Context { public let targetName: String diff --git a/Sources/SwiftRefactor/PackageManifest/AddProduct.swift b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift index f2de6affbbc..e735780fd78 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddProduct.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddProduct.swift @@ -15,6 +15,7 @@ import SwiftSyntax import SwiftSyntaxBuilder /// Add a product to the manifest's source code. +@_spi(PackageRefactor) public struct AddProduct: ManifestEditRefactoringProvider { public struct Context { public let product: ProductDescription diff --git a/Sources/SwiftRefactor/PackageManifest/AddSwiftSetting.swift b/Sources/SwiftRefactor/PackageManifest/AddSwiftSetting.swift index 9209d26f341..63534b79645 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddSwiftSetting.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddSwiftSetting.swift @@ -15,6 +15,7 @@ import SwiftSyntax import SwiftSyntaxBuilder /// Add a swift setting to a manifest's source code. +@_spi(PackageRefactor) public struct AddSwiftSetting: ManifestEditRefactoringProvider { public struct Context { let target: String diff --git a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift index 717c5b069c9..3d0f72ad7b9 100644 --- a/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift @@ -15,6 +15,7 @@ import SwiftSyntax import SwiftSyntaxBuilder /// Add a target dependency to a manifest's source code. +@_spi(PackageRefactor) public struct AddTargetDependency: ManifestEditRefactoringProvider { public struct Context { /// The dependency to add. diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift index 118479a6153..d87de291394 100644 --- a/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditError.swift @@ -14,6 +14,7 @@ import SwiftSyntax /// An error describing problems that can occur when attempting to edit a /// package manifest programattically. +@_spi(PackageRefactor) public enum ManifestEditError: Error, Equatable { case cannotFindPackage case cannotFindTargets diff --git a/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift b/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift index 9c32cda991b..f3d04af10c2 100644 --- a/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift +++ b/Sources/SwiftRefactor/PackageManifest/ManifestEditRefactoringProvider.swift @@ -12,6 +12,7 @@ import SwiftSyntax +@_spi(PackageRefactor) public protocol ManifestEditRefactoringProvider: EditRefactoringProvider where Self.Input == SourceFileSyntax { diff --git a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift index a309f2f0ae9..5e38bf4d919 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageDependency.swift @@ -16,6 +16,7 @@ import SwiftSyntaxBuilder /// Describes a package dependency for refactoring purposes. This is a syntactic /// subset of the full package manifest's description of a package dependency. +@_spi(PackageRefactor) public enum PackageDependency: Sendable { case fileSystem(FileSystem) case sourceControl(SourceControl) diff --git a/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift b/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift index 1c9180a3e9e..c7fb054583c 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageEdit.swift @@ -14,6 +14,7 @@ import SwiftSyntax /// The result of editing a package, including any edits to the package /// manifest and any new files that are introduced. +@_spi(PackageRefactor) public struct PackageEdit { /// Edits to perform to the package manifest. public var manifestEdits: [SourceEdit] = [] diff --git a/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift b/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift index 20109a8b40a..0956feeefc1 100644 --- a/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift +++ b/Sources/SwiftRefactor/PackageManifest/PackageTarget.swift @@ -14,6 +14,7 @@ import SwiftSyntax /// Syntactic wrapper type that describes a target for refactoring /// purposes but does not interpret its contents. +@_spi(PackageRefactor) public struct PackageTarget { public let name: String diff --git a/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift index 76886655287..1bf34d317eb 100644 --- a/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift +++ b/Sources/SwiftRefactor/PackageManifest/ProductDescription.swift @@ -14,6 +14,7 @@ import SwiftSyntax /// Syntactic wrapper type that describes a product for refactoring /// purposes but does not interpret its contents. +@_spi(PackageRefactor) public struct ProductDescription { /// The name of the product. public let name: String diff --git a/Sources/SwiftRefactor/PackageManifest/StringUtils.swift b/Sources/SwiftRefactor/PackageManifest/StringUtils.swift index 0e713562bde..a964cc33ec8 100644 --- a/Sources/SwiftRefactor/PackageManifest/StringUtils.swift +++ b/Sources/SwiftRefactor/PackageManifest/StringUtils.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +@_spi(PackageRefactor) extension String { /// Returns a form of the string that is valid C99 Extended Identifier (by /// replacing any invalid characters in an unspecified but consistent way). diff --git a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift index ec493b9e7a8..2f1abdb3a0c 100644 --- a/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift +++ b/Sources/SwiftRefactor/PackageManifest/SyntaxEditUtils.swift @@ -18,6 +18,7 @@ import SwiftSyntax /// to get it right. let defaultIndent = TriviaPiece.spaces(4) +@_spi(PackageRefactor) extension Trivia { /// Determine whether this trivia has newlines or not. var hasNewlines: Bool { @@ -50,6 +51,7 @@ private class FirstNodeFinder: SyntaxAnyVisitor { } } +@_spi(PackageRefactor) extension SyntaxProtocol { /// Find the first node of the Self type that matches the given predicate. static func findFirst( @@ -64,6 +66,7 @@ extension SyntaxProtocol { } } +@_spi(PackageRefactor) extension FunctionCallExprSyntax { /// Check whether this call expression has a callee that is a reference /// to a declaration with the given name. @@ -86,6 +89,7 @@ extension FunctionCallExprSyntax { } } +@_spi(PackageRefactor) extension LabeledExprListSyntax { /// Find the index at which the one would insert a new argument given /// the set of argument labels that could come after the argument we @@ -158,6 +162,7 @@ extension LabeledExprListSyntax { } } +@_spi(PackageRefactor) extension SyntaxProtocol { /// Look for a call expression to a callee with the given name. func findCall(calleeName: String) -> FunctionCallExprSyntax? { @@ -167,6 +172,7 @@ extension SyntaxProtocol { } } +@_spi(PackageRefactor) extension FunctionCallExprSyntax { /// Find the call that forms a target with the given name in this /// package manifest. @@ -200,6 +206,7 @@ extension FunctionCallExprSyntax { } } +@_spi(PackageRefactor) extension ArrayExprSyntax { /// Produce a new array literal expression that appends the given /// element, while trying to maintain similar indentation. @@ -259,6 +266,7 @@ extension ArrayExprSyntax { } } +@_spi(PackageRefactor) extension ExprSyntax { /// Find an array argument either at the top level or within a sequence /// expression. @@ -278,6 +286,7 @@ extension ExprSyntax { } // MARK: Utilities to oeprate on arrays of array literal elements. +@_spi(PackageRefactor) extension [ArrayElementSyntax] { /// Append a new argument expression. mutating func append(expression: ExprSyntax) { @@ -306,6 +315,7 @@ extension [ArrayElementSyntax] { // MARK: Utilities to operate on arrays of call arguments. +@_spi(PackageRefactor) extension [LabeledExprSyntax] { /// Append a potentially labeled argument with the argument expression. mutating func append(label: String?, expression: ExprSyntax) { @@ -430,6 +440,7 @@ private class ReplacingRewriter: SyntaxRewriter { } } +@_spi(PackageRefactor) fileprivate extension SyntaxProtocol { /// Replace the given child with a new child node. func replacingChild(_ childNode: Syntax, with newChildNode: Syntax) -> Self { @@ -440,6 +451,7 @@ fileprivate extension SyntaxProtocol { } } +@_spi(PackageRefactor) extension FunctionCallExprSyntax { /// Perform source edits that will add the given new element to the /// array for an argument with the given label (if there is one), or diff --git a/Tests/SwiftRefactorTest/ManifestEditTests.swift b/Tests/SwiftRefactorTest/ManifestEditTests.swift index 7319a9e5a9e..9e412e6eaaa 100644 --- a/Tests/SwiftRefactorTest/ManifestEditTests.swift +++ b/Tests/SwiftRefactorTest/ManifestEditTests.swift @@ -12,7 +12,7 @@ @_spi(FixItApplier) import SwiftIDEUtils import SwiftParser -import SwiftRefactor +@_spi(PackageRefactor) import SwiftRefactor import SwiftSyntax import SwiftSyntaxBuilder import XCTest