@@ -44,6 +44,13 @@ public enum AddPackageDependency {
44
44
throw ManifestEditError . cannotFindPackage
45
45
}
46
46
47
+ guard try checkExistingDependency (
48
+ dependency,
49
+ in: packageCall
50
+ ) else {
51
+ return PackageEditResult ( manifestEdits: [ ] )
52
+ }
53
+
47
54
let newPackageCall = try addPackageDependencyLocal (
48
55
dependency, to: packageCall
49
56
)
@@ -55,6 +62,52 @@ public enum AddPackageDependency {
55
62
)
56
63
}
57
64
65
+ /// Check that the given package dependency doesn't already exist in the manifest.
66
+ /// If the exact same dependency already exists, `false` is returned to indicate
67
+ /// that no edits are needed. If a different dependency with the same id or url
68
+ /// with different arguments exists, an error is thrown.
69
+ private static func checkExistingDependency(
70
+ _ dependency: MappablePackageDependency . Kind ,
71
+ in packageCall: FunctionCallExprSyntax
72
+ ) throws -> Bool {
73
+ let dependencySyntax = dependency. asSyntax ( )
74
+ guard let dependenctFnSyntax = dependencySyntax. as ( FunctionCallExprSyntax . self) else {
75
+ throw ManifestEditError . cannotFindPackage // TODO: Fix error
76
+ }
77
+
78
+ guard let id = dependenctFnSyntax. arguments. first ( where: {
79
+ $0. label? . text == " url " || $0. label? . text == " id " || $0. label? . text == " path "
80
+ } ) else {
81
+ throw InternalError ( " Missing id or url argument in dependency syntax " )
82
+ }
83
+
84
+ if let existingDependencies = packageCall. findArgument ( labeled: " dependencies " ) {
85
+ // If we have an existing dependencies array, we need to check if
86
+ if let expr = existingDependencies. expression. as ( ArrayExprSyntax . self) {
87
+ // Iterate through existing dependencies and look for an argument that matches
88
+ // either the `id` or `url` argument of the new dependency.
89
+ let existingArgument = expr. elements. first { elem in
90
+ if let funcExpr = elem. expression. as ( FunctionCallExprSyntax . self) {
91
+ return funcExpr. arguments. contains {
92
+ $0. trimmedDescription == id. trimmedDescription
93
+ }
94
+ }
95
+ return false
96
+ }
97
+
98
+ if let existingArgument {
99
+ let normalizedExistingArgument = existingArgument. detached. with ( \. trailingComma, nil )
100
+ // This exact dependency already exists, return false to indicate we should do nothing.
101
+ if normalizedExistingArgument. trimmedDescription == dependencySyntax. trimmedDescription {
102
+ return false
103
+ }
104
+ throw ManifestEditError . existingDependency ( dependencyName: dependency. identifier)
105
+ }
106
+ }
107
+ }
108
+ return true
109
+ }
110
+
58
111
/// Implementation of adding a package dependency to an existing call.
59
112
static func addPackageDependencyLocal(
60
113
_ dependency: MappablePackageDependency . Kind ,
@@ -67,3 +120,16 @@ public enum AddPackageDependency {
67
120
)
68
121
}
69
122
}
123
+
124
+ fileprivate extension MappablePackageDependency . Kind {
125
+ var identifier : String {
126
+ switch self {
127
+ case . sourceControl( let name, let path, _) :
128
+ return name ?? path
129
+ case . fileSystem( let name, let location) :
130
+ return name ?? location
131
+ case . registry( let id, _) :
132
+ return id
133
+ }
134
+ }
135
+ }
0 commit comments