dependabot-cargo 0.384.0 → 0.386.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,7 +41,7 @@ module Dependabot
41
41
  @path_dependency_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
42
42
  @lockfile = T.let(nil, T.nilable(Dependabot::DependencyFile))
43
43
  @toolchain = T.let(nil, T.nilable(Dependabot::DependencyFile))
44
- @config = T.let(nil, T.nilable(Dependabot::DependencyFile))
44
+ @config_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
45
45
  end
46
46
 
47
47
  sig { returns(T.any(String, T.noreturn)) }
@@ -305,11 +305,13 @@ module Dependabot
305
305
 
306
306
  File.write(lockfile.name, replace_ssh_urls(T.must(lockfile.content)))
307
307
  File.write(T.must(toolchain).name, T.must(toolchain).content) if toolchain
308
- config_file = config
309
- return unless config_file
310
-
311
- FileUtils.mkdir_p(File.dirname(config_file.name))
312
- File.write(config_file.name, Helpers.sanitize_cargo_config(T.must(config_file.content)))
308
+ config_files.each do |config_file|
309
+ FileUtils.mkdir_p(File.dirname(config_file.name))
310
+ File.write(
311
+ config_file.name,
312
+ Helpers.sanitize_cargo_config(T.must(config_file.content), file_name: config_file.name)
313
+ )
314
+ end
313
315
  end
314
316
 
315
317
  sig { void }
@@ -390,20 +392,31 @@ module Dependabot
390
392
  TomlRB.dump(parsed_manifest)
391
393
  end
392
394
 
393
- sig { params(parsed_manifest: T::Hash[String, T.untyped]).void }
395
+ sig { params(parsed_manifest: T::Hash[String, T.anything]).void }
394
396
  def pin_target_specific_dependencies!(parsed_manifest)
395
- parsed_manifest.fetch("target", {}).each do |target, t_details|
397
+ toml_table_or_empty(parsed_manifest.fetch("target", {})).each do |target, t_details|
398
+ t_details = toml_table_or_empty(t_details)
396
399
  Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
397
- t_details.fetch(type, {}).each do |name, requirement|
400
+ toml_table_or_empty(t_details.fetch(type, {})).each do |name, requirement|
398
401
  next unless name == dependency.name
399
402
 
400
403
  updated_req = "=#{dependency.version}"
401
404
 
402
- if requirement.is_a?(Hash)
403
- parsed_manifest["target"][target][type][name]["version"] =
405
+ if T.cast(requirement, T.nilable(Object)).is_a?(Hash)
406
+ toml_table_or_empty(
407
+ toml_table_or_empty(
408
+ toml_table_or_empty(
409
+ toml_table_or_empty(parsed_manifest["target"])[target]
410
+ )[type]
411
+ )[name]
412
+ )["version"] =
404
413
  updated_req
405
414
  else
406
- parsed_manifest["target"][target][type][name] = updated_req
415
+ toml_table_or_empty(
416
+ toml_table_or_empty(
417
+ toml_table_or_empty(parsed_manifest["target"])[target]
418
+ )[type]
419
+ )[name] = updated_req
407
420
  end
408
421
  end
409
422
  end
@@ -496,6 +509,12 @@ module Dependabot
496
509
  lockfile_content
497
510
  end
498
511
 
512
+ sig { params(value: T.anything).returns(T::Hash[String, T.anything]) }
513
+ def toml_table_or_empty(value)
514
+ obj = T.cast(value, T.nilable(Object))
515
+ obj.is_a?(Hash) ? obj : {}
516
+ end
517
+
499
518
  sig { returns(String) }
500
519
  def dummy_app_content
501
520
  %{fn main() {\nprintln!("Hello, world!");\n}}
@@ -537,9 +556,12 @@ module Dependabot
537
556
  dependency_files.find { |f| f.name == "rust-toolchain" }
538
557
  end
539
558
 
540
- sig { returns(T.nilable(Dependabot::DependencyFile)) }
541
- def config
542
- @config ||= dependency_files.find { |f| f.name == ".cargo/config.toml" }
559
+ # Cargo merges `.cargo/config.toml` hierarchically (package directory plus
560
+ # every ancestor up to the repo root), so we materialise all of them and
561
+ # let Cargo perform the merge with its own precedence rules.
562
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
563
+ def config_files
564
+ @config_files ||= dependency_files.select { |f| f.name.end_with?(".cargo/config.toml") }
543
565
  end
