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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9eafbe53d441a9d8b07914410bf4dc22012c56e3dc2cf3b57908c6024da99c27
4
+ data.tar.gz: a308cafa02b00033ad80af59a511fa44b30748052fc230aa9a74ec93e86b3d85
5
+ SHA512:
6
+ metadata.gz: 543b3691a28a571bf6cdfdf53af5e4fcf517190f51481944a8e62ecfab0218a30511ed16f7363851ca138e6ef047ba8bf17f41217ac51f6390e9ebfba94b5698
7
+ data.tar.gz: c2e1ee2eaf7055654ff518174718ee4f3a1afadea6724a69e0adff2d77453cfddf19452abdfcfef4ad858e478867bef303d9cbcac5c231ae8d7e759c826ea019
@@ -0,0 +1,121 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/file_fetchers"
7
+ require "dependabot/file_fetchers/base"
8
+ require "dependabot/file_filtering"
9
+
10
+ module Dependabot
11
+ module Sbt
12
+ class FileFetcher < Dependabot::FileFetchers::Base
13
+ extend T::Sig
14
+ extend T::Helpers
15
+
16
+ BUILD_SBT_FILENAME = "build.sbt"
17
+ PLUGINS_SBT_FILENAME = "project/plugins.sbt"
18
+ BUILD_PROPERTIES_FILENAME = "project/build.properties"
19
+ # Directories that are part of the SBT build structure, not subprojects
20
+ NON_SUBPROJECT_DIRS = T.let(%w(project target .git .github).freeze, T::Array[String])
21
+
22
+ sig { override.params(filenames: T::Array[String]).returns(T::Boolean) }
23
+ def self.required_files_in?(filenames)
24
+ filenames.any? { |name| name.end_with?(BUILD_SBT_FILENAME) }
25
+ end
26
+
27
+ sig { override.returns(String) }
28
+ def self.required_files_message
29
+ "Repo must contain a build.sbt file."
30
+ end
31
+
32
+ sig { override.returns(T::Array[DependencyFile]) }
33
+ def fetch_files
34
+ unless allow_beta_ecosystems?
35
+ raise Dependabot::DependencyFileNotFound.new(
36
+ nil,
37
+ "Sbt support is currently in beta. Enable the beta ecosystems experiment to use it " \
38
+ "(for example, run bin/dry-run.rb --enable-beta-ecosystems)."
39
+ )
40
+ end
41
+
42
+ fetched_files = T.let([], T::Array[DependencyFile])
43
+
44
+ fetched_files << build_sbt
45
+ fetched_files << T.must(plugins_sbt) if plugins_sbt
46
+ fetched_files << T.must(build_properties) if build_properties
47
+ fetched_files += subproject_build_files
48
+ fetched_files += project_scala_files
49
+
50
+ fetched_files.reject do |file|
51
+ Dependabot::FileFiltering.should_exclude_path?(file.name, "file from final collection", @exclude_paths)
52
+ end
53
+ end
54
+
55
+ sig { override.returns(T.nilable(T::Hash[Symbol, T.untyped])) }
56
+ def ecosystem_versions
57
+ return nil unless build_properties
58
+
59
+ sbt_version = parse_sbt_version(T.must(build_properties).content)
60
+ return nil unless sbt_version
61
+
62
+ {
63
+ package_managers: {
64
+ "sbt" => sbt_version
65
+ }
66
+ }
67
+ end
68
+
69
+ private
70
+
71
+ sig { returns(DependencyFile) }
72
+ def build_sbt
73
+ @build_sbt ||= T.let(fetch_file_from_host(BUILD_SBT_FILENAME), T.nilable(DependencyFile))
74
+ end
75
+
76
+ sig { returns(T.nilable(DependencyFile)) }
77
+ def plugins_sbt
78
+ @plugins_sbt ||= T.let(fetch_file_if_present(PLUGINS_SBT_FILENAME), T.nilable(DependencyFile))
79
+ end
80
+
81
+ sig { returns(T.nilable(DependencyFile)) }
82
+ def build_properties
83
+ @build_properties ||= T.let(fetch_file_if_present(BUILD_PROPERTIES_FILENAME), T.nilable(DependencyFile))
84
+ end
85
+
86
+ sig { returns(T::Array[DependencyFile]) }
87
+ def subproject_build_files
88
+ repo_contents(raise_errors: false)
89
+ .select { |item| item.type == "dir" }
90
+ .reject { |dir| NON_SUBPROJECT_DIRS.include?(dir.name) }
91
+ .reject { |dir| Dependabot::FileFiltering.should_exclude_path?(dir.name, "subproject directory", @exclude_paths) }
92
+ .filter_map { |dir| fetch_file_if_present(File.join(dir.name, BUILD_SBT_FILENAME)) }
93
+ end
94
+
95
+ sig { returns(T::Array[DependencyFile]) }
96
+ def project_scala_files
97
+ project_dir_contents = repo_contents(dir: "project", raise_errors: false)
98
+ project_dir_contents
99
+ .select { |item| item.type == "file" && item.name.end_with?(".scala") }
100
+ .filter_map { |item| fetch_file_if_present(File.join("project", item.name)) }
101
+ end
102
+
103
+ sig { params(content: T.nilable(String)).returns(T.nilable(String)) }
104
+ def parse_sbt_version(content)
105
+ return nil unless content
106
+
107
+ content.each_line do |line|
108
+ line = line.strip
109
+ next if line.empty? || line.start_with?("#", "!")
110
+
111
+ match = line.match(/\Asbt\.version\s*=\s*(.+)\z/)
112
+ return match[1]&.strip if match
113
+ end
114
+
115
+ nil
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ Dependabot::FileFetchers.register("sbt", Dependabot::Sbt::FileFetcher)
@@ -0,0 +1,179 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/sbt/file_parser"
7
+
8
+ module Dependabot
9
+ module Sbt
10
+ class FileParser < Dependabot::FileParsers::Base
11
+ class PropertyValueFinder
12
+ extend T::Sig
13
+
14
+ # Matches: val someVersion = "1.2.3"
15
+ # Also: val someVersion: String = "1.2.3"
16
+ # Also: lazy val someVersion = "1.2.3"
17
+ VAL_DECLARATION_REGEX = T.let(
18
+ /(?:^|\s)(?:lazy\s+)?val\s+(?<name>\w+)(?:\s*:\s*String)?\s*=\s*"(?<value>[^"]+)"/,
19
+ Regexp
20
+ )
21
+
22
+ sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
23
+ def initialize(dependency_files:)
24
+ @dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
25
+ @properties = T.let({}, T::Hash[String, T::Hash[String, T::Hash[Symbol, String]]])
26
+ end
27
+
28
+ sig do
29
+ params(property_name: String, callsite_buildfile: Dependabot::DependencyFile)
30
+ .returns(T.nilable(T::Hash[Symbol, String]))
31
+ end
32
+ def property_details(property_name:, callsite_buildfile:)
33
+ # Handle dotted references like "V.scalafixVersion" by looking up the member name
34
+ if property_name.include?(".")
35
+ return dotted_property_details(property_name: property_name, callsite_buildfile: callsite_buildfile)
36
+ end
37
+
38
+ # Look in the callsite file first, then fall back to the root build.sbt,
39
+ # then check project/*.scala build definition files
40
+ all_files = [callsite_buildfile, top_level_buildfile].compact
41
+ all_files += project_scala_files
42
+ all_files.uniq!
43
+ all_files.each do |file|
44
+ details = properties(file).fetch(property_name, nil)
45
+ return details if details
46
+ end
47
+ nil
48
+ end
49
+
50
+ sig { params(property_name: String, callsite_buildfile: Dependabot::DependencyFile).returns(T.nilable(String)) }
51
+ def property_value(property_name:, callsite_buildfile:)
52
+ property_details(
53
+ property_name: property_name,
54
+ callsite_buildfile: callsite_buildfile
55
+ )&.fetch(:value)
56
+ end
57
+
58
+ private
59
+
60
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
61
+ attr_reader :dependency_files
62
+
63
+ # Resolves dotted property references like "Versions.catsVersion" or "V.scala212".
64
+ # Searches for `val <member> = "..."` inside `object <ObjectName> { ... }` blocks
65
+ # across all dependency files.
66
+ sig do
67
+ params(property_name: String, callsite_buildfile: Dependabot::DependencyFile)
68
+ .returns(T.nilable(T::Hash[Symbol, String]))
69
+ end
70
+ def dotted_property_details(property_name:, callsite_buildfile:)
71
+ parts = property_name.split(".")
72
+ return nil unless parts.length == 2
73
+
74
+ object_name = T.must(parts.first)
75
+ member_name = T.must(parts.last)
76
+
77
+ # Resolve val aliases (e.g. "val V = BuildInfo" means V.x should look in object BuildInfo)
78
+ resolved_names = resolve_object_aliases(object_name, callsite_buildfile)
79
+
80
+ all_files = [callsite_buildfile, top_level_buildfile].compact
81
+ all_files += project_scala_files
82
+ all_files.uniq!
83
+
84
+ resolved_names.each do |resolved_name|
85
+ all_files.each do |file|
86
+ content = prepared_content(file)
87
+ object_regex = /object\s+#{Regexp.quote(resolved_name)}\b[^{]*\{(?<body>[^}]*)\}/m
88
+ content.scan(object_regex) do
89
+ body = T.must(Regexp.last_match).named_captures.fetch("body")
90
+ member_regex = /(?:^|\s)(?:lazy\s+)?val\s+#{Regexp.quote(member_name)}/
91
+ member_value_regex = /#{member_regex}(?:\s*:\s*String)?\s*=\s*"(?<value>[^"]+)"/
92
+ member_match = body&.match(member_value_regex)
93
+ next unless member_match
94
+
95
+ declaration_string = member_match.to_s.strip
96
+ return {
97
+ value: T.must(member_match[:value]),
98
+ declaration_string: declaration_string,
99
+ file: file.name
100
+ }
101
+ end
102
+ end
103
+ end
104
+
105
+ nil
106
+ end
107
+
108
+ # Resolves val aliases like "val V = BuildInfo" → returns ["V", "BuildInfo"]
109
+ # so that dotted references like V.member can look in object BuildInfo.
110
+ sig { params(name: String, callsite_buildfile: Dependabot::DependencyFile).returns(T::Array[String]) }
111
+ def resolve_object_aliases(name, callsite_buildfile)
112
+ names = [name]
113
+
114
+ all_files = [callsite_buildfile, top_level_buildfile].compact
115
+ all_files += project_scala_files
116
+ all_files.uniq!
117
+
118
+ all_files.each do |file|
119
+ prepared_content(file).scan(/(?:^|\s)(?:lazy\s+)?val\s+#{Regexp.quote(name)}\s*=\s*(?<target>[A-Z]\w*)/) do
120
+ target = T.must(Regexp.last_match).named_captures.fetch("target")
121
+ names << T.must(target) unless names.include?(target)
122
+ end
123
+ end
124
+
125
+ names
126
+ end
127
+
128
+ sig { params(buildfile: Dependabot::DependencyFile).returns(T::Hash[String, T::Hash[Symbol, String]]) }
129
+ def properties(buildfile)
130
+ @properties[buildfile.name] ||= fetch_val_declarations(buildfile)
131
+ end
132
+
133
+ sig { params(buildfile: Dependabot::DependencyFile).returns(T::Hash[String, T::Hash[Symbol, String]]) }
134
+ def fetch_val_declarations(buildfile)
135
+ props = T.let({}, T::Hash[String, T::Hash[Symbol, String]])
136
+
137
+ prepared_content(buildfile).scan(VAL_DECLARATION_REGEX) do
138
+ captures = T.must(Regexp.last_match).named_captures
139
+ name = T.must(captures.fetch("name"))
140
+ value = T.must(captures.fetch("value"))
141
+
142
+ unless props.key?(name)
143
+ props[name] = {
144
+ value: value,
145
+ declaration_string: Regexp.last_match.to_s.strip,
146
+ file: buildfile.name
147
+ }
148
+ end
149
+ end
150
+
151
+ props
152
+ end
153
+
154
+ sig { params(buildfile: Dependabot::DependencyFile).returns(String) }
155
+ def prepared_content(buildfile)
156
+ T.must(buildfile.content)
157
+ .gsub(%r{(?<=^|\s)//.*$}, "\n")
158
+ .gsub(%r{(?<=^|\s)/\*.*?\*/}m, "")
159
+ end
160
+
161
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
162
+ def top_level_buildfile
163
+ @top_level_buildfile ||= T.let(
164
+ dependency_files.find { |f| f.name == "build.sbt" },
165
+ T.nilable(Dependabot::DependencyFile)
166
+ )
167
+ end
168
+
169
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
170
+ def project_scala_files
171
+ @project_scala_files ||= T.let(
172
+ dependency_files.select { |f| f.name.end_with?(".scala") && f.name.start_with?("project/") },
173
+ T.nilable(T::Array[Dependabot::DependencyFile])
174
+ )
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,141 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/sbt/file_parser"
7
+
8
+ module Dependabot
9
+ module Sbt
10
+ class FileParser < Dependabot::FileParsers::Base
11
+ class RepositoriesFinder
12
+ extend T::Sig
13
+
14
+ CENTRAL_REPO_URL = "https://repo.maven.apache.org/maven2"
15
+
16
+ # Matches: resolvers += "Name" at "https://url"
17
+ RESOLVER_AT_REGEX = T.let(
18
+ /resolvers\s*\+?=\s*(?:Seq\s*\()?\s*"[^"]*"\s+at\s+"(?<url>[^"]+)"/,
19
+ Regexp
20
+ )
21
+
22
+ # Matches: resolvers ++= Seq("Name" at "url", ...)
23
+ RESOLVER_SEQ_AT_REGEX = T.let(
24
+ /"[^"]*"\s+at\s+"(?<url>[^"]+)"/,
25
+ Regexp
26
+ )
27
+
28
+ # Matches: Resolver.url("name", url("https://url"))
29
+ RESOLVER_URL_REGEX = T.let(
30
+ /Resolver\.url\(\s*"[^"]*"\s*,\s*url\(\s*"(?<url>[^"]+)"\s*\)/,
31
+ Regexp
32
+ )
33
+
34
+ # Matches: MavenRepository("name", "https://url")
35
+ MAVEN_REPOSITORY_REGEX = T.let(
36
+ /MavenRepository\(\s*"[^"]*"\s*,\s*"(?<url>[^"]+)"\s*\)/,
37
+ Regexp
38
+ )
39
+
40
+ sig do
41
+ params(
42
+ dependency_files: T::Array[Dependabot::DependencyFile],
43
+ credentials: T::Array[Dependabot::Credential]
44
+ ).void
45
+ end
46
+ def initialize(dependency_files:, credentials: [])
47
+ @dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
48
+ @credentials = T.let(credentials, T::Array[Dependabot::Credential])
49
+ end
50
+
51
+ sig { returns(T::Array[String]) }
52
+ def repository_urls
53
+ urls = T.let([], T::Array[String])
54
+
55
+ sbt_files.each do |file|
56
+ urls += repository_urls_from(file)
57
+ end
58
+
59
+ # SBT always includes Maven Central as a default resolver.
60
+ # Custom resolvers supplement but don't replace it.
61
+ urls << central_repo_url unless urls.include?(central_repo_url)
62
+
63
+ urls.uniq
64
+ end
65
+
66
+ private
67
+
68
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
69
+ attr_reader :dependency_files
70
+
71
+ sig { returns(T::Array[Dependabot::Credential]) }
72
+ attr_reader :credentials
73
+
74
+ sig { params(buildfile: Dependabot::DependencyFile).returns(T::Array[String]) }
75
+ def repository_urls_from(buildfile)
76
+ content = comment_free_content(buildfile)
77
+
78
+ urls = [RESOLVER_AT_REGEX, RESOLVER_SEQ_AT_REGEX, RESOLVER_URL_REGEX, MAVEN_REPOSITORY_REGEX]
79
+ .flat_map { |regex| scan_urls(content, regex) }
80
+
81
+ urls
82
+ .map { |url| url.strip.gsub(%r{/$}, "") }
83
+ .select { |url| valid_url?(url) }
84
+ .uniq
85
+ end
86
+
87
+ sig { params(content: String, regex: Regexp).returns(T::Array[String]) }
88
+ def scan_urls(content, regex)
89
+ urls = T.let([], T::Array[String])
90
+ content.scan(regex) do
91
+ urls << T.must(T.must(Regexp.last_match).named_captures.fetch("url"))
92
+ end
93
+ urls
94
+ end
95
+
96
+ sig { returns(String) }
97
+ def central_repo_url
98
+ base_credential = credentials.find do |cred|
99
+ cred["type"] == "maven_repository" && replaces_base?(cred) && cred["url"]
100
+ end
101
+
102
+ base_credential ? T.must(base_credential["url"]).gsub(%r{/+$}, "") : CENTRAL_REPO_URL
103
+ end
104
+
105
+ sig { params(credential: Dependabot::Credential).returns(T::Boolean) }
106
+ def replaces_base?(credential)
107
+ if credential.respond_to?(:replaces_base?)
108
+ credential.replaces_base?
109
+ else
110
+ credential["replaces-base"] == true
111
+ end
112
+ end
113
+
114
+ sig { params(url: String).returns(T::Boolean) }
115
+ def valid_url?(url)
116
+ return false unless url.start_with?("http")
117
+
118
+ URI.parse(url)
119
+ true
120
+ rescue URI::InvalidURIError
121
+ false
122
+ end
123
+
124
+ sig { params(buildfile: Dependabot::DependencyFile).returns(String) }
125
+ def comment_free_content(buildfile)
126
+ T.must(buildfile.content)
127
+ .gsub(%r{(?<=^|\s)//.*$}, "\n")
128
+ .gsub(%r{(?<=^|\s)/\*.*?\*/}m, "")
129
+ end
130
+
131
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
132
+ def sbt_files
133
+ @sbt_files ||= T.let(
134
+ dependency_files.select { |f| f.name.end_with?(".sbt") },
135
+ T.nilable(T::Array[Dependabot::DependencyFile])
136
+ )
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end