-
Notifications
You must be signed in to change notification settings - Fork 447
Swift package manifest refactoring actions #2904
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@swift-ci please test |
1 similar comment
@swift-ci please test |
@swift-ci please test Windows |
f371a2c
to
4061163
Compare
@swift-ci please test |
@swift-ci please test Windows |
@swift-ci please test |
1 similar comment
@swift-ci please test |
@swift-ci please test Linux |
@swift-ci please test macOS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for moving the code, Doug.
Sources/SwiftRefactor/PackageManifest/AddTargetDependency.swift
Outdated
Show resolved
Hide resolved
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.
…Refactor This manifest editing action introduces a new dependency to an existing target
…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.
This removes a conflict with the SwiftPM version.
ede1bea
to
6af939f
Compare
…sError` Removes `XCTAssertThrows` in favor of XCTest standard way of testing throwing functions.
…ents 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.
…ose Windows failures
…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.
…ring` 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.
… `AddPackageDependency` This was introduced by swiftlang/swift-package-manager#8534
…ft settings Allows to add `enable{Upcoming, Experimental}Feature`, `swiftLanguageMode`, `strictMemorySafety` as well as custom ones.
…always accept a string This matches `PackageDescription` which explicitly states that all source control dependencies expect a URL.
a0d3cfe
to
b896109
Compare
@swift-ci please test |
public protocol ManifestEditRefactoringProvider: EditRefactoringProvider | ||
where Self.Input == SourceFileSyntax { | ||
|
||
static func manifestRefactor(syntax: SourceFileSyntax, in context: Context) throws -> PackageEdit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: I'm planning to follow-up to this PR and remove this, I think all we need to do is change result type of textRefactor
to not only produce edits but also auxiliary files.
/// Add a package dependency to a package manifest's source code. | ||
public struct AddPackageDependency: ManifestEditRefactoringProvider { | ||
public struct Context { | ||
public var dependency: PackageDependency |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm tempted to say this should be optional and we could add placeholder if not given?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd think that the whole point of the refactoring is to actually add a dependency that's why it's non-optional at the moment, I'm not sure how we'd placeholder that efficiently because different dependency kinds have different arguments...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how we'd placeholder that efficiently
We'd just pick a reasonable default, ie. SourceControl
and from
:
.package(url: <#url#>, from: <#version#>)
(though those placeholders could also be typed)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe that is the way to go here but instead of having it optional we can make it a defaulted parameter on initializer in both cases.
/// Add a target to a manifest's source code. | ||
public struct AddPackageTarget: ManifestEditRefactoringProvider { | ||
public struct Context { | ||
public let target: PackageTarget |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as adding a package dependency, I could see it being useful to add a placeholder target. Although maybe I'm wrong and you could just use completion for that. Thoughts?
…d `SourceControl` Both types are identified by their path and location respectively.
…ageTarget.Context`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed this from a swift-syntax standpoint, not from a SwiftPM correctness point of view.
A couple high-level comments:
- This PR is quite large and I think we might need at least one more round of reviews. Would it be possible to split it up, eg. by adding only one refactoring action in this one.
- I am sceptical about adding SwiftPM-specific types to swift-syntax (or generally the amount of public API surface that this PR adds). Should we consider making these types SPI instead?
- For the TODO/FIXMEs in the codebase, could you file issues if you’re not planning to address them in this PR? In my experience they just get lost somehow and some of them probably make for good first issues.
- Should these refactorings live in a separte module (eg.
SwiftPackageRefactor
)? I think that would be a good separation of general Swift language refactorings and refactorings that have knowledge about SwiftPM package manifests. Ideally, we could consider moving that module to a separate repo at some point in the future. I’m not really a fan of adding SwiftPM-specific types to swift-syntax. I think in a perfect world, these types would live in some repository that depends on swift-syntax and that both SourceKit-LSP and SwiftPM can depend on. Would it make sense to mark these types as SPI for now?
|
||
import SwiftSyntax | ||
|
||
public protocol ManifestEditRefactoringProvider: EditRefactoringProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be a public protocol? I think it would be better of EditRefactoringProvider
would gain the ability to modify auxiliary files. At that point a package refactoring wouldn’t be anything special, it would just be a normal refactoring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment about this - #2904 (comment)
static func manifestRefactor(syntax: SourceFileSyntax, in context: Context) throws -> PackageEdit | ||
} | ||
|
||
extension EditRefactoringProvider where Self: ManifestEditRefactoringProvider { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn’t this be simplified to the following? Or doesn’t that work?
extension EditRefactoringProvider where Self: ManifestEditRefactoringProvider { | |
extension ManifestEditRefactoringProvider { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ You are right, it doesn't require Self:
could be placed directly.
case registry(Registry) | ||
|
||
public struct FileSystem: Sendable { | ||
public let nameForTargetDependencyResolutionOnly: String? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That name is quite a mouthful. If this is the best name, could you add a comment that describes what it does? Maybe an example would be the best way to convey what both of these members do – also for SourceControl
and Registry
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly seems like this could just be name
, I don't think the "for target dependency resolution" part really matters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed this completely since we don't really need that for refactorings.
} | ||
|
||
extension PackageDependency: ManifestSyntaxRepresentable { | ||
func asSyntax() -> ExprSyntax { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this make more sense as a computed property?
|
||
extension PackageDependency.SourceControl: ManifestSyntaxRepresentable { | ||
func asSyntax() -> ExprSyntax { | ||
// TODO: Not handling identity, nameForTargetDependencyResolutionOnly, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you file an issue for this if you’re not planning to address it in this PR. My experience is that these TODOs will just be forgotten. And this would probably also make for a nice good first issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should handle name
now and remove the TODO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ This TODO was outdated and I removed it, nameFor* is SwiftPM concept that shouldn't be involved here and source control could be identified by its location.
|
||
fileprivate extension SyntaxProtocol { | ||
/// Replace the given child with a new child node. | ||
func replacingChild(_ childNode: Syntax, with newChildNode: Syntax) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we ever replace nodes with nodes of a different type? If so, this would fail the cast(Self.self)
if self == childNode
. If we don’t need to replace nodes of different types, this could be
func replacingChild(_ childNode: Syntax, with newChildNode: Syntax) -> Self { | |
func replacingChild<T: SyntaxProtocol>(_ childNode: T, with newChildNode: T) -> Self { |
|
||
// Format the element appropriately for the context. | ||
let indentation = Trivia( | ||
pieces: arg.leadingTrivia.filter(\.isSpaceOrTab) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be using indentaitonOfLine
or something like that to cover if arg
is not on a new line?
} | ||
|
||
let importDecls = importModuleNames.lazy.sorted().map { name in | ||
DeclSyntax("import \(raw: name)").with(\.trailingTrivia, .newline) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can’t this just be
DeclSyntax("import \(raw: name)").with(\.trailingTrivia, .newline) | |
DeclSyntax("import \(raw: name)\n") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅
let sourceFileText: SourceFileSyntax | ||
switch target.type { | ||
case .binary, .plugin, .system: | ||
fatalError("should have exited above") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is above
? I don’t see the check where this should have exited.
} | ||
|
||
fileprivate extension String { | ||
func localizedFirstWordCapitalized() -> String { prefix(1).uppercased() + dropFirst() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What’s the localize
part of this function name?
f72f9d9
to
22bbcde
Compare
…porting types as SPI Avoid increasing public API surface of the swift-syntax for now until the factorings are fully evaluated.
22bbcde
to
2f6937d
Compare
We have discussed this and decided to SPI new refactorings for now. |
@swift-ci please test |
@swift-ci please test Windows platform |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good with me, assuming all declarations are at most SPI (ie. no public declarations).
Port the package manifest editing refactoring actions over to
SwiftRefactor
. These refactoring operations come from the swift-package-manager repository, and are being moved here for several reasons:SwiftRefactor
module is where we are collecting various refactoring.This ports all of the package manifest refactoring actions over from swift-package-manager (add package dependency, add target, add product, add target dependency) and introduces a new one that will be used by swift-package-manager in the future, "add plugin usage". The code itself is little changed from the SwiftPM version, although the
ManifestEditRefactoringProvider
protocol that unifies them all is new.Note that we end up bringing over syntactic versions of various SwiftPM types like
TargetDescription
, but in a string-backed, simplified form that is purely syntactic. These are needed to community with the refactoring actions, and can easily be constructed by clients.