544
566
 
545
567
  sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
@@ -19,8 +19,8 @@ module Dependabot
19
19
  # These per-registry settings override the global CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS env var,
20
20
  # causing Cargo to look up tokens locally. Since the dependabot proxy handles all registry authentication
21
21
  # transparently, we remove these so Cargo makes plain unauthenticated requests that the proxy can intercept.
22
- sig { params(config_content: String).returns(String) }
23
- def self.sanitize_cargo_config(config_content)
22
+ sig { params(config_content: String, file_name: String).returns(String) }
23
+ def self.sanitize_cargo_config(config_content, file_name: ".cargo/config.toml")
24
24
  parsed = TomlRB.parse(config_content)
25
25
  return config_content unless parsed.is_a?(Hash)
26
26
 
@@ -40,7 +40,7 @@ module Dependabot
40
40
  TomlRB.dump(parsed)
41
41
  rescue TomlRB::Error => e
42
42
  raise Dependabot::DependencyFileNotParseable.new(
43
- ".cargo/config.toml",
43
+ file_name,
44
44
  "Failed to parse Cargo config file: #{e.message}"
45
45
  )
46
46
  end
@@ -109,10 +109,17 @@ module Dependabot
109
109
  ).returns(T::Hash[String, String])
110
110
  end
111
111
  def self.registry_token_env_from_files(dependency_files, credentials)
112
- config_file = dependency_files.find { |f| f.name == ".cargo/config.toml" }
113
- return {} unless config_file
114
-
115
- registry_token_env(T.must(config_file.content), credentials)
112
+ config_files = dependency_files.select { |f| f.name.end_with?(".cargo/config.toml") }
113
+ return {} if config_files.empty?
114
+
115
+ # Cargo gives nearer configs precedence over ancestors on key collisions.
116
+ # A config's `../` depth (how many parent hops appear in its name)
117
+ # indicates how far up the tree it lives, so merge farthest-first and let
118
+ # the nearest config (fewest `../`) win.
119
+ ordered = config_files.sort_by { |f| -f.name.scan("../").count }
120
+ ordered.each_with_object({}) do |config_file, env|
121
+ env.merge!(registry_token_env(T.must(config_file.content), credentials))
122
+ end
116
123
  end
117
124
 
118
125
  # Builds the complete environment variable hash for running cargo commands:
@@ -19,7 +19,7 @@ module Dependabot
19
19
  sig { params(dependency: Dependabot::Dependency, credentials: T::Array[Dependabot::Credential]).void }
20
20
  def initialize(dependency:, credentials:)
21
21
  super
22
- @crates_listing = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
22
+ @crates_listing = T.let(nil, T.nilable(T::Hash[String, T.anything]))
23
23
  end
24
24
 
25
25
  private
@@ -43,7 +43,10 @@ module Dependabot
43
43
  def find_source_from_crates_listing
44
44
  potential_source_urls =
45
45
  SOURCE_KEYS
46
- .filter_map { |key| T.must(crates_listing).dig("crate", key) }
46
+ .filter_map do |key|
47
+ crate = T.cast(T.must(crates_listing)["crate"], T.nilable(T::Hash[String, T.anything]))
48
+ T.cast(crate&.fetch(key, nil), T.nilable(String))
49
+ end
47
50
 
48
51
  source_url = potential_source_urls.find { |url| Source.from_url(url) }
49
52
  Source.from_url(source_url)
@@ -51,18 +54,25 @@ module Dependabot
51
54
 
52
55
  sig { returns(T.nilable(Dependabot::Source)) }
53
56
  def find_source_from_git_url
54
- info = dependency.requirements.filter_map { |r| r[:source] }.first
57
+ info = T.cast(
58
+ dependency.requirements.filter_map { |r| r[:source] }.first,
59
+ T.nilable(T::Hash[T.any(String, Symbol), T.anything])
60
+ )
61
+ return unless info
55
62
 
56
- url = info[:url] || info.fetch("url")
57
- Source.from_url(url)
63
+ url = info[:url] || info["url"]
64
+ Source.from_url(T.cast(url, T.nilable(String)))
58
65
  end
59
66
 
60
- sig { returns(T.nilable(T::Hash[String, T.untyped])) }
67
+ sig { returns(T.nilable(T::Hash[String, T.anything])) }
61
68
  def crates_listing
62
69
  return @crates_listing unless @crates_listing.nil?
