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,468 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/dependency"
7
+ require "dependabot/ecosystem"
8
+ require "dependabot/file_parsers"
9
+ require "dependabot/file_parsers/base"
10
+ require "dependabot/sbt/version"
11
+ require "dependabot/sbt/package_manager"
12
+ require "dependabot/sbt/language"
13
+
14
+ module Dependabot
15
+ module Sbt
16
+ class FileParser < Dependabot::FileParsers::Base
17
+ extend T::Sig
18
+
19
+ require "dependabot/file_parsers/base/dependency_set"
20
+ require_relative "file_parser/property_value_finder"
21
+ require_relative "file_parser/repositories_finder"
22
+
23
+ BUILD_SBT_FILENAME = "build.sbt"
24
+ BUILD_PROPERTIES_FILENAME = "project/build.properties"
25
+
26
+ # Matches a simple identifier or dotted reference (e.g. myVal, V.scala212)
27
+ IDENT_OR_DOTTED = T.let(
28
+ "[a-zA-Z_]\\w*(?:\\.[a-zA-Z_]\\w*)*",
29
+ String
30
+ )
31
+
32
+ # "org" % "artifact" % "version" or "org" %% "artifact" % "version"
33
+ # Optionally followed by % "scope" (e.g. % "test", % "provided")
34
+ LIBRARY_DEP_REGEX = T.let(
35
+ /"(?<group>[^"]+)"\s+(?<op>%%?)\s+"(?<artifact>[^"]+)"\s+%\s+"(?<version>[^"]+)"(?:\s+%\s+"[^"]*")*/,
36
+ Regexp
37
+ )
38
+
39
+ # "org" % "artifact" % valName or "org" %% "artifact" % valName
40
+ # Also handles dotted references like Object.member
41
+ VAL_REF_DEP_REGEX = T.let(
42
+ /"(?<group>[^"]+)"\s+(?<op>%%?)\s+"(?<artifact>[^"]+)"\s+%\s+(?<val_name>[a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)/,
43
+ Regexp
44
+ )
45
+
46
+ # addSbtPlugin("org" % "name" % "version")
47
+ PLUGIN_DEP_REGEX = T.let(
48
+ /addSbtPlugin\(\s*"(?<group>[^"]+)"\s+%\s+"(?<artifact>[^"]+)"\s+%\s+"(?<version>[^"]+)"\s*\)/,
49
+ Regexp
50
+ )
51
+
52
+ # addSbtPlugin("org" % "name" % valName)
53
+ # Also handles dotted references like Object.member
54
+ PLUGIN_VAL_REF_REGEX = T.let(
55
+ /addSbtPlugin\(\s*"(?<group>[^"]+)"\s+%\s+"(?<artifact>[^"]+)"\s+%\s+(?<val_name>#{IDENT_OR_DOTTED})\s*\)/,
56
+ Regexp
57
+ )
58
+
59
+ # sbt.version=1.x.y in build.properties
60
+ SBT_VERSION_REGEX = T.let(
61
+ /\Asbt\.version\s*=\s*(?<version>.+)\z/,
62
+ Regexp
63
+ )
64
+
65
+ # scalaVersion := "2.13.12" or ThisBuild / scalaVersion := "2.13.12"
66
+ # Also: scalaVersion in ThisBuild := "2.13.12" (older SBT syntax)
67
+ SCALA_VERSION_REGEX = T.let(
68
+ %r{(?:ThisBuild\s*/\s*)?(?:scalaVersion\s+in\s+ThisBuild|scalaVersion)\s*:=\s*"(?<version>[^"]+)"},
69
+ Regexp
70
+ )
71
+
72
+ # scalaVersion := valRef or scalaVersion in ThisBuild := V.scala212
73
+ SCALA_VERSION_VAL_REGEX = T.let(
74
+ %r{(?:ThisBuild\s*/\s*)?(?:scalaVersion\s+in\s+ThisBuild|scalaVersion)\s*:=\s*(?<val_name>#{IDENT_OR_DOTTED})},
75
+ Regexp
76
+ )
77
+
78
+ sig { override.returns(T::Array[Dependabot::Dependency]) }
79
+ def parse
80
+ dependency_set = DependencySet.new
81
+
82
+ sbt_files.each do |buildfile|
83
+ dependency_set += buildfile_dependencies(buildfile)
84
+ end
85
+
86
+ scala_build_files.each do |buildfile|
87
+ dependency_set += buildfile_dependencies(buildfile)
88
+ end
89
+
90
+ build_properties_files.each do |properties_file|
91
+ dependency_set += sbt_version_dependency(properties_file)
92
+ end
93
+
94
+ sbt_files.each do |buildfile|
95
+ dependency_set += scala_version_dependency(buildfile)
96
+ end
97
+
98
+ dependency_set.dependencies
99
+ end
100
+
101
+ sig { returns(Ecosystem) }
102
+ def ecosystem
103
+ @ecosystem ||= T.let(
104
+ Ecosystem.new(
105
+ name: ECOSYSTEM,
106
+ package_manager: package_manager,
107
+ language: language
108
+ ),
109
+ T.nilable(Ecosystem)
110
+ )
111
+ end
112
+
113
+ private
114
+
115
+ sig { returns(Ecosystem::VersionManager) }
116
+ def package_manager
117
+ @package_manager ||= T.let(
118
+ PackageManager.new(sbt_raw_version),
119
+ T.nilable(Dependabot::Sbt::PackageManager)
120
+ )
121
+ end
122
+
123
+ sig { returns(T.nilable(Ecosystem::VersionManager)) }
124
+ def language
125
+ @language ||= T.let(
126
+ Language.new(scala_full_version),
127
+ T.nilable(Dependabot::Sbt::Language)
128
+ )
129
+ end
130
+
131
+ sig { returns(String) }
132
+ def sbt_raw_version
133
+ build_properties_files.each do |file|
134
+ T.must(file.content).each_line do |line|
135
+ match = line.strip.match(SBT_VERSION_REGEX)
136
+ return T.must(match[:version]).strip if match
137
+ end
138
+ end
139
+ "NOT-AVAILABLE"
140
+ end
141
+
142
+ sig { returns(String) }
143
+ def scala_full_version
144
+ sbt_files.each do |file|
145
+ version = resolve_scala_version(file)
146
+ return version if version
147
+ end
148
+ "NOT-AVAILABLE"
149
+ end
150
+
151
+ sig { params(buildfile: Dependabot::DependencyFile).returns(DependencySet) }
152
+ def buildfile_dependencies(buildfile)
153
+ dependency_set = DependencySet.new
154
+
155
+ dependency_set += literal_version_dependencies(buildfile)
156
+ dependency_set += val_ref_dependencies(buildfile)
157
+ dependency_set += plugin_dependencies(buildfile)
158
+ dependency_set += plugin_val_ref_dependencies(buildfile)
159
+
160
+ dependency_set
161
+ end
162
+
163
+ sig { params(buildfile: Dependabot::DependencyFile).returns(DependencySet) }
164
+ def literal_version_dependencies(buildfile)
165
+ dependency_set = DependencySet.new
166
+
167
+ content_without_plugins(buildfile).scan(LIBRARY_DEP_REGEX) do
168
+ captures = T.must(Regexp.last_match).named_captures
169
+ group = T.must(captures.fetch("group"))
170
+ artifact = T.must(captures.fetch("artifact"))
171
+ version = T.must(captures.fetch("version"))
172
+ cross_versioned = captures.fetch("op") == "%%"
173
+
174
+ dep_name = build_dependency_name(group, artifact, cross_versioned, buildfile)
175
+
176
+ next unless Sbt::Version.correct?(version)
177
+
178
+ meta = cross_versioned ? { packaging_type: "cross-versioned" } : nil
179
+ dependency_set << new_dependency(
180
+ name: dep_name,
181
+ version: version,
182
+ file: buildfile.name,
183
+ metadata: meta
184
+ )
185
+ end
186
+
187
+ dependency_set
188
+ end
189
+
190
+ sig { params(buildfile: Dependabot::DependencyFile).returns(DependencySet) }
191
+ def val_ref_dependencies(buildfile)
192
+ dependency_set = DependencySet.new
193
+
194
+ content_without_plugins(buildfile).scan(VAL_REF_DEP_REGEX) do
195
+ captures = T.must(Regexp.last_match).named_captures
196
+ group = T.must(captures.fetch("group"))
197
+ artifact = T.must(captures.fetch("artifact"))
198
+ val_name = T.must(captures.fetch("val_name"))
199
+ cross_versioned = captures.fetch("op") == "%%"
200
+
201
+ property_details = property_value_finder.property_details(
202
+ property_name: val_name,
203
+ callsite_buildfile: buildfile
204
+ )
205
+ next unless property_details
206
+
207
+ version = property_details[:value]
208
+ next unless version
209
+ next unless Sbt::Version.correct?(version)
210
+
211
+ dep_name = build_dependency_name(group, artifact, cross_versioned, buildfile)
212
+
213
+ metadata = T.let(
214
+ { property_name: val_name, property_source: property_details[:file] },
215
+ T::Hash[Symbol, T.untyped]
216
+ )
217
+ metadata[:packaging_type] = "cross-versioned" if cross_versioned
218
+
219
+ dependency_set << new_dependency(
220
+ name: dep_name,
221
+ version: version,
222
+ file: buildfile.name,
223
+ metadata: metadata
224
+ )
225
+ end
226
+
227
+ dependency_set
228
+ end
229
+
230
+ sig { params(buildfile: Dependabot::DependencyFile).returns(DependencySet) }
231
+ def plugin_dependencies(buildfile)
232
+ dependency_set = DependencySet.new
233
+
234
+ prepared_content(buildfile).scan(PLUGIN_DEP_REGEX) do
235
+ captures = T.must(Regexp.last_match).named_captures
236
+ group = T.must(captures.fetch("group"))
237
+ artifact = T.must(captures.fetch("artifact"))
238
+ version = T.must(captures.fetch("version"))
239
+
240
+ next unless Sbt::Version.correct?(version)
241
+
242
+ dependency_set << new_dependency(
243
+ name: "#{group}:#{artifact}",
244
+ version: version,
245
+ file: buildfile.name,
246
+ groups: ["plugins"]
247
+ )
248
+ end
249
+
250
+ dependency_set
251
+ end
252
+
253
+ sig { params(buildfile: Dependabot::DependencyFile).returns(DependencySet) }
254
+ def plugin_val_ref_dependencies(buildfile)
255
+ dependency_set = DependencySet.new
256
+
257
+ prepared_content(buildfile).scan(PLUGIN_VAL_REF_REGEX) do
258
+ captures = T.must(Regexp.last_match).named_captures
259
+ group = T.must(captures.fetch("group"))
260
+ artifact = T.must(captures.fetch("artifact"))
261
+ val_name = T.must(captures.fetch("val_name"))
262
+
263
+ property_details = property_value_finder.property_details(
264
+ property_name: val_name,
265
+ callsite_buildfile: buildfile
266
+ )
267
+ next unless property_details
268
+
269
+ version = property_details[:value]
270
+ next unless version
271
+ next unless Sbt::Version.correct?(version)
272
+
273
+ meta = { property_name: val_name, property_source: property_details[:file] }
274
+ dependency_set << new_dependency(
275
+ name: "#{group}:#{artifact}",
276
+ version: version,
277
+ file: buildfile.name,
278
+ groups: ["plugins"],
279
+ metadata: meta
280
+ )
281
+ end
282
+
283
+ dependency_set
284
+ end
285
+
286
+ sig { params(properties_file: Dependabot::DependencyFile).returns(DependencySet) }
287
+ def sbt_version_dependency(properties_file)
288
+ dependency_set = DependencySet.new
289
+
290
+ T.must(properties_file.content).each_line do |line|
291
+ line = line.strip
292
+ next if line.empty? || line.start_with?("#", "!")
293
+
294
+ match = line.match(SBT_VERSION_REGEX)
295
+ next unless match
296
+
297
+ version = T.must(match[:version]).strip
298
+ next unless Sbt::Version.correct?(version)
299
+
300
+ dependency_set << new_dependency(
301
+ name: "org.scala-sbt:sbt",
302
+ version: version,
303
+ file: properties_file.name,
304
+ metadata: { property_source: "build.properties" }
305
+ )
306
+
307
+ break
308
+ end
309
+
310
+ dependency_set
311
+ end
312
+
313
+ sig { params(buildfile: Dependabot::DependencyFile).returns(DependencySet) }
314
+ def scala_version_dependency(buildfile)
315
+ dependency_set = DependencySet.new
316
+
317
+ match = prepared_content(buildfile).match(SCALA_VERSION_REGEX)
318
+ return dependency_set unless match
319
+
320
+ version = T.must(match[:version])
321
+ return dependency_set unless Sbt::Version.correct?(version)
322
+
323
+ # Scala 3 uses scala3-library, Scala 2 uses scala-library
324
+ dep_name = version.start_with?("3") ? "org.scala-lang:scala3-library_3" : "org.scala-lang:scala-library"
325
+
326
+ dependency_set << new_dependency(
327
+ name: dep_name,
328
+ version: version,
329
+ file: buildfile.name,
330
+ metadata: { property_source: "scalaVersion" }
331
+ )
332
+
333
+ dependency_set
334
+ end
335
+
336
+ sig do
337
+ params(
338
+ group: String,
339
+ artifact: String,
340
+ cross_versioned: T::Boolean,
341
+ buildfile: Dependabot::DependencyFile
342
+ ).returns(String)
343
+ end
344
+ def build_dependency_name(group, artifact, cross_versioned, buildfile)
345
+ if cross_versioned
346
+ scala_major = scala_major_version_for(buildfile)
347
+ "#{group}:#{artifact}_#{scala_major}"
348
+ else
349
+ "#{group}:#{artifact}"
350
+ end
351
+ end
352
+
353
+ sig { params(buildfile: Dependabot::DependencyFile).returns(String) }
354
+ def scala_major_version_for(buildfile)
355
+ # Check the buildfile itself first, then fall back to root build.sbt
356
+ files_to_check = [buildfile]
357
+ root = sbt_files.find { |f| f.name == "build.sbt" }
358
+ files_to_check << root if root && root != buildfile
359
+
360
+ files_to_check.each do |file|
361
+ version = resolve_scala_version(file)
362
+ return extract_scala_major(version) if version
363
+ end
364
+
365
+ # Default to 2.13 if scalaVersion is not declared
366
+ "2.13"
367
+ end
368
+
369
+ # Resolves scalaVersion from a file, trying literal value first, then val reference.
370
+ sig { params(file: Dependabot::DependencyFile).returns(T.nilable(String)) }
371
+ def resolve_scala_version(file)
372
+ match = prepared_content(file).match(SCALA_VERSION_REGEX)
373
+ return T.must(match[:version]) if match
374
+
375
+ val_match = prepared_content(file).match(SCALA_VERSION_VAL_REGEX)
376
+ return nil unless val_match
377
+
378
+ val_name = T.must(val_match[:val_name])
379
+ property_value_finder.property_value(
380
+ property_name: val_name,
381
+ callsite_buildfile: file
382
+ )
383
+ end
384
+
385
+ sig { params(full_version: String).returns(String) }
386
+ def extract_scala_major(full_version)
387
+ parts = full_version.split(".")
388
+ parts[0] == "3" ? "3" : "#{parts[0]}.#{parts[1]}"
389
+ end
390
+
391
+ sig { params(buildfile: Dependabot::DependencyFile).returns(String) }
392
+ def prepared_content(buildfile)
393
+ T.must(buildfile.content)
394
+ .gsub(%r{(?<=^|\s)//.*$}, "\n")
395
+ .gsub(%r{(?<=^|\s)/\*.*?\*/}m, "")
396
+ end
397
+
398
+ # Returns prepared content with addSbtPlugin(...) calls removed so that
399
+ # LIBRARY_DEP_REGEX and VAL_REF_DEP_REGEX do not duplicate plugin matches.
400
+ sig { params(buildfile: Dependabot::DependencyFile).returns(String) }
401
+ def content_without_plugins(buildfile)
402
+ prepared_content(buildfile).gsub(/addSbtPlugin\([^)]*\)/, "")
403
+ end
404
+
405
+ sig { returns(PropertyValueFinder) }
406
+ def property_value_finder
407
+ @property_value_finder ||= T.let(
408
+ PropertyValueFinder.new(dependency_files: dependency_files),
409
+ T.nilable(PropertyValueFinder)
410
+ )
411
+ end
412
+
413
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
414
+ def sbt_files
415
+ @sbt_files ||= T.let(
416
+ dependency_files.select { |f| f.name.end_with?(".sbt") },
417
+ T.nilable(T::Array[Dependabot::DependencyFile])
418
+ )
419
+ end
420
+
421
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
422
+ def build_properties_files
423
+ @build_properties_files ||= T.let(
424
+ dependency_files.select { |f| f.name.end_with?("build.properties") },
425
+ T.nilable(T::Array[Dependabot::DependencyFile])
426
+ )
427
+ end
428
+
429
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
430
+ def scala_build_files
431
+ @scala_build_files ||= T.let(
432
+ dependency_files.select { |f| f.name.end_with?(".scala") && f.name.start_with?("project/") },
433
+ T.nilable(T::Array[Dependabot::DependencyFile])
434
+ )
435
+ end
436
+
437
+ sig do
438
+ params(
439
+ name: String,
440
+ version: String,
441
+ file: String,
442
+ groups: T::Array[String],
443
+ metadata: T.nilable(T::Hash[Symbol, T.untyped])
444
+ ).returns(Dependabot::Dependency)
445
+ end
446
+ def new_dependency(name:, version:, file:, groups: [], metadata: nil)
447
+ Dependency.new(
448
+ name: name,
449
+ version: version,
450
+ requirements: [{
451
+ requirement: version, file: file,
452
+ source: nil, groups: groups, metadata: metadata
453
+ }],
454
+ package_manager: "sbt"
455
+ )
456
+ end
457
+
458
+ sig { override.void }
459
+ def check_required_files
460
+ return if dependency_files.any? { |f| f.name.end_with?(BUILD_SBT_FILENAME) }
461
+
462
+ raise "No build.sbt!"
463
+ end
464
+ end
465
+ end
466
+ end
467
+
468
+ Dependabot::FileParsers.register("sbt", Dependabot::Sbt::FileParser)
@@ -0,0 +1,28 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/sbt/file_updater"
7
+ require "dependabot/sbt/file_parser/property_value_finder"
8
+ require "dependabot/maven/shared/shared_property_value_updater"
9
+
10
+ module Dependabot
11
+ module Sbt
12
+ class FileUpdater < Dependabot::FileUpdaters::Base
13
+ class PropertyValueUpdater < Dependabot::Maven::Shared::SharedPropertyValueUpdater
14
+ extend T::Sig
15
+
16
+ private
17
+
18
+ sig { override.returns(Sbt::FileParser::PropertyValueFinder) }
19
+ def property_value_finder
20
+ @property_value_finder ||= T.let(
21
+ Sbt::FileParser::PropertyValueFinder.new(dependency_files: dependency_files),
22
+ T.nilable(Sbt::FileParser::PropertyValueFinder)
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end