dependabot-go_modules 0.373.0 → 0.374.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27e3533322390a8ec28d44ef8886cc6b0a95844f16220d86bb4515a0d5c1f06d
4
- data.tar.gz: 26b876d7cf2a17b3422f5127541fbb309d929e18bfda9fb85eb024d33cef38b4
3
+ metadata.gz: 79d4612bc061dae5110e47d2213548ed4dc3b2d93fadb04494cb85f7b78fcef5
4
+ data.tar.gz: aacdfc5d7c13bf0a8e3c1fb9db5ab9e30dfb3fbcc0adcd1059cea0551eee5ade
5
5
  SHA512:
6
- metadata.gz: 7a48980f9cd636da1c7b9c0c014c21c86d38b5862f72d3b9a66cfa1b2a8b94c10684126cb48798d26b5448c98f2cb37510ad3296fd66deb85da05f3f492c4931
7
- data.tar.gz: de39bdb1b4a5bfeea0e00c6d97c91fb1f7306e4d128a0a254e58d36c2bbcfb5d9f7e33d570547efce890d3709a8edb001cf1f9a35d80edfa658cc349c76fbfcc
6
+ metadata.gz: 4a7e6bbb25f8999253090edc7639c804df977ad05495b6f562579bc4ef06799b317fd166e7a896ca5bc1801652cdfaa580a4e3da3dcf901cfdb10dc641e7397d
7
+ data.tar.gz: f5bbfefc7577f2d65e035b2b976fe2824cc423b063dd62875f1af201185c32332a6daf4f6c603d5e5165b75aa664728aec712fabf8ce9be529a1b8002546a083
@@ -4,6 +4,7 @@
4
4
  require "sorbet-runtime"
5
5
  require "dependabot/file_fetchers"
6
6
  require "dependabot/file_fetchers/base"
7
+ require "dependabot/go_modules/go_work_parser"
7
8
 
8
9
  module Dependabot
9
10
  module GoModules
@@ -13,41 +14,68 @@ module Dependabot
13
14
 
14
15
  sig { override.params(filenames: T::Array[String]).returns(T::Boolean) }
15
16
  def self.required_files_in?(filenames)
16
- filenames.include?("go.mod")
17
+ filenames.include?("go.mod") || filenames.include?("go.work")
17
18
  end
18
19
 
19
20
  sig { override.returns(String) }
20
21
  def self.required_files_message
21
- "Repo must contain a go.mod."
22
+ "Repo must contain a go.mod or go.work."
22
23
  end
23
24
 
24
25
  sig { override.returns(T::Hash[Symbol, T.untyped]) }
25
26
  def ecosystem_versions
27
+ version = go_version_from_file(go_mod) ||
28
+ go_version_from_file(go_work) ||
29
+ all_workspace_go_mods.filter_map { |f| go_version_from_file(f) }.first ||
30
+ "unknown"
31
+
26
32
  {
27
33
  package_managers: {
28
- "gomod" => go_mod&.content&.match(/^go\s(\d+\.\d+)/)&.captures&.first || "unknown"
34
+ "gomod" => version
29
35
  }
30
36
  }
31
37
  end
32
38
 
33
39
  sig { override.returns(T::Array[DependencyFile]) }
34
40
  def fetch_files
35
- # Ensure we always check out the full repo contents for go_module
36
- # updates.
37
- SharedHelpers.in_a_temporary_repo_directory(
38
- directory,
39
- clone_repo_contents
40
- ) do
41
- fetched_files = go_mod ? [go_mod] : []
42
- # Fetch the (optional) go.sum
43
- fetched_files << T.must(go_sum) if go_sum
44
- fetched_files << T.must(go_env) if go_env
41
+ SharedHelpers.in_a_temporary_repo_directory(directory, clone_repo_contents) do
42
+ fetched_files = collect_dependency_files
43
+ validate_files!(fetched_files)
45
44
  fetched_files
46
45
  end
47
46
  end
48
47
 
49
48
  private
50
49
 