63
70
 
64
- info = dependency.requirements.filter_map { |r| r[:source] }.first
65
- index = (info && info[:index]) || CRATES_IO_API
71
+ info = T.cast(
72
+ dependency.requirements.filter_map { |r| r[:source] }.first,
73
+ T.nilable(T::Hash[T.any(String, Symbol), T.anything])
74
+ )
75
+ index = T.cast((info && (info[:index] || info["index"])) || CRATES_IO_API, String)
66
76
  hdrs = build_headers(index, info)
67
77
 
68
78
  url = metadata_fetch_url(dependency, index)
@@ -71,14 +81,18 @@ module Dependabot
71
81
  @crates_listing = parse_response(response, index)
72
82
  end
73
83
 
74
- sig { params(index: String, info: T.nilable(T::Hash[String, T.untyped])).returns(T::Hash[String, String]) }
84
+ sig do
85
+ params(index: String, info: T.nilable(T::Hash[T.any(String, Symbol), T.anything]))
86
+ .returns(T::Hash[String, String])
87
+ end
75
88
  def build_headers(index, info)
76
89
  hdrs = { "User-Agent" => "Dependabot (dependabot.com)" }
77
90
  return hdrs if index == CRATES_IO_API
78
91
 
79
92
  return hdrs if info.nil?
80
93
 
81
- credentials.find { |cred| cred["type"] == "cargo_registry" && cred["registry"] == info["name"] }&.tap do |cred|
94
+ registry_name = T.cast(info["name"] || info[:name], T.nilable(String))
95
+ credentials.find { |cred| cred["type"] == "cargo_registry" && cred["registry"] == registry_name }&.tap do |cred|
82
96
  hdrs["Authorization"] = "Token #{cred['token']}"
83
97
  end
84
98
 
@@ -94,7 +108,7 @@ module Dependabot
94
108
  )
95
109
  end
96
110
 
97
- sig { params(response: Excon::Response, index: String).returns(T::Hash[String, T.untyped]) }
111
+ sig { params(response: Excon::Response, index: String).returns(T::Hash[String, T.anything]) }
98
112
  def parse_response(response, index)
99
113
  if index.start_with?("sparse+")
100
114
  parsed_response = response.body.lines.map { |line| JSON.parse(line) }
@@ -42,23 +42,25 @@ module Dependabot
42
42
  sig { returns(Dependabot::Dependency) }
43
43
  attr_reader :dependency
44
44
 
45
- sig { returns(T::Array[T.untyped]) }
45
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
46
46
  attr_reader :dependency_files
47
47
 
48
- sig { returns(T::Array[T.untyped]) }
48
+ sig { returns(T::Array[Dependabot::Credential]) }
49
49
  attr_reader :credentials
50
50
 
51
51
  sig do
52
52
  returns(T.nilable(Dependabot::Package::PackageDetails))
53
53
  end
54
54
  def fetch
55
- package_releases = crates_listing.fetch("versions", []).reject { |v| v["yanked"] }.map do |release|
55
+ releases = T.cast(crates_listing.fetch("versions", []), T::Array[T::Hash[String, T.anything]])
56
+ package_releases = releases.reject { |v| v["yanked"] }.map do |release|
57
+ created_at = T.cast(release["created_at"], T.nilable(String))
56
58
  package_release(
57
- version: release["num"] || release["vers"],
58
- released_at: release["created_at"] ? Time.parse(release["created_at"]) : nil,
59
- downloads: release["downloads"],
60
- url: "#{CRATES_IO_API}/#{release['dl_path']}",
61
- rust_version: release["rust"]
59
+ version: T.cast(release["num"], T.nilable(String)) || T.cast(release["vers"], String),
60
+ released_at: created_at ? Time.parse(created_at) : nil,
61
+ downloads: T.cast(release["downloads"], T.nilable(Integer)),
62
+ url: "#{CRATES_IO_API}/#{T.cast(release['dl_path'], T.nilable(String))}",
63
+ rust_version: T.cast(release["rust"], T.nilable(String))
62
64
  )
63
65
  end
64
66
 
@@ -68,7 +70,7 @@ module Dependabot
68
70
  private
69
71
 
70
72
  sig do
71
- returns(T.untyped)
73
+ returns(T::Hash[String, T.anything])
72
74
  end
73
75
  def crates_listing
74
76
  return @crates_listing unless @crates_listing.nil?
