@@ -47,6 +47,15 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider {
47
47
throw ManifestEditError . cannotFindPackage
48
48
}
49
49
50
+ guard
51
+ try ! dependencyAlreadyAdded(
52
+ dependency,
53
+ in: packageCall
54
+ )
55
+ else {
56
+ return PackageEdit ( manifestEdits: [ ] )
57
+ }
58
+
50
59
let newPackageCall = try addPackageDependencyLocal (
51
60
dependency,
52
61
to: packageCall
@@ -59,6 +68,52 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider {
59
68
)
60
69
}
61
70
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
+
62
117
/// Implementation of adding a package dependency to an existing call.
63
118
static func addPackageDependencyLocal(
64
119
_ dependency: PackageDependency ,
@@ -71,3 +126,16 @@ public struct AddPackageDependency: ManifestEditRefactoringProvider {
71
126
)
72
127
}
73
128
}
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