50
+ sig { returns(T::Array[DependencyFile]) }
51
+ def collect_dependency_files
52
+ fetched_files = T.let([], T::Array[DependencyFile])
53
+
54
+ if go_work
55
+ fetched_files << T.must(go_work)
56
+ fetched_files << T.must(go_work_sum) if go_work_sum
57
+ fetched_files.concat(workspace_module_files)
58
+ else
59
+ fetched_files << T.must(go_mod) if go_mod
60
+ fetched_files << T.must(go_sum) if go_sum
61
+ end
62
+
63
+ fetched_files << T.must(go_env) if go_env
64
+
65
+ fetched_files
66
+ end
67
+
68
+ sig { params(files: T::Array[DependencyFile]).void }
69
+ def validate_files!(files)
70
+ return if files.any? { |f| f.name.end_with?("go.mod") }
71
+
72
+ error_msg = go_work ? "No go.mod files found in workspace" : "No go.mod files found"
73
+ raise Dependabot::DependencyFileNotFound.new(
74
+ "go.mod",
75
+ error_msg
76
+ )
77
+ end
78
+
51
79
  sig { returns(T.nilable(Dependabot::DependencyFile)) }
52
80
  def go_mod
53
81
  @go_mod ||= T.let(fetch_file_if_present("go.mod"), T.nilable(Dependabot::DependencyFile))
@@ -58,6 +86,11 @@ module Dependabot
58
86
  @go_sum ||= T.let(fetch_file_if_present("go.sum"), T.nilable(Dependabot::DependencyFile))
59
87
  end
60
88
 
89
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
90
+ def go_work_sum
91
+ @go_work_sum ||= T.let(fetch_file_if_present("go.work.sum"), T.nilable(Dependabot::DependencyFile))
92
+ end
93
+
61
94
  sig { returns(T.nilable(Dependabot::DependencyFile)) }
62
95
  def go_env
63
96
  return @go_env if defined?(@go_env)
@@ -65,6 +98,66 @@ module Dependabot
65
98
  @go_env = T.let(fetch_support_file("go.env"), T.nilable(Dependabot::DependencyFile))
66
99
  @go_env
67
100
  end
101
+
102
+ sig { params(file: T.nilable(Dependabot::DependencyFile)).returns(T.nilable(String)) }
103
+ def go_version_from_file(file)
104
+ file&.content&.match(/^go\s+(\d+\.\d+)/)&.captures&.first
105
+ end
106
+
107
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
108
+ def all_workspace_go_mods
109
+ return [] unless go_work
110
+
111
+ workspace_module_paths.filter_map do |module_path|
112
+ name = module_path == "." ? "go.mod" : File.join(module_path, "go.mod")
113
+ fetch_file_if_present(name)
114
+ end
115
+ end
116
+
117
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
118
+ def go_work
119
+ @go_work ||= T.let(fetch_file_if_present("go.work"), T.nilable(Dependabot::DependencyFile))
120
+ end
121
+
122
+ sig { returns(T::Array[String]) }
123
+ def workspace_module_paths
124
+ return [] unless go_work
125
+
126
+ content = T.must(T.must(go_work).content)
127
+ GoWorkParser.use_paths(content)
128
+ .select { |p| valid_module_path?(p) }
129
+ end
130
+
131
+ sig { params(path: String).returns(T::Boolean) }
132
+ def valid_module_path?(path)
133
+ return false if path.empty?
134
+ return false if Pathname.new(path).absolute?
135
+ return false if path.include?("\0")
136
+
137
+ clean = Pathname.new(path).cleanpath.to_s
138
+ return false if clean.start_with?("../")
139
+
140
+ true
141
+ end
142
+
143
+ sig { returns(T::Array[DependencyFile]) }
144
+ def workspace_module_files
145
+ files = T.let([], T::Array[DependencyFile])
146
+
147
+ workspace_module_paths.each do |module_path|
148
+ mod_name = module_path == "." ? "go.mod" : File.join(module_path, "go.mod")
149
+ mod_file = fetch_file_if_present(mod_name)
150
+ next unless mod_file
151
+
152
+ files << mod_file
153
+
154
+ sum_name = module_path == "." ? "go.sum" : File.join(module_path, "go.sum")
155
+ sum_file = fetch_file_if_present(sum_name)
156
+ files << sum_file if sum_file
157
+ end
158
+
159
+ files
160
+ end
68
161
  end