@@ -86,41 +88,45 @@ module Dependabot
86
88
  response = fetch_response(url, hdrs)
87
89
  return {} if response.status == 404
88
90
 
89
- @crates_listing = T.let(parse_response(response, index), T.nilable(T::Hash[T.untyped, T.untyped]))
91
+ @crates_listing = T.let(parse_response(response, index), T.nilable(T::Hash[String, T.anything]))
90
92
 
91
93
  Dependabot.logger.info("Fetched metadata for #{dependency.name} from #{index} successfully")
92
94
 
93
- @crates_listing
95
+ T.must(@crates_listing)
94
96
  end
95
97
 
96
- sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
98
+ sig { returns(T.nilable(T::Hash[T.any(String, Symbol), T.anything])) }
97
99
  def fetch_dependency_info
98
- dependency.requirements.filter_map { |r| r[:source] }.first
100
+ T.cast(
101
+ dependency.requirements.filter_map { |r| r[:source] }.first,
102
+ T.nilable(T::Hash[T.any(String, Symbol), T.anything])
103
+ )
99
104
  end
100
105
 
101
- sig { params(info: T.untyped).returns(String) }
106
+ sig { params(info: T.nilable(T::Hash[T.any(String, Symbol), T.anything])).returns(String) }
102
107
  def fetch_index(info)
103
- (info && info[:index]) || CRATES_IO_API
108
+ T.cast((info && (info[:index] || info["index"])) || CRATES_IO_API, String)
104
109
  end
105
110
 
106
- sig { returns(T::Hash[T.untyped, T.untyped]) }
111
+ sig { returns(T::Hash[String, String]) }
107
112
  def default_headers
108
113
  { "User-Agent" => "Dependabot (dependabot.com)" }
109
114
  end
110
115
 
111
- sig { params(info: T.untyped).returns(T::Hash[T.untyped, T.untyped]) }
116
+ sig { params(info: T.nilable(T::Hash[T.any(String, Symbol), T.anything])).returns(T::Hash[String, String]) }
112
117
  def auth_headers(info)
118
+ registry_name = T.cast(info && (info[:name] || info["name"]), T.nilable(String))
113
119
  registry_creds = credentials.find do |cred|
114
- cred["type"] == "cargo_registry" && cred["registry"] == info[:name]
120
+ cred["type"] == "cargo_registry" && cred["registry"] == registry_name
115
121
  end
116
122
 
117
- return {} if registry_creds.nil?
123
+ return {} unless registry_creds
118
124
 
119
125
  token = registry_creds["token"] || "placeholder_token"
120
126
  { "Authorization" => token }
121
127
  end
122
128
 
123
- sig { params(url: String, headers: T.untyped).returns(Excon::Response) }
129
+ sig { params(url: String, headers: T::Hash[String, String]).returns(Excon::Response) }
124
130
  def fetch_response(url, headers)
125
131
  Excon.get(
126
132
  url,
@@ -129,7 +135,7 @@ module Dependabot
129
135
  )
130
136
  end
131
137
 
132
- sig { params(response: Excon::Response, index: T.untyped).returns(T::Hash[T.untyped, T.untyped]) }
138
+ sig { params(response: Excon::Response, index: String).returns(T::Hash[String, T.anything]) }
133
139
  def parse_response(response, index)
134
140
  if index.start_with?("sparse+")
135
141
  parsed_response = response.body.lines
@@ -148,7 +154,7 @@ module Dependabot
148
154
  end
149
155
  end
150
156
 
151
- sig { params(dependency: T.untyped, index: T.untyped).returns(String) }
157
+ sig { params(dependency: Dependabot::Dependency, index: String).returns(String) }
152
158
  def metadata_fetch_url(dependency, index)
153
159
  return "#{index}/#{dependency.name}" if index == CRATES_IO_API
154
160
 
@@ -123,9 +123,9 @@ module Dependabot
123
123
  TomlRB.dump(parsed_manifest)
124
124
  end
125
125
 
126
- sig { params(parsed_manifest: T::Hash[String, T.untyped], filename: String).void }
126
+ sig { params(parsed_manifest: T::Hash[String, T.anything], filename: String).void }
127
127
  def replace_req_on_target_specific_deps!(parsed_manifest, filename)
128
- parsed_manifest.fetch("target", {}).each do |target, _|
128
+ toml_table_or_empty(parsed_manifest.fetch("target", {})).each do |target, _|
129
129
  Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
