dependabot-sbt 0.377.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.
@@ -0,0 +1,240 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/file_updaters"
7
+ require "dependabot/file_updaters/base"
8
+ require "dependabot/sbt/file_parser"
9
+
10
+ module Dependabot
11
+ module Sbt
12
+ class FileUpdater < Dependabot::FileUpdaters::Base
13
+ extend T::Sig
14
+
15
+ require_relative "file_updater/property_value_updater"
16
+
17
+ # Regex matching scalaVersion declarations in all supported SBT syntaxes
18
+ SCALA_VERSION_DECL = T.let(
19
+ "(?:ThisBuild\\s*/\\s*)?" \
20
+ "(?:scalaVersion\\s+in\\s+ThisBuild|scalaVersion)" \
21
+ '\\s*:=\\s*"',
22
+ String
23
+ )
24
+
25
+ sig { override.returns(T::Array[Dependabot::DependencyFile]) }
26
+ def updated_dependency_files
27
+ updated_files = T.let(dependency_files.dup, T::Array[Dependabot::DependencyFile])
28
+
29
+ dependencies.each do |dependency|
30
+ updated_files = update_files_for_dependency(
31
+ original_files: updated_files,
32
+ dependency: dependency
33
+ )
34
+ end
35
+
36
+ updated_files.reject! { |f| dependency_files.include?(f) }
37
+
38
+ raise "No files changed!" if updated_files.none?
39
+
40
+ updated_files
41
+ end
42
+
43
+ private
44
+
45
+ sig { override.void }
46
+ def check_required_files
47
+ raise "No build.sbt!" unless get_original_file("build.sbt")
48
+ end
49
+
50
+ sig do
51
+ params(
52
+ original_files: T::Array[Dependabot::DependencyFile],
53
+ dependency: Dependabot::Dependency
54
+ ).returns(T::Array[Dependabot::DependencyFile])
55
+ end
56
+ def update_files_for_dependency(original_files:, dependency:)
57
+ files = original_files.dup
58
+
59
+ reqs = dependency.requirements.zip(dependency.previous_requirements.to_a)
60
+ .reject { |new_req, old_req| new_req == old_req }
61
+
62
+ reqs.each do |new_req, old_req|
63
+ raise "Bad req match" unless new_req[:file] == T.must(old_req)[:file]
64
+ next if new_req[:requirement] == T.must(old_req)[:requirement]
65
+
66
+ files = apply_requirement_update(files, dependency, new_req, T.must(old_req))
67
+ end
68
+
69
+ files
70
+ end
71
+
72
+ sig do
73
+ params(
74
+ files: T::Array[Dependabot::DependencyFile],
75
+ dependency: Dependabot::Dependency,
76
+ new_req: T::Hash[Symbol, T.untyped],
77
+ old_req: T::Hash[Symbol, T.untyped]
78
+ ).returns(T::Array[Dependabot::DependencyFile])
79
+ end
80
+ def apply_requirement_update(files, dependency, new_req, old_req)
81
+ if new_req.dig(:metadata, :property_name)
82
+ update_files_for_property_change(files, old_req, new_req)
83
+ elsif T.let(new_req[:file], String).end_with?("build.properties")
84
+ update_build_properties(files, old_req, new_req)
85
+ elsif scala_version_requirement?(new_req)
86
+ file = T.must(files.find { |f| f.name == new_req[:file] })
87
+ idx = T.must(files.index(file))
88
+ files.dup.tap { |updated| updated[idx] = update_scala_version(file, old_req, new_req) }
89
+ else
90
+ file = T.must(files.find { |f| f.name == new_req[:file] })
91
+ idx = T.must(files.index(file))
92
+ files.dup.tap { |updated| updated[idx] = update_version_in_buildfile(dependency, file, old_req, new_req) }
93
+ end
94
+ end
95
+
96
+ sig do
97
+ params(
98
+ files: T::Array[Dependabot::DependencyFile],
99
+ old_req: T::Hash[Symbol, T.untyped],
100
+ new_req: T::Hash[Symbol, T.untyped]
101
+ ).returns(T::Array[Dependabot::DependencyFile])
102
+ end
103
+ def update_files_for_property_change(files, old_req, new_req)
104
+ property_name = T.let(new_req.dig(:metadata, :property_name), String)
105
+ callsite = T.must(files.find { |f| f.name == new_req[:file] })
106
+
107
+ PropertyValueUpdater.new(dependency_files: files)
108
+ .update_files_for_property_change(
109
+ property_name: property_name,
110
+ callsite_buildfile: callsite,
111
+ previous_value: T.let(old_req.fetch(:requirement), String),
112
+ updated_value: T.let(new_req.fetch(:requirement), String)
113
+ )
114
+ end
115
+
116
+ sig do
117
+ params(
118
+ files: T::Array[Dependabot::DependencyFile],
119
+ old_req: T::Hash[Symbol, T.untyped],
120
+ new_req: T::Hash[Symbol, T.untyped]
121
+ ).returns(T::Array[Dependabot::DependencyFile])
122
+ end
123
+ def update_build_properties(files, old_req, new_req)
124
+ file = T.must(files.find { |f| f.name == new_req[:file] })
125
+ old_version = T.let(old_req.fetch(:requirement), String)
126
+ new_version = T.let(new_req.fetch(:requirement), String)
127
+
128
+ updated_content = T.must(file.content).sub(
129
+ /(sbt\.version\s*=\s*)#{Regexp.quote(old_version)}/,
130
+ "\\1#{new_version}"
131
+ )
132
+
133
+ raise "Expected content to change!" if updated_content == file.content
134
+
135
+ updated_files = files.dup
136
+ updated_files[T.must(files.index(file))] =
137
+ updated_file(file: file, content: updated_content)
138
+ updated_files
139
+ end
140
+
141
+ sig do
142
+ params(
143
+ file: Dependabot::DependencyFile,
144
+ old_req: T::Hash[Symbol, T.untyped],
145
+ new_req: T::Hash[Symbol, T.untyped]
146
+ ).returns(Dependabot::DependencyFile)
147
+ end
148
+ def update_scala_version(file, old_req, new_req)
149
+ old_version = T.let(old_req.fetch(:requirement), String)
150
+ new_version = T.let(new_req.fetch(:requirement), String)
151
+
152
+ updated_content = T.must(file.content).sub(
153
+ /(#{SCALA_VERSION_DECL})#{Regexp.quote(old_version)}(")/,
154
+ "\\1#{new_version}\\2"
155
+ )
156
+
157
+ raise "Expected content to change!" if updated_content == file.content
158
+
159
+ updated_file(file: file, content: updated_content)
160
+ end
161
+
162
+ sig do
163
+ params(
164
+ dependency: Dependabot::Dependency,
165
+ buildfile: Dependabot::DependencyFile,
166
+ previous_req: T::Hash[Symbol, T.untyped],
167
+ requirement: T::Hash[Symbol, T.untyped]
168
+ ).returns(Dependabot::DependencyFile)
169
+ end
170
+ def update_version_in_buildfile(dependency, buildfile, previous_req, requirement)
171
+ updated_content = T.must(buildfile.content.dup)
172
+
173
+ original_declarations = original_buildfile_declarations(dependency, previous_req)
174
+ original_declarations.each do |old_dec|
175
+ updated_content = updated_content.gsub(old_dec) do
176
+ updated_buildfile_declaration(old_dec, previous_req, requirement)
177
+ end
178
+ end
179
+
180
+ raise "Expected content to change!" if updated_content == buildfile.content
181
+
182
+ updated_file(file: buildfile, content: updated_content)
183
+ end
184
+
185
+ sig do
186
+ params(
187
+ dependency: Dependabot::Dependency,
188
+ requirement: T::Hash[Symbol, T.untyped]
189
+ ).returns(T::Array[String])
190
+ end
191
+ def original_buildfile_declarations(dependency, requirement)
192
+ buildfile = T.must(dependency_files.find { |f| f.name == T.let(requirement.fetch(:file), String) })
193
+ group, artifact = dependency_group_and_artifact(dependency)
194
+
195
+ T.must(buildfile.content).lines.select do |line|
196
+ next false unless line.include?(group)
197
+ next false unless line.include?(artifact)
198
+
199
+ line.include?(T.let(requirement.fetch(:requirement), String))
200
+ end
201
+ end
202
+
203
+ sig { params(dependency: Dependabot::Dependency).returns([String, String]) }
204
+ def dependency_group_and_artifact(dependency)
205
+ parts = dependency.name.split(":")
206
+ group = T.must(parts.first)
207
+ artifact = T.must(parts.last)
208
+
209
+ # Strip Scala version suffix for cross-versioned deps (e.g. cats-core_2.13 → cats-core)
210
+ artifact = artifact.sub(/_\d+(\.\d+)?$/, "")
211
+
212
+ [group, artifact]
213
+ end
214
+
215
+ sig do
216
+ params(
217
+ old_declaration: String,
218
+ previous_req: T::Hash[Symbol, T.untyped],
219
+ requirement: T::Hash[Symbol, T.untyped]
220
+ ).returns(String)
221
+ end
222
+ def updated_buildfile_declaration(old_declaration, previous_req, requirement)
223
+ original_req_string = T.let(previous_req.fetch(:requirement), String)
224
+ new_req_string = T.let(requirement.fetch(:requirement), String)
225
+
226
+ old_declaration.gsub(
227
+ /"#{Regexp.quote(original_req_string)}"/,
228
+ "\"#{new_req_string}\""
229
+ )
230
+ end
231
+
232
+ sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
233
+ def scala_version_requirement?(req)
234
+ req.dig(:metadata, :property_source) == "scalaVersion"
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ Dependabot::FileUpdaters.register("sbt", Dependabot::Sbt::FileUpdater)
@@ -0,0 +1,24 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/ecosystem"
6
+ require "dependabot/sbt/version"
7
+
8
+ module Dependabot
9
+ module Sbt
10
+ LANGUAGE = "scala"
11
+
12
+ class Language < Dependabot::Ecosystem::VersionManager
13
+ extend T::Sig
14
+
15
+ sig { params(raw_version: String).void }
16
+ def initialize(raw_version)
17
+ super(
18
+ name: LANGUAGE,
19
+ version: Version.new(raw_version)
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/metadata_finders"
5
+ require "dependabot/maven/shared/shared_metadata_finder"
6
+ require "dependabot/sbt/file_fetcher"
7
+ require "sorbet-runtime"
8
+
9
+ module Dependabot
10
+ module Sbt
11
+ class MetadataFinder < Dependabot::Maven::Shared::SharedMetadataFinder
12
+ extend T::Sig
13
+
14
+ private
15
+
16
+ sig { override.returns(T.class_of(Dependabot::FileFetchers::Base)) }
17
+ def file_fetcher_class
18
+ Dependabot::Sbt::FileFetcher
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ Dependabot::MetadataFinders.register("sbt", Dependabot::Sbt::MetadataFinder)
@@ -0,0 +1,28 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Dependabot
7
+ module Sbt
8
+ module NativeHelpers
9
+ extend T::Sig
10
+
11
+ sig { returns(String) }
12
+ def self.coursier_path
13
+ clean_path(File.join(native_helpers_root, "sbt/bin/cs"))
14
+ end
15
+
16
+ sig { returns(String) }
17
+ def self.native_helpers_root
18
+ default_path = File.join(__dir__, "../../../helpers/install-dir")
19
+ ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", default_path)
20
+ end
21
+
22
+ sig { params(path: String).returns(String) }
23
+ def self.clean_path(path)
24
+ Pathname.new(path).cleanpath.to_path
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,175 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "nokogiri"
5
+ require "sorbet-runtime"
6
+ require "dependabot/registry_client"
7
+ require "dependabot/package/package_release"
8
+ require "dependabot/package/package_details"
9
+ require "dependabot/sbt/file_parser/repositories_finder"
10
+ require "dependabot/sbt/version"
11
+ require "dependabot/sbt/requirement"
12
+ require "dependabot/maven/shared/shared_package_details_fetcher"
13
+ require "dependabot/maven/utils/auth_headers_finder"
14
+
15
+ module Dependabot
16
+ module Sbt
17
+ module Package
18
+ class PackageDetailsFetcher < Dependabot::Maven::Shared::SharedPackageDetailsFetcher
19
+ extend T::Sig
20
+
21
+ sig do
22
+ params(
23
+ dependency: Dependabot::Dependency,
24
+ dependency_files: T::Array[Dependabot::DependencyFile],
25
+ credentials: T::Array[Dependabot::Credential]
26
+ ).void
27
+ end
28
+ def initialize(dependency:, dependency_files:, credentials:)
29
+ @dependency = T.let(dependency, Dependabot::Dependency)
30
+ @dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
31
+ @credentials = T.let(credentials, T::Array[Dependabot::Credential])
32
+
33
+ @repositories_cache = T.let(nil, T.nilable(T::Array[T::Hash[String, T.untyped]]))
34
+ @repository_finder = T.let(nil, T.nilable(Sbt::FileParser::RepositoriesFinder))
35
+ @package_details = T.let(nil, T.nilable(Dependabot::Package::PackageDetails))
36
+ end
37
+
38
+ sig { override.returns(Dependabot::Dependency) }
39
+ attr_reader :dependency
40
+
41
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
42
+ attr_reader :dependency_files
43
+
44
+ sig { override.returns(T::Array[Dependabot::Credential]) }
45
+ attr_reader :credentials
46
+
47
+ sig { returns(Dependabot::Package::PackageDetails) }
48
+ def fetch
49
+ return @package_details if @package_details
50
+
51
+ releases = versions.map do |version_details|
52
+ Dependabot::Package::PackageRelease.new(
53
+ version: version_details.fetch(:version),
54
+ released_at: version_details.fetch(:release_date, nil),
55
+ url: version_details.fetch(:source_url)
56
+ )
57
+ end
58
+
59
+ @package_details = Dependabot::Package::PackageDetails.new(
60
+ dependency: dependency,
61
+ releases: releases
62
+ )
63
+
64
+ @package_details
65
+ end
66
+
67
+ sig { returns(T::Array[Dependabot::Package::PackageRelease]) }
68
+ def releases
69
+ fetch.releases
70
+ end
71
+
72
+ # Assembles the list of Maven repositories to search: credential repos + SBT resolver repos.
73
+ sig { override.returns(T::Array[T::Hash[String, T.untyped]]) }
74
+ def repositories
75
+ return @repositories_cache if @repositories_cache
76
+
77
+ @repositories_cache = credentials_repository_details
78
+
79
+ sbt_repository_details.each do |repo|
80
+ @repositories_cache << repo unless @repositories_cache.any? do |r|
81
+ r[URL_KEY] == repo[URL_KEY]
82
+ end
83
+ end
84
+
85
+ @repositories_cache
86
+ end
87
+
88
+ sig { override.returns(String) }
89
+ def central_repo_url
90
+ Sbt::FileParser::RepositoriesFinder::CENTRAL_REPO_URL
91
+ end
92
+
93
+ # Override to always use "jar" for the HEAD check. The SBT parser sets
94
+ # packaging_type to "cross-versioned" as metadata for %% dependencies, but the
95
+ # actual Maven artifact is always a .jar file.
96
+ sig { override.params(repository_url: String, version: Dependabot::Version).returns(String) }
97
+ def dependency_files_url(repository_url, version)
98
+ _, artifact_id = dependency_parts
99
+ base_url = dependency_base_url(repository_url)
100
+
101
+ "#{base_url}/#{version}/#{artifact_id}-#{version}.jar"
102
+ end
103
+
104
+ # Override to handle SBT plugin cross-versioning.
105
+ # SBT plugins are published with a double-suffix: artifact_scalaVersion_sbtVersion
106
+ # e.g., sbt-jmh_2.12_1.0 for SBT 1.x plugins.
107
+ sig { override.returns([String, String]) }
108
+ def dependency_parts
109
+ @dependency_parts = T.let(@dependency_parts, T.nilable([String, String]))
110
+ return @dependency_parts if @dependency_parts
111
+
112
+ group_id, artifact_id = dependency.name.split(":")
113
+ group_path = T.must(group_id).tr(".", "/")
114
+
115
+ artifact_id = "#{artifact_id}_#{plugin_scala_version}_#{sbt_binary_version}" if sbt_plugin?
116
+
117
+ @dependency_parts = [group_path, T.must(artifact_id)]
118
+ end
119
+
120
+ private
121
+
122
+ sig { returns(Sbt::FileParser::RepositoriesFinder) }
123
+ def repository_finder
124
+ @repository_finder ||= Sbt::FileParser::RepositoriesFinder.new(
125
+ dependency_files: dependency_files,
126
+ credentials: credentials
127
+ )
128
+ end
129
+
130
+ # Returns the repository details discovered from SBT build files.
131
+ sig { returns(T::Array[T::Hash[String, T.untyped]]) }
132
+ def sbt_repository_details
133
+ repository_finder
134
+ .repository_urls
135
+ .map do |url|
136
+ { URL_KEY => url, AUTH_HEADERS_KEY => auth_headers(url) }
137
+ end
138
+ end
139
+
140
+ # SBT plugins are identified by having "plugins" in their groups.
141
+ sig { returns(T::Boolean) }
142
+ def sbt_plugin?
143
+ dependency.requirements.any? { |req| req.fetch(:groups, []).include?("plugins") }
144
+ end
145
+
146
+ # SBT 1.x plugins use Scala 2.12; SBT 2.x plugins use Scala 3.
147
+ sig { returns(String) }
148
+ def plugin_scala_version
149
+ sbt_major_version >= 2 ? "3" : "2.12"
150
+ end
151
+
152
+ # SBT binary version for plugin cross-versioning: "1.0" for SBT 1.x, "2.0" for SBT 2.x.
153
+ sig { returns(String) }
154
+ def sbt_binary_version
155
+ "#{sbt_major_version}.0"
156
+ end
157
+
158
+ sig { returns(Integer) }
159
+ def sbt_major_version
160
+ build_properties = dependency_files.find { |f| f.name.end_with?("build.properties") }
161
+ return 1 unless build_properties&.content
162
+
163
+ T.must(build_properties.content).each_line do |line|
164
+ match = line.strip.match(Sbt::FileParser::SBT_VERSION_REGEX)
165
+ next unless match
166
+
167
+ return T.must(match[:version]).strip.split(".").first.to_i
168
+ end
169
+
170
+ 1
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,39 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/ecosystem"
6
+ require "dependabot/sbt/version"
7
+
8
+ module Dependabot
9
+ module Sbt
10
+ ECOSYSTEM = "sbt"
11
+ PACKAGE_MANAGER = "sbt"
12
+ SUPPORTED_SBT_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
13
+ DEPRECATED_SBT_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
14
+
15
+ class PackageManager < Dependabot::Ecosystem::VersionManager
16
+ extend T::Sig
17
+
18
+ sig { params(raw_version: String).void }
19
+ def initialize(raw_version)
20
+ super(
21
+ name: PACKAGE_MANAGER,
22
+ version: Version.new(raw_version),
23
+ deprecated_versions: DEPRECATED_SBT_VERSIONS,
24
+ supported_versions: SUPPORTED_SBT_VERSIONS
25
+ )
26
+ end
27
+
28
+ sig { returns(T::Boolean) }
29
+ def deprecated?
30
+ false
31
+ end
32
+
33
+ sig { returns(T::Boolean) }
34
+ def unsupported?
35
+ false
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,64 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/requirement"
7
+ require "dependabot/utils"
8
+ require "dependabot/maven/shared/shared_requirement"
9
+ require "dependabot/sbt/version"
10
+
11
+ module Dependabot
12
+ module Sbt
13
+ # SBT typically uses exact versions ("org" % "artifact" % "1.2.3"),
14
+ # but Maven-style version ranges are valid since artifacts resolve
15
+ # from Maven repositories.
16
+ class Requirement < Dependabot::Maven::Shared::SharedRequirement
17
+ extend T::Sig
18
+
19
+ quoted = OPS.keys.map { |k| Regexp.quote k }.join("|")
20
+ PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{Sbt::Version::VERSION_PATTERN})\\s*".freeze, String)
21
+ PATTERN = T.let(/\A#{PATTERN_RAW}\z/, Regexp)
22
+ RUBY_STYLE_PATTERN = T.let(/\A\s*(#{quoted})\s*(#{Sbt::Version::VERSION_PATTERN})\s*\z/, Regexp)
23
+
24
+ sig { override.returns(Regexp) }
25
+ def self.pattern
26
+ PATTERN
27
+ end
28
+
29
+ sig { override.returns(Regexp) }
30
+ def self.ruby_style_pattern
31
+ RUBY_STYLE_PATTERN
32
+ end
33
+
34
+ sig { override.params(obj: T.any(Gem::Version, String)).returns([String, Gem::Version]) }
35
+ def self.parse(obj)
36
+ return ["=", Sbt::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
37
+
38
+ unless (matches = PATTERN.match(obj.to_s))
39
+ msg = "Illformed requirement [#{obj.inspect}]"
40
+ raise BadRequirementError, msg
41
+ end
42
+
43
+ return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
44
+
45
+ [matches[1] || "=", Sbt::Version.new(T.must(matches[2]))]
46
+ end
47
+
48
+ sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) }
49
+ def self.requirements_array(requirement_string)
50
+ split_java_requirement(requirement_string).map do |str|
51
+ new(str)
52
+ end
53
+ end
54
+
55
+ sig { override.params(version: Gem::Version).returns(T::Boolean) }
56
+ def satisfied_by?(version)
57
+ version = Sbt::Version.new(version.to_s)
58
+ super
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ Dependabot::Utils.register_requirement_class("sbt", Dependabot::Sbt::Requirement)
@@ -0,0 +1,98 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/requirements_updater/base"
6
+ require "dependabot/sbt/update_checker"
7
+ require "dependabot/sbt/version"
8
+ require "dependabot/sbt/requirement"
9
+
10
+ module Dependabot
11
+ module Sbt
12
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
13
+ class RequirementsUpdater
14
+ extend T::Sig
15
+ extend T::Generic
16
+
17
+ Version = type_member { { fixed: Dependabot::Sbt::Version } }
18
+ Requirement = type_member { { fixed: Dependabot::Sbt::Requirement } }
19
+
20
+ include Dependabot::RequirementsUpdater::Base
21
+
22
+ sig do
23
+ params(
24
+ requirements: T::Array[T::Hash[Symbol, T.untyped]],
25
+ latest_version: T.nilable(T.any(Version, String)),
26
+ source_url: T.nilable(String),
27
+ properties_to_update: T::Array[String]
28
+ ).void
29
+ end
30
+ def initialize(
31
+ requirements:,
32
+ latest_version:,
33
+ source_url:,
34
+ properties_to_update:
35
+ )
36
+ @requirements = requirements
37
+ @source_url = source_url
38
+ @properties_to_update = properties_to_update
39
+ return unless latest_version
40
+
41
+ @latest_version = T.let(version_class.new(latest_version), Version)
42
+ end
43
+
44
+ sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
45
+ def updated_requirements
46
+ return requirements unless latest_version
47
+
48
+ requirements.map do |req|
49
+ next req if req.fetch(:requirement).nil?
50
+ next req if req.fetch(:requirement).include?(",")
51
+
52
+ property_name = req.dig(:metadata, :property_name)
53
+ next req if property_name && !properties_to_update.include?(property_name)
54
+
55
+ new_req = update_requirement(req[:requirement])
56
+ req.merge(requirement: new_req, source: updated_source)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
63
+ attr_reader :requirements
64
+
65
+ sig { returns(T.nilable(Version)) }
66
+ attr_reader :latest_version
67
+
68
+ sig { returns(T.nilable(String)) }
69
+ attr_reader :source_url
70
+
71
+ sig { returns(T::Array[String]) }
72
+ attr_reader :properties_to_update
73
+
74
+ sig { params(req_string: String).returns(String) }
75
+ def update_requirement(req_string)
76
+ old_version = requirement_class.new(req_string)
77
+ .requirements.first.last
78
+ req_string.gsub(old_version.to_s, T.must(latest_version).to_s)
79
+ end
80
+
81
+ sig { override.returns(T::Class[Version]) }
82
+ def version_class
83
+ Sbt::Version
84
+ end
85
+
86
+ sig { override.returns(T::Class[Requirement]) }
87
+ def requirement_class
88
+ Sbt::Requirement
89
+ end
90
+
91
+ sig { returns(T::Hash[Symbol, T.untyped]) }
92
+ def updated_source
93
+ { type: "maven_repo", url: source_url }
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end