69
162
  end
70
163
  end
@@ -6,6 +6,7 @@ require "sorbet-runtime"
6
6
  require "open3"
7
7
  require "dependabot/dependency"
8
8
  require "dependabot/file_parsers/base/dependency_set"
9
+ require "dependabot/go_modules/go_work_parser"
9
10
  require "dependabot/go_modules/path_converter"
10
11
  require "dependabot/go_modules/replace_stubber"
11
12
  require "dependabot/errors"
@@ -52,11 +53,13 @@ module Dependabot
52
53
  def parse
53
54
  dependency_set = Dependabot::FileParsers::Base::DependencySet.new
54
55
 
55
- required_packages.each do |hsh|
56
- unless skip_dependency?(hsh) # rubocop:disable Style/Next
56
+ if workspace?
57
+ parse_workspace_dependencies(dependency_set)
58
+ else
59
+ required_packages.each do |hsh|
60
+ next if skip_dependency?(hsh)
57
61
 
58
- dep = dependency_from_details(hsh)
59
- dependency_set << dep
62
+ dependency_set << dependency_from_details(hsh)
60
63
  end
61
64
  end
62
65
 
@@ -190,9 +193,98 @@ module Dependabot
190
193
  @go_env ||= T.let(get_original_file("go.env"), T.nilable(Dependabot::DependencyFile))
191
194
  end
192
195
 
196
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
197
+ def go_work
198
+ @go_work ||= T.let(get_original_file("go.work"), T.nilable(Dependabot::DependencyFile))
199
+ end
200
+
201
+ sig { returns(T::Boolean) }
202
+ def workspace?
203
+ !go_work.nil?
204
+ end
205
+
206
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
207
+ def all_go_mods
208
+ @all_go_mods ||= T.let(
209
+ if go_work
210
+ workspace_mod_names = GoWorkParser.use_paths(T.must(T.must(go_work).content)).map do |path|
211
+ path == "." ? "go.mod" : "#{path}/go.mod"
212
+ end
213
+ dependency_files.select { |f| workspace_mod_names.include?(f.name) }
214
+ else
215
+ dependency_files.select { |f| f.name.end_with?("go.mod") }
216
+ end,
217
+ T.nilable(T::Array[Dependabot::DependencyFile])
218
+ )
219
+ end
220
+
221
+ sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).void }
222
+ def parse_workspace_dependencies(dependency_set)
223
+ all_go_mods.each do |mod_file|
224
+ parse_single_module(mod_file).each do |dep|
225
+ dependency_set << dep
226
+ end
227
+ end
228
+ end
229
+
230
+ sig { params(mod_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::Dependency]) }
231
+ def parse_single_module(mod_file)
232
+ SharedHelpers.in_a_temporary_directory do |path|
233
+ File.write("go.mod", mod_file.content)
234
+
235
+ command = "go mod edit -json"
236
+ stdout, stderr, status = Open3.capture3(command)
237
+ handle_parser_error(path, stderr, file_path: mod_file.path) unless status.success?
238
+
239
+ parsed = JSON.parse(stdout)
240
+ packages = parsed["Require"] || []
241
+
242
+ packages.filter_map do |hsh|
243
+ next if skip_dependency_in_manifest?(hsh, parsed)
244
+
245
+ source = { type: "default", source: hsh["Path"] }
246
+ version = hsh["Version"]&.sub(/^v?/, "")
247
+
248
+ reqs = [{
249
+ requirement: hsh["Version"],
250
+ file: mod_file.name,
251
+ source: source,
252
+ groups: []
253
+ }]
254
+
255
+ Dependency.new(
256
+ name: hsh["Path"],
257
+ version: version,
258
+ requirements: hsh["Indirect"] ? [] : reqs,
259
+ package_manager: "go_modules"
260
+ )
261
+ end
262
+ end
263
+ end
264
+
265
+ sig { params(dep: T::Hash[String, T.untyped], mod_manifest: T::Hash[String, T.untyped]).returns(T::Boolean) }
266
+ def skip_dependency_in_manifest?(dep, mod_manifest)
267
+ return true if dependency_is_replaced_in?(dep, mod_manifest)
268
+
269
+ path_uri = URI.parse("https://#{dep['Path']}")
270
+ !path_uri.host&.include?(".")
271
+ rescue URI::InvalidURIError
272
+ false
273
+ end
274
+
275
+ sig { params(details: T::Hash[String, T.untyped], mod_manifest: T::Hash[String, T.untyped]).returns(T::Boolean) }
276
+ def dependency_is_replaced_in?(details, mod_manifest)
277
+ return false unless mod_manifest["Replace"]
278
+
279
+ mod_manifest["Replace"].any? do |replace|
280
+ replace["Old"]["Path"] == details["Path"] &&
281
+ (!replace["Old"]["Version"] || replace["Old"]["Version"] == details["Version"])
282
+ end
283
+ end
284
+
193
285
  sig { override.void }
