dependabot-uv 0.332.0 → 0.333.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.
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -12,22 +12,23 @@ module Dependabot
12
12
  class Requirement < Dependabot::Requirement
13
13
  extend T::Sig
14
14
 
15
- OR_SEPARATOR = /(?<=[a-zA-Z0-9)*])\s*\|+/
15
+ OR_SEPARATOR = T.let(/(?<=[a-zA-Z0-9)*])\s*\|+/, Regexp)
16
16
 
17
17
  # Add equality and arbitrary-equality matchers
18
- OPS = OPS.merge(
19
- "==" => ->(v, r) { v == r },
20
- "===" => ->(v, r) { v.to_s == r.to_s }
21
- )
18
+ OPS = T.let(OPS.merge(
19
+ "==" => ->(v, r) { v == r },
20
+ "===" => ->(v, r) { v.to_s == r.to_s }
21
+ ), T::Hash[String, T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T.untyped)])
22
22
 
23
23
  quoted = OPS.keys.sort_by(&:length).reverse
24
24
  .map { |k| Regexp.quote(k) }.join("|")
25
25
  version_pattern = Uv::Version::VERSION_PATTERN
26
26
 
27
- PATTERN_RAW = "\\s*(?<op>#{quoted})?\\s*(?<version>#{version_pattern})\\s*".freeze
28
- PATTERN = /\A#{PATTERN_RAW}\z/
29
- PARENS_PATTERN = /\A\(([^)]+)\)\z/
27
+ PATTERN_RAW = T.let("\\s*(?<op>#{quoted})?\\s*(?<version>#{version_pattern})\\s*".freeze, String)
28
+ PATTERN = T.let(/\A#{PATTERN_RAW}\z/, Regexp)
29
+ PARENS_PATTERN = T.let(/\A\(([^)]+)\)\z/, Regexp)
30
30
 
31
+ sig { params(obj: T.any(Gem::Version, String)).returns([String, Gem::Version]) }
31
32
  def self.parse(obj)
