@@ -47,6 +47,15 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider {
4747 throw ManifestEditError . cannotFindPackage
4848 }
4949
50+ guard
51+ try ! dependencyAlreadyAdded(
52+ dependency,
53+ in: packageCall
54+ )
55+ else {
56+ return PackageEdit ( manifestEdits: [ ] )
57+ }
58+
5059 let newPackageCall = try addPackageDependencyLocal (
5160 dependency,
5261 to: packageCall
@@ -59,6 +68,52 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider {
5968 )
6069 }
6170
71+ /// Return `true` if the dependency already exists in the manifest, otherwise return `false`.
72+ /// Throws an error if a dependency already exists with the same id or url, but different arguments.
73+ private static func dependencyAlreadyAdded(
74+ _ dependency: PackageDependency ,
75+ in packageCall: FunctionCallExprSyntax
76+ ) throws -> Bool {
77+ let dependencySyntax = dependency. asSyntax ( )
78+ guard let dependencyFnSyntax = dependencySyntax. as ( FunctionCallExprSyntax . self) else {
79+ throw ManifestEditError . cannotFindPackage
80+ }
81+
82+ guard
83+ let id = dependencyFnSyntax. arguments. first ( where: {
84+ $0. label? . text == " url " || $0. label? . text == " id " || $0. label? . text == " path "
85+ } )
86+ else {
87+ throw ManifestEditError . malformedManifest ( error: " missing id or url argument in dependency syntax " )
88+ }
89+
90+ if let existingDependencies = packageCall. findArgument ( labeled: " dependencies " ) {
91+ // If we have an existing dependencies array, we need to check if
92+ if let expr = existingDependencies. expression. as ( ArrayExprSyntax . self) {
93+ // Iterate through existing dependencies and look for an argument that matches
94+ // either the `id` or `url` argument of the new dependency.
95+ let existingArgument = expr. elements. first { elem in
96+ if let funcExpr = elem. expression. as ( FunctionCallExprSyntax . self) {
97+ return funcExpr. arguments. contains {
98+ $0. trimmedDescription == id. trimmedDescription
99+ }
100+ }
101+ return true
102+ }
103+
104+ if let existingArgument {
105+ let normalizedExistingArgument = existingArgument. detached. with ( \. trailingComma, nil )
106+ // This exact dependency already exists, return false to indicate we should do nothing.
107+ if normalizedExistingArgument. trimmedDescription == dependencySyntax. trimmedDescription {
108+ return true
109+ }
110+ throw ManifestEditError . existingDependency ( dependencyName: dependency. identifier)
111+ }
112+ }
113+ }
114+ return false
115+ }
116+
62117 /// Implementation of adding a package dependency to an existing call.
63118 static func addPackageDependencyLocal(
64119 _ dependency: PackageDependency ,
@@ -71,3 +126,16 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider {
71126 )
72127 }
73128}
129+
130+ fileprivate extension PackageDependency {
131+ var identifier : String {
132+ switch self {
133+ case . sourceControl( let info) :
134+ return info. identity
135+ case . fileSystem( let info) :
136+ return info. identity
137+ case . registry( let info) :
138+ return info. identity
139+ }
140+ }
141+ }
0 commit comments