130
130
  dependency_names = dependency_names_for_type_and_target(
131
131
  parsed_manifest,
@@ -134,35 +134,39 @@ module Dependabot
134
134
  )
135
135
 
136
136
  dependency_names.each do |name|
137
- req = parsed_manifest.dig("target", target, type, name)
137
+ req = target_dependency_declaration(parsed_manifest, target, type, name)
138
138
 
139
139
  updated_req = temporary_requirement_for_resolution(filename)
140
140
 
141
141
  if req.is_a?(Hash)
142
- parsed_manifest["target"][target][type][name]["version"] =
143
- updated_req
142
+ target_dependency_table(parsed_manifest, target, type, name)["version"] = updated_req
144
143
  else
145
- parsed_manifest["target"][target][type][name] = updated_req
144
+ target_dependency_type_table(parsed_manifest, target, type)[name] = updated_req
146
145
  end
147
146
  end
148
147
  end
149
148
  end
150
149
  end
151
150
 
152
- sig { params(parsed_manifest: T::Hash[String, T.untyped], filename: String).void }
151
+ sig { params(parsed_manifest: T::Hash[String, T.anything], filename: String).void }
153
152
  def replace_req_on_workspace_deps!(parsed_manifest, filename)
154
- workspace = parsed_manifest.fetch("workspace", {})
155
- workspace_deps = workspace.fetch("dependencies", {})
153
+ workspace = toml_table_or_empty(parsed_manifest.fetch("workspace", {}))
154
+ workspace_deps = toml_table_or_empty(workspace.fetch("dependencies", {}))
156
155
 
157
156
  workspace_deps.each do |name, req|
157
+ req = dependency_declaration(req)
158
158
  next unless dependency.name == name_from_declaration(name, req)
159
159
 
160
160
  updated_req = temporary_requirement_for_resolution(filename)
161
161
 
162
162
  if req.is_a?(Hash)
163
- parsed_manifest["workspace"]["dependencies"][name]["version"] = updated_req
163
+ toml_table_or_empty(
164
+ toml_table_or_empty(
165
+ toml_table_or_empty(parsed_manifest["workspace"])["dependencies"]
166
+ )[name]
167
+ )["version"] = updated_req
164
168
  else
165
- parsed_manifest["workspace"]["dependencies"][name] = updated_req
169
+ toml_table_or_empty(toml_table_or_empty(parsed_manifest["workspace"])["dependencies"])[name] = updated_req
166
170
  end
167
171
  end
168
172
  end
@@ -173,13 +177,14 @@ module Dependabot
173
177
 
174
178
  Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
175
179
  dependency_names_for_type(parsed_manifest, type).each do |name|
176
- req = parsed_manifest.dig(type, name)
180
+ req = T.cast(parsed_manifest.dig(type, name), T.any(String, T::Hash[String, T.anything]))
177
181
  next unless req.is_a?(Hash)
178
182
  next unless [req["tag"], req["rev"]].compact.uniq.one?
179
183
 
180
- parsed_manifest[type][name]["tag"] = replacement_git_pin if req["tag"]
184
+ dependency_options = toml_table_or_empty(toml_table_or_empty(parsed_manifest[type])[name])
185
+ dependency_options["tag"] = replacement_git_pin if req["tag"]
181
186
 
182
- parsed_manifest[type][name]["rev"] = replacement_git_pin if req["rev"]
187
+ dependency_options["rev"] = replacement_git_pin if req["rev"]
183
188
  end
184
189
  end
185
190
 
@@ -188,9 +193,9 @@ module Dependabot
188
193
  TomlRB.dump(parsed_manifest)
189
194
  end
190
195
 
191
- sig { params(parsed_manifest: T::Hash[String, T.untyped]).void }
196
+ sig { params(parsed_manifest: T::Hash[String, T.anything]).void }
192
197
  def replace_git_pin_on_target_specific_deps!(parsed_manifest)
193
- parsed_manifest.fetch("target", {}).each do |target, _|
198
+ toml_table_or_empty(parsed_manifest.fetch("target", {})).each do |target, _|
194
199
  Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
195
200
  dependency_names = dependency_names_for_type_and_target(
196
201
  parsed_manifest,
@@ -199,19 +204,15 @@ module Dependabot
199
204
  )
200
205
 
201
206
  dependency_names.each do |name|