32
33
  return ["=", Uv::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
33
34
 
@@ -63,6 +64,7 @@ module Dependabot
63
64
  end
64
65
  end
65
66
 
67
+ sig { params(requirements: T.nilable(T.any(String, T::Array[String]))).void }
66
68
  def initialize(*requirements)
67
69
  requirements = requirements.flatten.flat_map do |req_string|
68
70
  next if req_string.nil?
@@ -78,20 +80,23 @@ module Dependabot
78
80
  super(requirements)
79
81
  end
80
82
 
83
+ sig { params(version: T.any(Gem::Version, String)).returns(T::Boolean) }
81
84
  def satisfied_by?(version)
82
85
  version = Uv::Version.new(version.to_s)
83
86
 
84
- requirements.all? { |op, rv| (OPS[op] || OPS["="]).call(version, rv) }
87
+ requirements.all? { |op, rv| T.must(OPS[op] || OPS["="]).call(version, rv) }
85
88
  end
86
89
 
90
+ sig { returns(T::Boolean) }
87
91
  def exact?
88
- return false unless @requirements.size == 1
92
+ return false unless requirements.size == 1
89
93
 
90
- %w(= == ===).include?(@requirements[0][0])
94
+ %w(= == ===).include?(requirements[0][0])
91
95
  end
92
96
 
93
97
  private
94
98
 
99
+ sig { params(req_string: T.nilable(String)).returns(T.nilable(T.any(String, T::Array[String]))) }
95
100
  def convert_python_constraint_to_ruby_constraint(req_string)
96
101
  return nil if req_string.nil? || req_string.strip.empty?
97
102
  return nil if req_string == "*"
@@ -111,6 +116,7 @@ module Dependabot
111
116
 
112
117
  # Poetry uses ~ requirements.
113
118
  # https://github.com/sdispater/poetry#tilde-requirements
119
+ sig { params(req_string: String).returns(String) }
114
120
  def convert_tilde_req(req_string)
115
121
  version = req_string.gsub(/^~\>?/, "")
116
122
  parts = version.split(".")
@@ -120,44 +126,47 @@ module Dependabot
120
126
 
121
127
  # Poetry uses ^ requirements
122
128
  # https://github.com/sdispater/poetry#caret-requirement
129
+ sig { params(req_string: String).returns(T::Array[String]) }
123
130
  def convert_caret_req(req_string)
124
131
  version = req_string.gsub(/^\^/, "")
125
132
  parts = version.split(".")
126
- parts.fill(0, parts.length...3)
133
+ parts.fill("0", parts.length...3)
127
134
  first_non_zero = parts.find { |d| d != "0" }
128
135
  first_non_zero_index =
129
136
  first_non_zero ? parts.index(first_non_zero) : parts.count - 1
130
137
  upper_bound = parts.map.with_index do |part, i|
131
- if i < first_non_zero_index then part
138
+ if i < T.must(first_non_zero_index) then part
132
139
  elsif i == first_non_zero_index then (part.to_i + 1).to_s
133
140
  # .dev has lowest precedence: https://packaging.python.org/en/latest/specifications/version-specifiers/#summary-of-permitted-suffixes-and-relative-ordering
134
- elsif i > first_non_zero_index && i == 2 then "0.dev"
141
+ elsif i > T.must(first_non_zero_index) && i == 2 then "0.dev"
135
142
  else
136
- 0
143
+ "0"
137
144
  end
138
145
  end.join(".")
139
146
 
140
147
  [">= #{version}", "< #{upper_bound}"]
141
148
  end
142
149
 
150
+ sig { params(req_string: String).returns(String) }
143
151
  def convert_wildcard(req_string)
144
152
  # NOTE: This isn't perfect. It replaces the "!= 1.0.*" case with
145
153
  # "!= 1.0.0". There's no way to model this correctly in Ruby :'(
146
154
  quoted_ops = OPS.keys.sort_by(&:length).reverse
147
155
  .map { |k| Regexp.quote(k) }.join("|")
148
- op = req_string.match(/\A\s*(#{quoted_ops})?/)
149
- .captures.first.to_s&.strip
156
+ op_match = req_string.match(/\A\s*(#{quoted_ops})?/)
157
+ op = op_match&.captures&.first.to_s.strip
150
158
  exact_op = ["", "=", "==", "==="].include?(op)
151
159
 
152
160
  req_string.strip
153
161
  .split(".")
154
- .first(req_string.split(".").index { |s| s.include?("*") } + 1)
162
+ .first(T.must(req_string.split(".").index { |s| s.include?("*") }) + 1)
155
163
  .join(".")
156
164
  .gsub(/\*(?!$)/, "0")
157
165
  .gsub(/\*$/, "0.dev")
158
166
  .tap { |s| exact_op ? s.gsub!(/^(?<!!)=*/, "~>") : s }
159
167
  end
160
168
 
169
+ sig { params(req_string: String).returns(T.any(String, T::Array[String])) }
161
170
  def convert_exact(req_string)
162
171
  arbitrary_equality = req_string.start_with?("===")
163
172
  cleaned_version = req_string.gsub(/^=+/, "").strip
@@ -1,6 +1,8 @@
1
- # typed: true
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  require "dependabot/uv/version"
5
7
  require "dependabot/uv/requirement"
6
8
  require "dependabot/uv/update_checker"
@@ -9,6 +11,16 @@ module Dependabot
9
11
  module Uv
10
12
  class UpdateChecker
11
13
  class LockFileResolver
14
+ extend T::Sig
15
+
16
+ sig do
17
+ params(
18
+ dependency: Dependabot::Dependency,
19
+ dependency_files: T::Array[Dependabot::DependencyFile],
20
+ credentials: T::Array[Dependabot::Credential],
21
+ repo_contents_path: T.nilable(String)
22
+ ).void
23
+ end
12
24
  def initialize(dependency:, dependency_files:, credentials:, repo_contents_path: nil)
13
25
  @dependency = dependency
14
26
  @dependency_files = dependency_files
@@ -16,6 +28,7 @@ module Dependabot
16
28
  @repo_contents_path = repo_contents_path
17
29
  end
18
30
 
31
+ sig { params(requirement: T.nilable(String)).returns(T.nilable(Dependabot::Uv::Version)) }
19
32
  def latest_resolvable_version(requirement:)
20
33
  return nil unless requirement
21
34
 
@@ -28,19 +41,30 @@ module Dependabot
28
41
  nil
29
42
  end
30
43
 
31
- def resolvable?(*)
44
+ sig { params(_version: T.untyped).returns(T::Boolean) }
45
+ def resolvable?(_version)
46
+ # Always return true since we don't actually attempt resolution
47
+ # This is just a placeholder implementation
32
48
  true
33
49
  end
34
50
 
51
+ sig { returns(T.nilable(Dependabot::Uv::Version)) }
35
52
  def lowest_resolvable_security_fix_version
36
53
  nil
37
54
  end
38
55
 
39
56
  private
40
57
 
58
+ sig { returns(Dependabot::Dependency) }
41
59
  attr_reader :dependency
60
+
61
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
42
62
  attr_reader :dependency_files
63
+
64
+ sig { returns(T::Array[Dependabot::Credential]) }
43
65
  attr_reader :credentials
66
+
67
+ sig { returns(T.nilable(String)) }
44
68
  attr_reader :repo_contents_path
45
69
  end
46
70
  end
@@ -1,7 +1,9 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "open3"
5
+ require "sorbet-runtime"
6
+
5
7
  require "dependabot/dependency"
6
8
  require "dependabot/uv/requirement_parser"
7
9
  require "dependabot/uv/file_fetcher"
@@ -22,34 +24,60 @@ module Dependabot
22
24
  # This class does version resolution for pip-compile. Its approach is:
23
25
  # - Unlock the dependency we're checking in the requirements.in file
24
26
  # - Run `pip-compile` and see what the result is
27
+ # rubocop:disable Metrics/ClassLength
25
28
  class PipCompileVersionResolver
26
- GIT_DEPENDENCY_UNREACHABLE_REGEX = /git clone --filter=blob:none --quiet (?<url>[^\s]+).* /
27
- GIT_REFERENCE_NOT_FOUND_REGEX = /Did not find branch or tag '(?<tag>[^\n"]+)'/m
28
- NATIVE_COMPILATION_ERROR =
29
- "pip._internal.exceptions.InstallationSubprocessError: Getting requirements to build wheel exited with 1"
29
+ extend T::Sig
30
+
31
+ GIT_DEPENDENCY_UNREACHABLE_REGEX = T.let(/git clone --filter=blob:none --quiet (?<url>[^\s]+).* /, Regexp)
32
+ GIT_REFERENCE_NOT_FOUND_REGEX = T.let(/Did not find branch or tag '(?<tag>[^\n"]+)'/m, Regexp)
33
+ NATIVE_COMPILATION_ERROR = T.let(
34
+ "pip._internal.exceptions.InstallationSubprocessError: Getting requirements to build wheel exited with 1",
35
+ String
36
+ )
30
37
  # See https://packaging.python.org/en/latest/tutorials/packaging-projects/#configuring-metadata
31
- PYTHON_PACKAGE_NAME_REGEX = /[A-Za-z0-9_\-]+/
32
- RESOLUTION_IMPOSSIBLE_ERROR = "ResolutionImpossible"
33
- ERROR_REGEX = /(?<=ERROR\:\W).*$/
34
- UV_UNRESOLVABLE_REGEX = / × No solution found when resolving dependencies:[\s\S]*$/
38
+ PYTHON_PACKAGE_NAME_REGEX = T.let(/[A-Za-z0-9_\-]+/, Regexp)
39
+ RESOLUTION_IMPOSSIBLE_ERROR = T.let("ResolutionImpossible", String)
40
+ ERROR_REGEX = T.let(/(?<=ERROR\:\W).*$/, Regexp)
41
+ UV_UNRESOLVABLE_REGEX = T.let(/ × No solution found when resolving dependencies:[\s\S]*$/, Regexp)
35
42
 
43
+ sig { returns(Dependabot::Dependency) }
36
44
  attr_reader :dependency
45
+
46
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
37
47
  attr_reader :dependency_files
48
+
49
+ sig { returns(T::Array[Dependabot::Credential]) }
38
50
  attr_reader :credentials
51
+
52
+ sig { returns(T.nilable(String)) }
39
53
  attr_reader :repo_contents_path
54
+
55
+ sig { returns(PipCompileErrorHandler) }
40
56
  attr_reader :error_handler
41
57
 
58
+ sig do
59
+ params(
60
+ dependency: Dependabot::Dependency,
61
+ dependency_files: T::Array[Dependabot::DependencyFile],
62
+ credentials: T::Array[Dependabot::Credential],
63
+ repo_contents_path: T.nilable(String)
64
+ ).void
65
+ end
42
66
  def initialize(dependency:, dependency_files:, credentials:, repo_contents_path:)
43
- @dependency = dependency
44
- @dependency_files = dependency_files
45
- @credentials = credentials
46
- @repo_contents_path = repo_contents_path
47
- @build_isolation = true
48
- @error_handler = PipCompileErrorHandler.new
67
+ @dependency = T.let(dependency, Dependabot::Dependency)
68
+ @dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
69
+ @credentials = T.let(credentials, T::Array[Dependabot::Credential])
70
+ @repo_contents_path = T.let(repo_contents_path, T.nilable(String))
71
+ @build_isolation = T.let(true, T::Boolean)
72
+ @error_handler = T.let(PipCompileErrorHandler.new, PipCompileErrorHandler)
49
73
  end
50
74
 
75
+ sig { params(requirement: T.nilable(String)).returns(T.nilable(Dependabot::Uv::Version)) }
51
76
  def latest_resolvable_version(requirement: nil)
52
- @latest_resolvable_version_string ||= {}
77
+ @latest_resolvable_version_string ||= T.let(
78
+ {},
79
+ T.nilable(T::Hash[T.nilable(String), T.nilable(Dependabot::Uv::Version)])
80
+ )
53
81
  return @latest_resolvable_version_string[requirement] if @latest_resolvable_version_string.key?(requirement)
54
82
 
55
83
  version_string =
@@ -59,9 +87,10 @@ module Dependabot
59
87
  version_string.nil? ? nil : Uv::Version.new(version_string)
60
88
  end
61
89
 
90
+ sig { params(version: Gem::Version).returns(T::Boolean) }
62
91
  def resolvable?(version:)
63
- @resolvable ||= {}
64
- return @resolvable[version] if @resolvable.key?(version)
92
+ @resolvable ||= T.let({}, T.nilable(T::Hash[Gem::Version, T::Boolean]))
93
+ return T.must(@resolvable[version]) if @resolvable.key?(version)
65
94
 
66
95
  @resolvable[version] = if latest_resolvable_version(requirement: "==#{version}")
67
96
  true
@@ -72,6 +101,7 @@ module Dependabot
72
101
 
73
102
  private
74
103
 
104
+ sig { params(requirement: T.nilable(String)).returns(T.nilable(String)) }
75
105
  def fetch_latest_resolvable_version_string(requirement:)
76
106
  SharedHelpers.in_a_temporary_directory do
77
107
  SharedHelpers.with_git_configured(credentials: credentials) do
@@ -90,6 +120,7 @@ module Dependabot
90
120
  end
91
121
  end
92
122
 
123
+ sig { params(filename: String).returns(T::Boolean) }
93
124
  def compile_file(filename)
94
125
  # Shell out to pip-compile.
95
126
  # This is slow, as pip-compile needs to do installs.
@@ -126,17 +157,24 @@ module Dependabot
126
157
  end
127
158
 
128
159
  handle_pip_compile_errors(e.message)
160
+ false
129
161
  end
130
162
 
163
+ sig { params(error: Dependabot::SharedHelpers::HelperSubprocessFailed).returns(T::Boolean) }
131
164
  def compilation_error?(error)
132
165
  error.message.include?(NATIVE_COMPILATION_ERROR)
133
166
  end
134
167
 
135
168
  # rubocop:disable Metrics/AbcSize
136
169
  # rubocop:disable Metrics/PerceivedComplexity
170
+ sig { params(message: String).returns(T.nilable(String)) }
137
171
  def handle_pip_compile_errors(message)
138
172
  if message.include?("No solution found when resolving dependencies")
139
- raise DependencyFileNotResolvable, message.scan(UV_UNRESOLVABLE_REGEX).last
173
+ match_result = message.scan(UV_UNRESOLVABLE_REGEX).last
174
+ if match_result
175
+ error_message = match_result.is_a?(Array) ? match_result.join : match_result
176
+ raise DependencyFileNotResolvable, error_message
177
+ end
140
178
  end
141
179
 
142
180
  check_original_requirements_resolvable if message.include?(RESOLUTION_IMPOSSIBLE_ERROR)
@@ -153,21 +191,23 @@ module Dependabot
153
191
  end
154
192
 
155
193
  if message.match?(GIT_REFERENCE_NOT_FOUND_REGEX)
156
- tag = message.match(GIT_REFERENCE_NOT_FOUND_REGEX).named_captures.fetch("tag")
157
- constraints_section = message.split("Finding the best candidates:").first
194
+ tag = T.must(T.must(message.match(GIT_REFERENCE_NOT_FOUND_REGEX)).named_captures.fetch("tag"))
195
+ constraints_section = T.must(message.split("Finding the best candidates:").first)
158
196
  egg_regex = /#{Regexp.escape(tag)}#egg=(#{PYTHON_PACKAGE_NAME_REGEX})/
159
197
  name_match = constraints_section.scan(egg_regex)
160
198
 
161
199
  # We can determine the name of the package from another part of the logger output if it has a unique tag
162
- raise GitDependencyReferenceNotFound, name_match.first.first if name_match.length == 1
200
+ if name_match.length == 1 && name_match.first.is_a?(Array)
201
+ raise GitDependencyReferenceNotFound, T.must(T.cast(T.must(name_match.first), T::Array[String]).first)
202
+ end
163
203
 
164
204
  raise GitDependencyReferenceNotFound, "(unknown package at #{tag})"
165
205
  end
166
206
 
167
207
  if message.match?(GIT_DEPENDENCY_UNREACHABLE_REGEX)
168
- url = message.match(GIT_DEPENDENCY_UNREACHABLE_REGEX)
169
- .named_captures.fetch("url")
170
- raise GitDependenciesNotReachable, url
208
+ url = T.must(message.match(GIT_DEPENDENCY_UNREACHABLE_REGEX))
209
+ .named_captures.fetch("url")
210
+ raise GitDependenciesNotReachable, T.must(url)
171
211
  end
172
212
 
173
213
  raise Dependabot::OutOfDisk if message.end_with?("[Errno 28] No space left on device")
@@ -185,6 +225,7 @@ module Dependabot
185
225
  # Note: We raise errors from this method, rather than returning a
186
226
  # boolean, so that all deps for this repo will raise identical
187
227
  # errors when failing to update
228
+ sig { returns(T::Boolean) }
188
229
  def check_original_requirements_resolvable
189
230
  SharedHelpers.in_a_temporary_directory do
190
231
  SharedHelpers.with_git_configured(credentials: credentials) do
@@ -216,12 +257,14 @@ module Dependabot
216
257
  end
217
258
  end
218
259
 
219
- def run_command(command, env: python_env, fingerprint:)
260
+ sig { params(command: String, fingerprint: String, env: T::Hash[String, String]).void }
261
+ def run_command(command, fingerprint:, env: python_env)
220
262
  SharedHelpers.run_shell_command(command, env: env, fingerprint: fingerprint, stderr_to_stdout: true)
221
263
  rescue SharedHelpers::HelperSubprocessFailed => e
222
264
  handle_pip_compile_errors(e.message)
223
265
  end
224
266
 
267
+ sig { params(options: String).returns(String) }
225
268
  def pip_compile_options_fingerprint(options)
226
269
  options.sub(
227
270
  /--output-file=\S+/, "--output-file=<output_file>"
@@ -232,6 +275,7 @@ module Dependabot
232
275
  )
233
276
  end
234
277
 
278
+ sig { params(filename: String).returns(String) }
235
279
  def pip_compile_options(filename)
236
280
  options = @build_isolation ? ["--build-isolation"] : ["--no-build-isolation"]
237
281
  options += pip_compile_index_options
@@ -247,6 +291,7 @@ module Dependabot
247
291
  options.join(" ")
248
292
  end
249
293
 
294
+ sig { returns(T::Array[String]) }
250
295
  def pip_compile_index_options
251
296
  credentials
252
297
  .select { |cred| cred["type"] == "python_index" }
@@ -261,6 +306,7 @@ module Dependabot
261
306
  end
262
307
  end
263
308
 
309
+ sig { params(command: String, fingerprint: String).void }
264
310
  def run_pip_compile_command(command, fingerprint:)
265
311
  run_command(
266
312
  "pyenv local #{language_version_manager.python_major_minor}",
@@ -270,38 +316,43 @@ module Dependabot
270
316
  run_command(command, fingerprint: fingerprint)
271
317
  end
272
318
 
319
+ # rubocop:disable Metrics/AbcSize
320
+ sig { params(requirements_file: Dependabot::DependencyFile).returns(T::Array[String]) }
273
321
  def uv_pip_compile_options_from_compiled_file(requirements_file)
274
322
  options = []
275
323
 
276
- options << "--no-emit-index-url" unless requirements_file.content.include?("index-url http")
324
+ options << "--no-emit-index-url" unless T.must(requirements_file.content).include?("index-url http")
277
325
 
278
- options << "--generate-hashes" if requirements_file.content.include?("--hash=sha")
326
+ options << "--generate-hashes" if T.must(requirements_file.content).include?("--hash=sha")
279
327
 
280
- options << "--no-annotate" unless requirements_file.content.include?("# via ")
328
+ options << "--no-annotate" unless T.must(requirements_file.content).include?("# via ")
281
329
 
282
- options << "--pre" if requirements_file.content.include?("--pre")
330
+ options << "--pre" if T.must(requirements_file.content).include?("--pre")
283
331
 
284
- options << "--no-strip-extras" if requirements_file.content.include?("--no-strip-extras")
332
+ options << "--no-strip-extras" if T.must(requirements_file.content).include?("--no-strip-extras")
285
333
 
286
- if requirements_file.content.include?("--no-binary") || requirements_file.content.include?("--only-binary")
334
+ if T.must(requirements_file.content).include?("--no-binary") ||
335
+ T.must(requirements_file.content).include?("--only-binary")
287
336
  options << "--emit-build-options"
288
337
  end
289
338
 
290
- if (resolver = FileUpdater::CompileFileUpdater::RESOLVER_REGEX.match(requirements_file.content))
339
+ if (resolver = FileUpdater::CompileFileUpdater::RESOLVER_REGEX.match(T.must(requirements_file.content)))
291
340
  options << "--resolver=#{resolver}"
292
341
  end
293
342
 
294
- options << "--universal" if requirements_file.content.include?("--universal")
343
+ options << "--universal" if T.must(requirements_file.content).include?("--universal")
295
344
 
296
345
  options
297
346
  end
347
+ # rubocop:enable Metrics/AbcSize
298
348
 
349
+ sig { returns(T::Hash[String, String]) }
299
350
  def python_env
300
351
  env = {}
301
352
 
302
353
  # Handle Apache Airflow 1.10.x installs
303
- if dependency_files.any? { |f| f.content.include?("apache-airflow") }
304
- if dependency_files.any? { |f| f.content.include?("unidecode") }
354
+ if dependency_files.any? { |f| T.must(f.content).include?("apache-airflow") }
355
+ if dependency_files.any? { |f| T.must(f.content).include?("unidecode") }
305
356
  env["AIRFLOW_GPL_UNIDECODE"] = "yes"
306
357
  else
307
358
  env["SLUGIFY_USES_TEXT_UNIDECODE"] = "yes"
@@ -311,6 +362,9 @@ module Dependabot
311
362
  env
312
363
  end
313
364
 
365
+ sig do
366
+ params(updated_req: T.nilable(String), update_requirement: T::Boolean).void
367
+ end
314
368
  def write_temporary_dependency_files(updated_req: nil,
315
369
  update_requirement: true)
316
370
  dependency_files.each do |file|
@@ -328,6 +382,7 @@ module Dependabot
328
382
  File.write(".python-version", language_version_manager.python_major_minor)
329
383
  end
330
384
 
385
+ sig { void }
331
386
  def write_original_manifest_files
332
387
  pip_compile_files.each do |file|
333
388
  FileUtils.mkdir_p(Pathname.new(file.name).dirname)
@@ -335,29 +390,33 @@ module Dependabot
335
390
  end
336
391
  end
337
392
 
393
+ sig { params(file: Dependabot::DependencyFile, updated_req: T.nilable(String)).returns(String) }
338
394
  def update_req_file(file, updated_req)
339
- return file.content unless file.name.end_with?(".in")
395
+ return T.must(file.content) unless file.name.end_with?(".in")
340
396
 
341
397
  req = dependency.requirements.find { |r| r[:file] == file.name }
342
398
 
343
- return file.content + "\n#{dependency.name} #{updated_req}" unless req&.fetch(:requirement)
399
+ return T.must(file.content) + "\n#{dependency.name} #{updated_req}" unless req&.fetch(:requirement)
344
400
 
345
401
  Uv::FileUpdater::RequirementReplacer.new(
346
- content: file.content,
402
+ content: T.must(file.content),
347
403
  dependency_name: dependency.name,
348
404
  old_requirement: req[:requirement],
349
405
  new_requirement: updated_req
350
406
  ).updated_content
351
407
  end
352
408
 
409
+ sig { params(name: String).returns(String) }
353
410
  def normalise(name)
354
411
  NameNormaliser.normalise(name)
355
412
  end
356
413
 
414
+ sig { params(message: String).returns(String) }
357
415
  def clean_error_message(message)
358
- message.scan(ERROR_REGEX).last
416
+ T.must(T.cast(message.scan(ERROR_REGEX), T::Array[String]).last)
359
417
  end
360
418
 
419
+ sig { returns(T::Array[String]) }
361
420
  def filenames_to_compile
362
421
  files_from_reqs =
363
422
  dependency.requirements
@@ -375,10 +434,11 @@ module Dependabot
375
434
  order_filenames_for_compilation(filenames)
376
435
  end
377
436
 
437
+ sig { params(filename: String).returns(T.nilable(Dependabot::DependencyFile)) }
378
438
  def compiled_file_for_filename(filename)
379
439
  compiled_file =
380
440
  compiled_files
381
- .find { |f| f.content.match?(output_file_regex(filename)) }
441
+ .find { |f| T.must(f.content).match?(output_file_regex(filename)) }
382
442
 
383
443
  compiled_file ||=
384
444
  compiled_files
@@ -387,22 +447,25 @@ module Dependabot
387
447
  compiled_file
388
448
  end
389
449
 
450
+ sig { params(filename: String).returns(String) }
390
451
  def output_file_regex(filename)
391
452
  "--output-file[=\s]+.*\s#{Regexp.escape(filename)}\s*$"
392
453
  end
393
454
 
455
+ sig { params(compiled_file: T.nilable(Dependabot::DependencyFile)).returns(T::Boolean) }
394
456
  def compiled_file_includes_dependency?(compiled_file)
395
457
  return false unless compiled_file
396
458
 
397
459
  regex = RequirementParser::INSTALL_REQ_WITH_REQUIREMENT
398
460
 
399
461
  matches = []
400
- compiled_file.content.scan(regex) { matches << Regexp.last_match }
462
+ T.must(compiled_file.content).scan(regex) { matches << Regexp.last_match }
401
463
  matches.any? { |m| normalise(m[:name]) == dependency.name }
402
464
  end
403
465
 
404
466
  # If the files we need to update require one another then we need to
405
467
  # update them in the right order
468
+ sig { params(filenames: T::Array[String]).returns(T::Array[String]) }
406
469
  def order_filenames_for_compilation(filenames)
407
470
  ordered_filenames = T.let([], T::Array[String])
408
471
 
@@ -410,7 +473,7 @@ module Dependabot
410
473
  ordered_filenames +=
411
474
  remaining_filenames
412
475
  .reject do |fn|
413
- unupdated_reqs = requirement_map[fn] - ordered_filenames
476
+ unupdated_reqs = T.must(requirement_map[fn]) - ordered_filenames
414
477
  unupdated_reqs.intersect?(filenames)
415
478
  end
416
479
  end
@@ -418,11 +481,12 @@ module Dependabot
418
481
  ordered_filenames
419
482
  end
420
483
 
484
+ sig { returns(T::Hash[String, T::Array[String]]) }
421
485
  def requirement_map
422
486
  child_req_regex = Uv::FileFetcher::CHILD_REQUIREMENT_REGEX
423
- @requirement_map ||=
487
+ @requirement_map ||= T.let(
424
488
  pip_compile_files.each_with_object({}) do |file, req_map|
425
- paths = file.content.scan(child_req_regex).flatten
489
+ paths = T.must(file.content).scan(child_req_regex).flatten
426
490
  current_dir = File.dirname(file.name)
427
491
 
428
492
  req_map[file.name] =
@@ -434,9 +498,12 @@ module Dependabot
434
498
 
435
499
  path
436
500
  end.uniq.compact
437
- end
501
+ end,
502
+ T.nilable(T::Hash[String, T::Array[String]])
503
+ )
438
504
  end
439
505
 
506
+ sig { returns(T.nilable(String)) }
440
507
  def parse_updated_files
441
508
  updated_files =
442
509
  dependency_files.map do |file|
@@ -454,47 +521,59 @@ module Dependabot
454
521
  ).parse.find { |d| d.name == dependency.name }&.version
455
522
  end
456
523
 
524
+ sig { returns(Dependabot::Uv::FileParser::PythonRequirementParser) }
457
525
  def python_requirement_parser
458
- @python_requirement_parser ||=
526
+ @python_requirement_parser ||= T.let(
459
527
  FileParser::PythonRequirementParser.new(
460
528
  dependency_files: dependency_files
461
- )
529
+ ), T.nilable(FileParser::PythonRequirementParser)
530
+ )
462
531
  end
463
532
 
533
+ sig { returns(Dependabot::Uv::LanguageVersionManager) }
464
534
  def language_version_manager
465
- @language_version_manager ||=
535
+ @language_version_manager ||= T.let(
466
536
  LanguageVersionManager.new(
467
537
  python_requirement_parser: python_requirement_parser
468
- )
538
+ ), T.nilable(LanguageVersionManager)
539
+ )
469
540
  end
470
541
 
542
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
471
543
  def setup_files
472
544
  dependency_files.select { |f| f.name.end_with?("setup.py") }
473
545
  end
474
546
 
547
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
475
548
  def pip_compile_files
476
549
  dependency_files.select { |f| f.name.end_with?(".in") }
477
550
  end
478
551
 
552
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
479
553
  def compiled_files
480
554
  dependency_files.select { |f| f.name.end_with?(".txt") }
481
555
  end
482
556
 
557
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
483
558
  def setup_cfg_files
484
559
  dependency_files.select { |f| f.name.end_with?("setup.cfg") }
485
560
  end
486
561
  end
562
+ # rubocop:enable Metrics/ClassLength
487
563
  end
488
564
 
489
565
  class PipCompileErrorHandler
490
- SUBPROCESS_ERROR = /subprocess-exited-with-error/
566
+ extend T::Sig
567
+
568
+ SUBPROCESS_ERROR = T.let(/subprocess-exited-with-error/, Regexp)
491
569
 
492
- INSTALLATION_ERROR = /InstallationError/
570
+ INSTALLATION_ERROR = T.let(/InstallationError/, Regexp)
493
571
 
494
- INSTALLATION_SUBPROCESS_ERROR = /InstallationSubprocessError/
572
+ INSTALLATION_SUBPROCESS_ERROR = T.let(/InstallationSubprocessError/, Regexp)
495
573
 
496
- HASH_MISMATCH = /HashMismatch/
574
+ HASH_MISMATCH = T.let(/HashMismatch/, Regexp)
497
575
 
576
+ sig { params(error: String).void }
498
577
  def handle_pipcompile_error(error)
499
578
  return unless error.match?(SUBPROCESS_ERROR) || error.match?(INSTALLATION_ERROR) ||
500
579
  error.match?(INSTALLATION_SUBPROCESS_ERROR) || error.match?(HASH_MISMATCH)