194
286
  def check_required_files
195
- raise "No go.mod!" unless go_mod
287
+ raise "No go.mod or go.work!" unless go_mod || go_work
196
288
  end
197
289
 
198
290
  sig { params(details: T::Hash[String, T.untyped]).returns(Dependabot::Dependency) }
@@ -264,10 +356,11 @@ module Dependabot
264
356
  end
265
357
  end
266
358
 
267
- sig { params(path: T.any(Pathname, String), stderr: String).returns(T.noreturn) }
268
- def handle_parser_error(path, stderr)
359
+ sig { params(path: T.any(Pathname, String), stderr: String, file_path: T.nilable(String)).returns(T.noreturn) }
360
+ def handle_parser_error(path, stderr, file_path: nil)
269
361
  msg = stderr.gsub(path.to_s, "").strip
270
- raise Dependabot::DependencyFileNotParseable.new(T.must(go_mod).path, msg)
362
+ resolved_path = file_path || go_mod&.path || go_work&.path || "go.mod"
363
+ raise Dependabot::DependencyFileNotParseable.new(resolved_path, msg)
271
364
  end
272
365
 
273
366
  sig { params(dep: T::Hash[String, T.untyped]).returns(T::Boolean) }
@@ -7,13 +7,14 @@ require "dependabot/shared_helpers"
7
7
  require "dependabot/errors"
8
8
  require "dependabot/logger"
9
9
  require "dependabot/go_modules/file_updater"
10
+ require "dependabot/go_modules/go_work_parser"
10
11
  require "dependabot/go_modules/replace_stubber"
11
12
  require "dependabot/go_modules/resolvability_errors"
12
13
 
13
14
  module Dependabot
14
15
  module GoModules
15
16
  class FileUpdater
16
- class GoModUpdater
17
+ class GoModUpdater # rubocop:disable Metrics/ClassLength
17
18
  extend T::Sig
18
19
 