202
- req = parsed_manifest.dig("target", target, type, name)
207
+ req = target_dependency_declaration(parsed_manifest, target, type, name)
203
208
  next unless req.is_a?(Hash)
204
209
  next unless [req["tag"], req["rev"]].compact.uniq.one?
205
210
 
206
- if req["tag"]
207
- parsed_manifest["target"][target][type][name]["tag"] =
208
- replacement_git_pin
209
- end
211
+ target_dependency_table(parsed_manifest, target, type, name)["tag"] = replacement_git_pin if req["tag"]
210
212
 
211
- if req["rev"]
212
- parsed_manifest["target"][target][type][name]["rev"] =
213
- replacement_git_pin
214
- end
213
+ next unless req["rev"]
214
+
215
+ target_dependency_table(parsed_manifest, target, type, name)["rev"] = replacement_git_pin
215
216
  end
216
217
  end
217
218
  end
@@ -222,12 +223,12 @@ module Dependabot
222
223
  parsed_manifest = TomlRB.parse(content)
223
224
 
224
225
  Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
225
- (parsed_manifest[type] || {}).each do |_, details|
226
- next unless details.is_a?(Hash)
227
- next unless details["git"]
226
+ toml_table_or_empty(parsed_manifest[type]).each do |_, details|
227
+ details = toml_table_or_empty(details)
228
+ git = T.cast(details.fetch("git", nil), T.nilable(String))
229
+ next unless git
228
230
 