19
20
  RESOLVABILITY_ERROR_REGEXES = T.let(
@@ -151,6 +152,14 @@ module Dependabot
151
152
  updated_files[:go_sum]
152
153
  end
153
154
 
155
+ sig { returns(T::Hash[String, String]) }
156
+ def updated_workspace_module_files
157
+ @updated_workspace_module_files ||= T.let(
158
+ update_workspace_files,
159
+ T.nilable(T::Hash[String, String])
160
+ )
161
+ end
162
+
154
163
  private
155
164
 
156
165
  sig { returns(T::Array[Dependabot::Dependency]) }
@@ -220,6 +229,109 @@ module Dependabot
220
229
  end
221
230
  end
222
231
 
232
+ sig { returns(T::Hash[String, String]) }
233
+ def update_workspace_files
234
+ in_repo_path do
235
+ dependency_files.each do |file|
236
+ path = Pathname.new(file.name).expand_path
237
+ FileUtils.mkdir_p(path.dirname)
238
+ File.write(path, file.content)
239
+ end
240
+
241
+ # Run `go get dep@version` in each module directory so every go.mod
242
+ # that requires the dependency gets the version bump, not just the first.
243
+ # Follow with a bare `go get` validation pass per module, matching the
244
+ # single-module update path's intent (see run_go_get comment).
245
+ workspace_module_paths.each do |mod_dir|
246
+ Dir.chdir(mod_dir) do
247
+ run_go_get(dependencies)
248
+ run_go_get
249
+ end
250
+ end
251
+
252
+ run_go_work_sync
253
+ run_workspace_tidy
254
+
255
+ collect_workspace_file_contents
256
+ end
257
+ end
258
+
259
+ sig { void }
260
+ def run_go_work_sync
261
+ command = "go work sync"
262
+ _, stderr, status = Open3.capture3(command)
263
+ return if status.success?
264
+
265
+ handle_subprocess_error(stderr)
266
+ end
267
+
268
+ sig { void }
269
+ def run_workspace_tidy
270
+ return unless tidy?
271
+
272
+ workspace_module_paths.each do |mod_path|
273
+ Dir.chdir(mod_path) do
274
+ command = "go mod tidy -e"
275
+ _, stderr, status = Open3.capture3(command)
276
+ if status.success?
277
+ Dependabot.logger.info "`go mod tidy` succeeded in #{mod_path}"
278
+ else
279
+ Dependabot.logger.info "Failed to `go mod tidy` in #{mod_path}: #{stderr}"
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ sig { returns(T::Array[String]) }
286
+ def workspace_module_paths
287
+ go_work_file = dependency_files.find { |f| f.name.end_with?("go.work") }
288
+ return ["."] unless go_work_file
289
+
290
+ fetched_mod_names = dependency_files.select { |f| f.name.end_with?("go.mod") }
291
+ .to_set(&:name)
292
+
293
+ GoWorkParser.use_paths(T.must(go_work_file.content))
294
+ .select { |p| valid_workspace_path?(p) && fetched_mod_names.include?(workspace_mod_name(p)) }
295
+ .map { |p| p == "." ? "." : "./#{p}" }
296
+ end
297
+
298
+ sig { params(path: String).returns(T::Boolean) }
299
+ def valid_workspace_path?(path)
300
+ return false if Pathname.new(path).absolute?
301
+
302
+ !Pathname.new(path).cleanpath.to_s.start_with?("../")
303
+ end
304
+
305
+ sig { params(use_path: String).returns(String) }
306
+ def workspace_mod_name(use_path)
307
+ use_path == "." ? "go.mod" : "#{use_path}/go.mod"
308
+ end
309
+
310
+ sig { returns(T::Hash[String, String]) }
311
+ def collect_workspace_file_contents
312
+ results = T.let({}, T::Hash[String, String])
313
+
314
+ workspace_module_paths.each do |mod_path|
315
+ relative_base = mod_path.delete_prefix("./")
316
+
317
+ mod_file = File.join(mod_path, "go.mod")
318
+ if File.exist?(mod_file)
319
+ key = relative_base.empty? || relative_base == "." ? "go.mod" : "#{relative_base}/go.mod"
320
+ results[key] = File.read(mod_file)
321
+ end
322
+
323
+ sum_file = File.join(mod_path, "go.sum")
324
+ next unless File.exist?(sum_file)
325
+
326
+ key = relative_base.empty? || relative_base == "." ? "go.sum" : "#{relative_base}/go.sum"
327
+ results[key] = File.read(sum_file)
328
+ end
329
+
330
+ results["go.work.sum"] = File.read("go.work.sum") if File.exist?("go.work.sum")
331
+
332
+ results
333
+ end
334
+
223
335
  sig { void }
224
336
  def run_go_mod_tidy
225
337
  return unless tidy?
@@ -7,6 +7,7 @@ require "dependabot/shared_helpers"
7
7
  require "dependabot/file_updaters"
8
8
  require "dependabot/file_updaters/base"
9
9
  require "dependabot/file_updaters/vendor_updater"
10
+ require "dependabot/go_modules/go_work_parser"
10
11
 
11
12
  module Dependabot
12
13
  module GoModules
@@ -37,7 +38,55 @@ module Dependabot
37
38
 
38
39
  sig { override.returns(T::Array[Dependabot::DependencyFile]) }
39
40
  def updated_dependency_files
40
- updated_files = []
41
+ updated_files = if workspace?
42
+ updated_workspace_files
43
+ else
44
+ updated_single_module_files
45
+ end
46
+
47
+ raise "No files changed!" if updated_files.none?
48
+
49
+ updated_files
50
+ end
51
+
52
+ private
53
+
54
+ sig { params(go_mod: Dependabot::DependencyFile).returns(T::Boolean) }
55
+ def dependency_changed?(go_mod)
56
+ # file_changed? only checks for changed requirements. Need to check for indirect dep version changes too.
57
+ file_changed?(go_mod) || dependencies.any? { |dep| dep.previous_version != dep.version }
58
+ end
59
+
60
+ sig { override.void }
61
+ def check_required_files
62
+ return if go_mod || go_work
63
+
64
+ raise "No go.mod or go.work!"
65
+ end
66
+
67
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
68
+ def go_mod
69
+ @go_mod ||= T.let(get_original_file("go.mod"), T.nilable(Dependabot::DependencyFile))
70
+ end
71
+
72
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
73
+ def go_sum
74
+ @go_sum ||= T.let(get_original_file("go.sum"), T.nilable(Dependabot::DependencyFile))
75
+ end
76
+
77
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
78
+ def go_work
79
+ @go_work ||= T.let(get_original_file("go.work"), T.nilable(Dependabot::DependencyFile))
80
+ end
81
+
82
+ sig { returns(T::Boolean) }
83
+ def workspace?
84
+ !go_work.nil?
85
+ end
86
+
87
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
88
+ def updated_single_module_files
89
+ updated_files = T.let([], T::Array[Dependabot::DependencyFile])
41
90
 
42
91
  if go_mod && dependency_changed?(T.must(go_mod))
43
92
  updated_files <<
@@ -60,34 +109,51 @@ module Dependabot
60
109
  end
61
110
  end
62
111
 
63
- raise "No files changed!" if updated_files.none?
64
-
65
112
  updated_files
66
113
  end
67
114
 
68
- private
115
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
116
+ def updated_workspace_files
117
+ check_workspace_not_vendored!
69
118
 
70
- sig { params(go_mod: Dependabot::DependencyFile).returns(T::Boolean) }
71
- def dependency_changed?(go_mod)
72
- # file_changed? only checks for changed requirements. Need to check for indirect dep version changes too.
73
- file_changed?(go_mod) || dependencies.any? { |dep| dep.previous_version != dep.version }
74
- end
119
+ updated_files = T.let([], T::Array[Dependabot::DependencyFile])
120
+ workspace_results = file_updater.updated_workspace_module_files
75
121
 
76
- sig { override.void }
77
- def check_required_files
78
- return if go_mod
122
+ workspace_results.each do |file_path, content|
123
+ original = dependency_files.find { |f| f.name == file_path }
124
+ next unless original
125
+ next if original.content == content
79
126
 
80
- raise "No go.mod!"
81
- end
127
+ updated_files << updated_file(file: original, content: content)
128
+ end
82
129
 
83
- sig { returns(T.nilable(Dependabot::DependencyFile)) }
84
- def go_mod
85
- @go_mod ||= T.let(get_original_file("go.mod"), T.nilable(Dependabot::DependencyFile))
130
+ updated_files
86
131
  end
87
132
 
88
- sig { returns(T.nilable(Dependabot::DependencyFile)) }
89
- def go_sum
90
- @go_sum ||= T.let(get_original_file("go.sum"), T.nilable(Dependabot::DependencyFile))
133
+ sig { void }
134
+ def check_workspace_not_vendored!
135
+ mod_paths = GoWorkParser.use_paths(T.must(T.must(go_work).content))
136
+
137
+ vendored_path = mod_paths.find do |mod_path|
138
+ vendor_modules_txt = if mod_path == "."
139
+ File.join(vendor_dir, "modules.txt")
140
+ else
141
+ File.join(
142
+ T.must(repo_contents_path),
143
+ T.must(directory),
144
+ mod_path,
145
+ "vendor",
146
+ "modules.txt"
147
+ )
148
+ end
149
+ File.exist?(vendor_modules_txt)
150
+ end
151
+
152
+ return unless vendored_path
153
+
154
+ raise Dependabot::DependencyFileNotResolvable,
155
+ "Go workspace module \"#{vendored_path}\" has a vendor directory. " \
156
+ "Vendored workspaces are not yet supported."
91
157
  end
92
158
 
93
159
  sig { returns(T.nilable(String)) }
@@ -0,0 +1,40 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Dependabot
7
+ module GoModules
8
+ class GoWorkParser
9
+ extend T::Sig
10
+
11
+ # Parses the `use` directives from go.work content.
12
+ # Returns an array of module paths with `./` prefix stripped.
13
+ # `"."` is included when the root module is listed.
14
+ # Result is deduped but not sorted, filtered, or re-prefixed.
15
+ sig { params(content: String).returns(T::Array[String]) }
16
+ def self.use_paths(content)
17
+ paths = T.let([], T::Array[String])
18
+
19
+ # Multi-line use block: use (\n ./path\n ...\n)
20
+ content.scan(/^use\s+\(([^)]+)\)/m).each do |block_match|
21
+ T.must(block_match[0]).each_line do |line|
22
+ path = line.split("//").first&.strip || ""
23
+ next if path.empty?
24
+
25
+ paths << path.sub(%r{^\./}, "")
26
+ end
27
+ end
28
+
29
+ # Single-line use directive: use . or use ./path
30
+ # [^\s]* (zero-or-more) so bare `use .` is captured
31
+ content.scan(/^use\s+(?!\()(\.[^\s]*)/m).each do |match|
32
+ path = T.must(match[0]).sub(%r{^\./}, "")
33
+ paths << path
34
+ end
35
+
36
+ paths.uniq
37
+ end
38
+ end
39
+ end
40
+ end
@@ -120,7 +120,18 @@ module Dependabot
120
120
 
121
121
  sig { returns(T.nilable(Dependabot::DependencyFile)) }
122
122
  def go_mod
123
- @go_mod ||= T.let(dependency_files.find { |f| f.name == "go.mod" }, T.nilable(Dependabot::DependencyFile))
123
+ @go_mod ||= T.let(
124
+ begin
125
+ req_file = dependency.requirements.first&.fetch(:file, nil)
126
+ if req_file
127
+ dependency_files.find { |f| f.name == req_file }
128
+ else
129
+ dependency_files.find { |f| f.name == "go.mod" } ||
130
+ dependency_files.find { |f| f.name.end_with?("/go.mod") }
131
+ end
132
+ end,
133
+ T.nilable(Dependabot::DependencyFile)
134
+ )
124
135
  end
125
136
 
126
137
  sig do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-go_modules
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.373.0
4
+ version: 0.374.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.373.0
18
+ version: 0.374.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.373.0
25
+ version: 0.374.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -256,6 +256,7 @@ files:
256
256
  - lib/dependabot/go_modules/file_parser.rb
257
257
  - lib/dependabot/go_modules/file_updater.rb
258
258
  - lib/dependabot/go_modules/file_updater/go_mod_updater.rb
259
+ - lib/dependabot/go_modules/go_work_parser.rb
259
260
  - lib/dependabot/go_modules/language.rb
260
261
  - lib/dependabot/go_modules/metadata_finder.rb
261
262
  - lib/dependabot/go_modules/native_helpers.rb
@@ -274,7 +275,7 @@ licenses:
274
275
  - MIT
275
276
  metadata:
276
277
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
277
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.373.0
278
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.374.0
278
279
  rdoc_options: []
279
280
  require_paths:
280
281
  - lib