229
- details["git"] = details["git"]
230
- .gsub(%r{ssh://git@(.*?)/}, 'https://\1/')
231
+ details["git"] = git.gsub(%r{ssh://git@(.*?)/}, 'https://\1/')
231
232
  end
232
233
  end
233
234
 
@@ -283,17 +284,29 @@ module Dependabot
283
284
  def git_dependency_version
284
285
  return unless lockfile
285
286
 
286
- TomlRB.parse(T.must(lockfile).content)
287
- .fetch("package", [])
288
- .select { |p| p["name"] == dependency.name }
289
- .find { |p| p["source"].end_with?(dependency.version) }
290
- .fetch("version")
287
+ version = dependency.version
288
+ return unless version
289
+
290
+ packages = T.cast(
291
+ TomlRB.parse(T.must(lockfile).content).fetch("package", []),
292
+ T::Array[T::Hash[String, T.anything]]
293
+ )
294
+ package = packages
295
+ .select { |p| T.cast(p["name"], T.nilable(String)) == dependency.name }
296
+ .find do |p|
297
+ source = T.cast(p["source"], T.nilable(String))
298
+ source&.end_with?(version)
299
+ end
300
+ return unless package
301
+
302
+ T.cast(package.fetch("version"), String)
291
303
  end
292
304
 
293
- sig { params(parsed_manifest: T::Hash[String, T.untyped], type: String).returns(T::Array[String]) }
305
+ sig { params(parsed_manifest: T::Hash[String, T.anything], type: String).returns(T::Array[String]) }
294
306
  def dependency_names_for_type(parsed_manifest, type)
295
307
  names = []
296
- parsed_manifest.fetch(type, {}).each do |nm, req|
308
+ toml_table_or_empty(parsed_manifest.fetch(type, {})).each do |nm, req|
309
+ req = dependency_declaration(req)
297
310
  next unless dependency.name == name_from_declaration(nm, req)
298
311
 
299
312
  names << nm
@@ -302,11 +315,13 @@ module Dependabot
302
315
  end
303
316
 
304
317
  sig do
305
- params(parsed_manifest: T::Hash[String, T.untyped], type: String, target: String).returns(T::Array[String])
318
+ params(parsed_manifest: T::Hash[String, T.anything], type: String, target: String).returns(T::Array[String])
306
319
  end
307
320
  def dependency_names_for_type_and_target(parsed_manifest, type, target)
308
321
  names = []
309
- (parsed_manifest.dig("target", target, type) || {}).each do |nm, req|
322
+ target_details = toml_table_or_empty(toml_table_or_empty(parsed_manifest["target"])[target])
323
+ toml_table_or_empty(target_details[type]).each do |nm, req|
324
+ req = dependency_declaration(req)
310
325
  next unless dependency.name == name_from_declaration(nm, req)
311
326
 
312
327
  names << nm
@@ -314,13 +329,63 @@ module Dependabot
314
329
  names
315
330
  end
316
331
 
317
- sig { params(name: String, declaration: T.untyped).returns(String) }
332
+ sig { params(name: String, declaration: T.any(String, T::Hash[String, T.anything])).returns(String) }
318
333
  def name_from_declaration(name, declaration)
319
334
  return name if declaration.is_a?(String)
320
335
 
321
- raise "Unexpected dependency declaration: #{declaration}" unless declaration.is_a?(Hash)
336
+ T.cast(declaration.fetch("package", name), String)
337
+ end
338
+
339
+ sig do
340
+ params(
341
+ parsed_manifest: T::Hash[String, T.anything],
342
+ target: String,
343
+ type: String
344
+ ).returns(T::Hash[String, T.anything])
345
+ end
346
+ def target_dependency_type_table(parsed_manifest, target, type)
347
+ target_details = toml_table_or_empty(toml_table_or_empty(parsed_manifest["target"])[target])
348
+ toml_table_or_empty(target_details[type])
349
+ end
350
+
351
+ sig do
352
+ params(
353
+ parsed_manifest: T::Hash[String, T.anything],
354
+ target: String,
355
+ type: String,
356
+ name: String
357
+ ).returns(T.any(String, T::Hash[String, T.anything]))
358
+ end
359
+ def target_dependency_declaration(parsed_manifest, target, type, name)
360
+ T.cast(
361
+ target_dependency_type_table(parsed_manifest, target, type)[name],
362
+ T.any(String, T::Hash[String, T.anything])
363
+ )
364
+ end
365
+
366
+ sig do
367
+ params(
368
+ parsed_manifest: T::Hash[String, T.anything],
369
+ target: String,
370
+ type: String,
371
+ name: String
372
+ ).returns(T::Hash[String, T.anything])
373
+ end
374
+ def target_dependency_table(parsed_manifest, target, type, name)
375
+ toml_table_or_empty(target_dependency_type_table(parsed_manifest, target, type)[name])
376
+ end
377
+
378
+ sig { params(value: T.anything).returns(T::Hash[String, T.anything]) }
379
+ def toml_table_or_empty(value)
380
+ obj = T.cast(value, T.nilable(Object))
381
+ obj.is_a?(Hash) ? obj : {}
382
+ end
383
+
384
+ sig { params(value: T.anything).returns(T.any(String, T::Hash[String, T.anything])) }
385
+ def dependency_declaration(value)
386
+ return T.cast(value, String) if T.cast(value, T.nilable(Object)).is_a?(String)
322
387
 
323
- declaration.fetch("package", name)
388
+ T.cast(value, T::Hash[String, T.anything])
324
389
  end
325
390
 
326
391
  sig { returns(T::Array[Dependabot::DependencyFile]) }
@@ -36,7 +36,7 @@ module Dependabot
36
36
  sig do
37
37
  params(
38
38
  requirements: T::Array[Dependabot::DependencyRequirement],
39
- updated_source: T.nilable(T::Hash[T.any(String, Symbol), T.untyped]),
39
+ updated_source: T.nilable(T::Hash[T.any(String, Symbol), T.anything]),
40
40
  update_strategy: Dependabot::RequirementsUpdateStrategy,
41
41
  target_version: T.nilable(T.any(String, Gem::Version))
42
42
  ).void
@@ -51,7 +51,7 @@ module Dependabot
51
51
  requirements.map { |req| Dependabot::DependencyRequirement.create(req) },
52
52
  T::Array[Dependabot::DependencyRequirement]
53
53
  )
54
- @updated_source = T.let(updated_source, T.nilable(T::Hash[T.any(String, Symbol), T.untyped]))
54
+ @updated_source = T.let(updated_source, T.nilable(T::Hash[T.any(String, Symbol), T.anything]))
55
55
  @update_strategy = T.let(update_strategy, Dependabot::RequirementsUpdateStrategy)
56
56
 
57
57
  check_update_strategy
@@ -87,7 +87,7 @@ module Dependabot
87
87
  sig { returns(T::Array[Dependabot::DependencyRequirement]) }
88
88
  attr_reader :requirements
89
89
 
90
- sig { returns(T.nilable(T::Hash[T.any(String, Symbol), T.untyped])) }
90
+ sig { returns(T.nilable(T::Hash[T.any(String, Symbol), T.anything])) }
91
91
  attr_reader :updated_source
92
92
 
93
93
  sig { returns(Dependabot::RequirementsUpdateStrategy) }