dependabot-bundler 0.396.0 → 0.397.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: 7b148dc013501428e1d1805c3f8c44e833fff5dec4e42efd28048d2252fdfa4b
4
- data.tar.gz: 416201519f49e7f757fb404743fa8cd700295d16122c5ca26d1cfe1e811da553
3
+ metadata.gz: 43d670382313df98efe56ca75538daf1ef494b65bdb4dfa87e79bcb2f5ae3fdc
4
+ data.tar.gz: 46faf363b2a8e37874cc5516425abf14bf31a50c12573fac6c837d78aba6a182
5
5
  SHA512:
6
- metadata.gz: daef51089d338a57f43a6655fb955854654e37ecdd773f36066fe6840f9e4404893cd4e107c4b5fb80bf1faa2f3c67d77f410669a37b9847d90278d27b6b40d1
7
- data.tar.gz: dd14dc06263439c9bf3e37d56c9b8c142043b0ca6eaa7cb6119c0e1049226473855057513e3011e309cb94b12f7a4b0cb150289097f60ebecebe21bea9b2aeb7
6
+ metadata.gz: 24a9688ad6a277c68a763f39a298e13b9c2a8a64fea6ecc57d2414ea55555d9cfb49ec5feeddc1ad99a99ce475625ef18a4f35ee83a1bb2e26666fecb166ae58
7
+ data.tar.gz: 9b8e28ec1cc08b2d841112f29d75e3d7545be0563dd69dd1f0a67cf693cf66759e175245cf88b658229ca90878aaaa53cc13ea40c5e400ae23a2738efdde0150
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "native_spec_helper"
5
5
  require "shared_contexts"
6
+ require "json"
6
7
 
7
8
  RSpec.describe Functions::FileParser do
8
9
  include_context "when in a temporary bundler directory"
@@ -23,87 +24,87 @@ RSpec.describe Functions::FileParser do
23
24
  let(:project_name) { "gemfile" }
24
25
 
25
26
  it "parses gemfile" do
26
- parsed_gemfile = [
27
+ expected = [
27
28
  {
28
- groups: [:default],
29
- name: "business",
30
- requirement: Gem::Requirement.new("~> 1.4.0"),
31
- source: nil,
32
- type: :runtime
29
+ "groups" => ["default"],
30
+ "name" => "business",
31
+ "requirement" => "~> 1.4.0",
32
+ "source" => nil,
33
+ "type" => "runtime"
33
34
  },
34
35
  {
35
- groups: [:default],
36
- name: "statesman",
37
- requirement: Gem::Requirement.new("~> 1.2.0"),
38
- source: nil,
39
- type: :runtime
36
+ "groups" => ["default"],
37
+ "name" => "statesman",
38
+ "requirement" => "~> 1.2.0",
39
+ "source" => nil,
40
+ "type" => "runtime"
40
41
  }
41
42
  ]
42
- expect(parsed_gemfile).not_to be_nil # to get past IdenticalEqualityAssertion and NamedSubject
43
+ expect(JSON.parse(JSON.dump(parsed_gemfile))).to eq(expected)
43
44
  end
44
45
 
45
46
  context "with a git source" do
46
47
  let(:project_name) { "git_source" }
47
48
 
48
49
  it "parses gemfile" do
49
- parsed_gemfile = [
50
+ expected = [
50
51
  {
51
- groups: [:default],
52
- name: "business",
53
- requirement: Gem::Requirement.new("~> 1.6.0"),
54
- source: {
55
- branch: nil,
56
- ref: "a1b78a9",
57
- type: "git",
58
- url: "git@github.com:dependabot-fixtures/business"
52
+ "groups" => ["default"],
53
+ "name" => "business",
54
+ "requirement" => "~> 1.6.0",
55
+ "source" => {
56
+ "branch" => nil,
57
+ "ref" => "a1b78a9",
58
+ "type" => "git",
59
+ "url" => "git@github.com:dependabot-fixtures/business"
59
60
  },
60
- type: :runtime
61
+ "type" => "runtime"
61
62
  },
62
63
  {
63
- groups: [:default],
64
- name: "statesman",
65
- requirement: Gem::Requirement.new("~> 1.2.0"),
66
- source: nil,
67
- type: :runtime
64
+ "groups" => ["default"],
65
+ "name" => "statesman",
66
+ "requirement" => "~> 1.2.0",
67
+ "source" => nil,
68
+ "type" => "runtime"
68
69
  },
69
70
  {
70
- groups: [:default],
71
- name: "prius",
72
- requirement: Gem::Requirement.new(">= 0"),
73
- source: {
74
- branch: nil,
75
- ref: nil,
76
- type: "git",
77
- url: "https://github.com/dependabot-fixtures/prius"
71
+ "groups" => ["default"],
72
+ "name" => "prius",
73
+ "requirement" => ">= 0",
74
+ "source" => {
75
+ "branch" => nil,
76
+ "ref" => nil,
77
+ "type" => "git",
78
+ "url" => "https://github.com/dependabot-fixtures/prius"
78
79
  },
79
- type: :runtime
80
+ "type" => "runtime"
80
81
  },
81
82
  {
82
- groups: [:default],
83
- name: "que",
84
- requirement: Gem::Requirement.new(">= 0"),
85
- source: {
86
- branch: nil,
87
- ref: "v0.11.6",
88
- type: "git",
89
- url: "git@github.com:dependabot-fixtures/que"
83
+ "groups" => ["default"],
84
+ "name" => "que",
85
+ "requirement" => ">= 0",
86
+ "source" => {
87
+ "branch" => nil,
88
+ "ref" => "v0.11.6",
89
+ "type" => "git",
90
+ "url" => "git@github.com:dependabot-fixtures/que"
90
91
  },
91
- type: :runtime
92
+ "type" => "runtime"
92
93
  },
93
94
  {
94
- groups: [:default],
95
- name: "uk_phone_numbers",
96
- requirement: Gem::Requirement.new(">= 0"),
97
- source: {
98
- branch: nil,
99
- ref: nil,
100
- type: "git",
101
- url: "https://github.com/dependabot-fixtures/uk_phone_numbers"
95
+ "groups" => ["default"],
96
+ "name" => "uk_phone_numbers",
97
+ "requirement" => ">= 0",
98
+ "source" => {
99
+ "branch" => nil,
100
+ "ref" => nil,
101
+ "type" => "git",
102
+ "url" => "https://github.com/dependabot-fixtures/uk_phone_numbers"
102
103
  },
103
- type: :runtime
104
+ "type" => "runtime"
104
105
  }
105
106
  ]
106
- expect(parsed_gemfile).not_to be_nil # to get past IdenticalEqualityAssertion and NamedSubject
107
+ expect(JSON.parse(JSON.dump(parsed_gemfile))).to eq(expected)
107
108
  end
108
109
  end
109
110
  end
@@ -118,23 +119,23 @@ RSpec.describe Functions::FileParser do
118
119
  let(:project_name) { "gemfile_exact" }
119
120
 
120
121
  it "parses gemspec" do
121
- parsed_gemspec = [
122
+ expected = [
122
123
  {
123
- groups: nil,
124
- name: "business",
125
- requirement: Gem::Requirement.new("= 1.0.0"),
126
- source: nil,
127
- type: :runtime
124
+ "groups" => nil,
125
+ "name" => "business",
126
+ "requirement" => "= 1.0.0",
127
+ "source" => nil,
128
+ "type" => "runtime"
128
129
  },
129
130
  {
130
- groups: nil,
131
- name: "statesman",
132
- requirement: Gem::Requirement.new("= 1.0.0"),
133
- source: nil,
134
- type: :runtime
131
+ "groups" => nil,
132
+ "name" => "statesman",
133
+ "requirement" => "= 1.0.0",
134
+ "source" => nil,
135
+ "type" => "runtime"
135
136
  }
136
137
  ]
137
- expect(parsed_gemspec).not_to be_nil # to get past IdenticalEqualityAssertion and NamedSubject
138
+ expect(JSON.parse(JSON.dump(parsed_gemspec))).to eq(expected)
138
139
  end
139
140
  end
140
141
  end
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "native_spec_helper"
5
5
  require "shared_contexts"
6
+ require "json"
6
7
 
7
8
  RSpec.describe Functions::FileParser do
8
9
  include_context "when in a temporary bundler directory"
@@ -23,87 +24,87 @@ RSpec.describe Functions::FileParser do
23
24
  let(:project_name) { "gemfile" }
24
25
 
25
26
  it "parses gemfile" do
26
- parsed_gemfile = [
27
+ expected = [
27
28
  {
28
- groups: [:default],
29
- name: "business",
30
- requirement: Gem::Requirement.new("~> 1.4.0"),
31
- source: nil,
32
- type: :runtime
29
+ "groups" => ["default"],
30
+ "name" => "business",
31
+ "requirement" => "~> 1.4.0",
32
+ "source" => nil,
33
+ "type" => "runtime"
33
34
  },
34
35
  {
35
- groups: [:default],
36
- name: "statesman",
37
- requirement: Gem::Requirement.new("~> 1.2.0"),
38
- source: nil,
39
- type: :runtime
36
+ "groups" => ["default"],
37
+ "name" => "statesman",
38
+ "requirement" => "~> 1.2.0",
39
+ "source" => nil,
40
+ "type" => "runtime"
40
41
  }
41
42
  ]
42
- expect(parsed_gemfile).not_to be_nil # to get past IdenticalEqualityAssertion and NamedSubject
43
+ expect(JSON.parse(JSON.dump(parsed_gemfile))).to eq(expected)
43
44
  end
44
45
 
45
46
  context "with a git source" do
46
47
  let(:project_name) { "git_source" }
47
48
 
48
49
  it "parses gemfile" do
49
- parsed_gemfile = [
50
+ expected = [
50
51
  {
51
- groups: [:default],
52
- name: "business",
53
- requirement: Gem::Requirement.new("~> 1.6.0"),
54
- source: {
55
- branch: nil,
56
- ref: "a1b78a9",
57
- type: "git",
58
- url: "git@github.com:dependabot-fixtures/business"
52
+ "groups" => ["default"],
53
+ "name" => "business",
54
+ "requirement" => "~> 1.6.0",
55
+ "source" => {
56
+ "branch" => nil,
57
+ "ref" => "a1b78a9",
58
+ "type" => "git",
59
+ "url" => "git@github.com:dependabot-fixtures/business"
59
60
  },
60
- type: :runtime
61
+ "type" => "runtime"
61
62
  },
62
63
  {
63
- groups: [:default],
64
- name: "statesman",
65
- requirement: Gem::Requirement.new("~> 1.2.0"),
66
- source: nil,
67
- type: :runtime
64
+ "groups" => ["default"],
65
+ "name" => "statesman",
66
+ "requirement" => "~> 1.2.0",
67
+ "source" => nil,
68
+ "type" => "runtime"
68
69
  },
69
70
  {
70
- groups: [:default],
71
- name: "prius",
72
- requirement: Gem::Requirement.new(">= 0"),
73
- source: {
74
- branch: nil,
75
- ref: nil,
76
- type: "git",
77
- url: "https://github.com/dependabot-fixtures/prius"
71
+ "groups" => ["default"],
72
+ "name" => "prius",
73
+ "requirement" => ">= 0",
74
+ "source" => {
75
+ "branch" => nil,
76
+ "ref" => nil,
77
+ "type" => "git",
78
+ "url" => "https://github.com/dependabot-fixtures/prius"
78
79
  },
79
- type: :runtime
80
+ "type" => "runtime"
80
81
  },
81
82
  {
82
- groups: [:default],
83
- name: "que",
84
- requirement: Gem::Requirement.new(">= 0"),
85
- source: {
86
- branch: nil,
87
- ref: "v0.11.6",
88
- type: "git",
89
- url: "git@github.com:dependabot-fixtures/que"
83
+ "groups" => ["default"],
84
+ "name" => "que",
85
+ "requirement" => ">= 0",
86
+ "source" => {
87
+ "branch" => nil,
88
+ "ref" => "v0.11.6",
89
+ "type" => "git",
90
+ "url" => "git@github.com:dependabot-fixtures/que"
90
91
  },
91
- type: :runtime
92
+ "type" => "runtime"
92
93
  },
93
94
  {
94
- groups: [:default],
95
- name: "uk_phone_numbers",
96
- requirement: Gem::Requirement.new(">= 0"),
97
- source: {
98
- branch: nil,
99
- ref: nil,
100
- type: "git",
101
- url: "https://github.com/dependabot-fixtures/uk_phone_numbers"
95
+ "groups" => ["default"],
96
+ "name" => "uk_phone_numbers",
97
+ "requirement" => ">= 0",
98
+ "source" => {
99
+ "branch" => nil,
100
+ "ref" => nil,
101
+ "type" => "git",
102
+ "url" => "https://github.com/dependabot-fixtures/uk_phone_numbers"
102
103
  },
103
- type: :runtime
104
+ "type" => "runtime"
104
105
  }
105
106
  ]
106
- expect(parsed_gemfile).not_to be_nil # to get past IdenticalEqualityAssertion and NamedSubject
107
+ expect(JSON.parse(JSON.dump(parsed_gemfile))).to eq(expected)
107
108
  end
108
109
  end
109
110
  end
@@ -118,23 +119,23 @@ RSpec.describe Functions::FileParser do
118
119
  let(:project_name) { "gemfile_exact" }
119
120
 
120
121
  it "parses gemspec" do
121
- parsed_gemspec = [
122
+ expected = [
122
123
  {
123
- groups: nil,
124
- name: "business",
125
- requirement: Gem::Requirement.new("= 1.0.0"),
126
- source: nil,
127
- type: :runtime
124
+ "groups" => nil,
125
+ "name" => "business",
126
+ "requirement" => "= 1.0.0",
127
+ "source" => nil,
128
+ "type" => "runtime"
128
129
  },
129
130
  {
130
- groups: nil,
131
- name: "statesman",
132
- requirement: Gem::Requirement.new("= 1.0.0"),
133
- source: nil,
134
- type: :runtime
131
+ "groups" => nil,
132
+ "name" => "statesman",
133
+ "requirement" => "= 1.0.0",
134
+ "source" => nil,
135
+ "type" => "runtime"
135
136
  }
136
137
  ]
137
- expect(parsed_gemspec).not_to be_nil # to get past IdenticalEqualityAssertion and NamedSubject
138
+ expect(JSON.parse(JSON.dump(parsed_gemspec))).to eq(expected)
138
139
  end
139
140
  end
140
141
  end
@@ -4,7 +4,7 @@
4
4
  require "prism"
5
5
  require "sorbet-runtime"
6
6
 
7
- require "dependabot/file_parsers/base"
7
+ require "dependabot/bundler/file_parser"
8
8
 
9
9
  module Dependabot
10
10
  module Bundler
@@ -16,21 +16,20 @@ module Dependabot
16
16
  sig { params(gemfile: Dependabot::DependencyFile).void }
17
17
  def initialize(gemfile:)
18
18
  @gemfile = gemfile
19
- @declaration_nodes = T.let({}, T::Hash[T::Hash[String, String], T.nilable(Prism::Node)])
19
+ @declaration_nodes = T.let({}, T::Hash[String, T.nilable(Prism::Node)])
20
20
  end
21
21
 
22
- sig { params(dependency: T::Hash[String, String]).returns(T::Boolean) }
23
- def gemfile_includes_dependency?(dependency)
24
- !declaration_node(dependency).nil?
22
+ sig { params(dependency_name: String).returns(T::Boolean) }
23
+ def gemfile_includes_dependency?(dependency_name)
24
+ !declaration_node(dependency_name).nil?
25
25
  end
26
26
 
27
27
  # rubocop:disable Metrics/PerceivedComplexity
28
- sig { params(dependency: T::Hash[String, String]).returns(T.nilable(String)) }
29
- def enhanced_req_string(dependency)
30
- return unless gemfile_includes_dependency?(dependency)
28
+ sig { params(dependency_name: String, fallback_string: String).returns(T.nilable(String)) }
29
+ def enhanced_req_string(dependency_name, fallback_string)
30
+ return unless gemfile_includes_dependency?(dependency_name)
31
31
 
32
- fallback_string = dependency.fetch("requirement")
33
- call_node = declaration_node(dependency)
32
+ call_node = declaration_node(dependency_name)
34
33
  return fallback_string unless call_node.is_a?(Prism::CallNode)
35
34
 
36
35
  req_nodes = call_node.arguments&.arguments&.[](1..-1) || []
@@ -63,32 +62,32 @@ module Dependabot
63
62
  )
64
63
  end
65
64
 
66
- sig { params(dependency: T::Hash[String, String]).returns(T.nilable(Prism::Node)) }
67
- def declaration_node(dependency)
68
- return @declaration_nodes[dependency] if @declaration_nodes.key?(dependency)
65
+ sig { params(dependency_name: String).returns(T.nilable(Prism::Node)) }
66
+ def declaration_node(dependency_name)
67
+ return @declaration_nodes[dependency_name] if @declaration_nodes.key?(dependency_name)
69
68
  return unless parsed_gemfile
70
69
 
71
- @declaration_nodes[dependency] = nil
70
+ @declaration_nodes[dependency_name] = nil
72
71
  T.must(parsed_gemfile).child_nodes.any? do |node|
73
- @declaration_nodes[dependency] = deep_search_for_gem(node, dependency)
72
+ @declaration_nodes[dependency_name] = deep_search_for_gem(node, dependency_name)
74
73
  end
75
- @declaration_nodes[dependency]
74
+ @declaration_nodes[dependency_name]
76
75
  end
77
76
 
78
77
  sig do
79
78
  params(
80
79
  node: T.nilable(Prism::Node),
81
- dependency: T::Hash[String, String]
80
+ dependency_name: String
82
81
  )
83
82
  .returns(T.nilable(Prism::Node))
84
83
  end
85
- def deep_search_for_gem(node, dependency)
84
+ def deep_search_for_gem(node, dependency_name)
86
85
  return unless node.is_a?(Prism::Node)
87
- return node if declares_targeted_gem?(node, dependency)
86
+ return node if declares_targeted_gem?(node, dependency_name)
88
87
 
89
88
  declaration_node = T.let(nil, T.nilable(Prism::Node))
90
89
  node.child_nodes.find do |child_node|
91
- declaration_node = deep_search_for_gem(child_node, dependency)
90
+ declaration_node = deep_search_for_gem(child_node, dependency_name)
92
91
  end
93
92
  declaration_node
94
93
  end
@@ -96,18 +95,18 @@ module Dependabot
96
95
  sig do
97
96
  params(
98
97
  node: T.nilable(Prism::Node),
99
- dependency: T::Hash[String, String]
98
+ dependency_name: String
100
99
  )
101
100
  .returns(T::Boolean)
102
101
  end
103
- def declares_targeted_gem?(node, dependency)
102
+ def declares_targeted_gem?(node, dependency_name)
104
103
  return false unless node.is_a?(Prism::CallNode)
105
104
  return false unless node.name == :gem
106
105
 
107
106
  gem_name_node = node.arguments&.arguments&.first
108
107
  return false unless gem_name_node.is_a?(Prism::StringNode)
109
108
 
110
- gem_name_node.unescaped == dependency.fetch("name")
109
+ gem_name_node.unescaped == dependency_name
111
110
  end
112
111
  end
113
112
  end
@@ -4,7 +4,7 @@
4
4
  require "prism"
5
5
  require "sorbet-runtime"
6
6
 
7
- require "dependabot/file_parsers/base"
7
+ require "dependabot/bundler/file_parser"
8
8
 
9
9
  module Dependabot
10
10
  module Bundler
@@ -16,12 +16,12 @@ module Dependabot
16
16
  sig { params(gemspec: Dependabot::DependencyFile).void }
17
17
  def initialize(gemspec:)
18
18
  @gemspec = gemspec
19
- @declaration_nodes = T.let({}, T::Hash[T::Hash[String, String], T.nilable(Prism::Node)])
19
+ @declaration_nodes = T.let({}, T::Hash[String, T.nilable(Prism::Node)])
20
20
  end
21
21
 
22
- sig { params(dependency: T::Hash[String, String]).returns(T::Boolean) }
23
- def gemspec_includes_dependency?(dependency)
24
- !declaration_node(dependency).nil?
22
+ sig { params(dependency_name: String).returns(T::Boolean) }
23
+ def gemspec_includes_dependency?(dependency_name)
24
+ !declaration_node(dependency_name).nil?
25
25
  end
26
26
 
27
27
  private
@@ -34,32 +34,32 @@ module Dependabot
34
34
  @parsed_gemspec ||= T.let(Prism.parse(gemspec.content).value, T.nilable(Prism::Node))
35
35
  end
36
36
 
37
- sig { params(dependency: T::Hash[String, String]).returns(T.nilable(Prism::Node)) }
38
- def declaration_node(dependency)
39
- return @declaration_nodes[dependency] if @declaration_nodes.key?(dependency)
37
+ sig { params(dependency_name: String).returns(T.nilable(Prism::Node)) }
38
+ def declaration_node(dependency_name)
39
+ return @declaration_nodes[dependency_name] if @declaration_nodes.key?(dependency_name)
40
40
  return unless parsed_gemspec
41
41
 
42
- @declaration_nodes[dependency] = nil
42
+ @declaration_nodes[dependency_name] = nil
43
43
  T.must(parsed_gemspec).child_nodes.any? do |node|
44
- @declaration_nodes[dependency] = deep_search_for_gem(node, dependency)
44
+ @declaration_nodes[dependency_name] = deep_search_for_gem(node, dependency_name)
45
45
  end
46
- @declaration_nodes[dependency]
46
+ @declaration_nodes[dependency_name]
47
47
  end
48
48
 
49
- sig { params(node: T.nilable(Prism::Node), dependency: T::Hash[String, String]).returns(T.nilable(Prism::Node)) }
50
- def deep_search_for_gem(node, dependency)
49
+ sig { params(node: T.nilable(Prism::Node), dependency_name: String).returns(T.nilable(Prism::Node)) }
50
+ def deep_search_for_gem(node, dependency_name)
51
51
  return unless node.is_a?(Prism::Node)
52
- return T.cast(node, Prism::CallNode) if declares_targeted_gem?(node, dependency)
52
+ return T.cast(node, Prism::CallNode) if declares_targeted_gem?(node, dependency_name)
53
53
 
54
54
  declaration_node = T.let(nil, T.nilable(Prism::Node))
55
55
  node.child_nodes.find do |child_node|
56
- declaration_node = deep_search_for_gem(child_node, dependency)
56
+ declaration_node = deep_search_for_gem(child_node, dependency_name)
57
57
  end
58
58
  declaration_node
59
59
  end
60
60
 
61
- sig { params(node: T.nilable(Prism::Node), dependency: T::Hash[String, String]).returns(T::Boolean) }
62
- def declares_targeted_gem?(node, dependency)
61
+ sig { params(node: T.nilable(Prism::Node), dependency_name: String).returns(T::Boolean) }
62
+ def declares_targeted_gem?(node, dependency_name)
63
63
  return false unless node.is_a?(Prism::CallNode)
64
64
 
65
65
  second_child = node.name
@@ -69,7 +69,7 @@ module Dependabot
69
69
  gem_name_node = node.arguments&.child_nodes&.first
70
70
  return false unless gem_name_node.is_a?(Prism::StringNode)
71
71
 
72
- gem_name_node.unescaped == dependency.fetch("name")
72
+ gem_name_node.unescaped == dependency_name
73
73
  end
74
74
  end
75
75
  end
@@ -0,0 +1,116 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/errors"
6
+ require "dependabot/bundler/file_parser"
7
+
8
+ module Dependabot
9
+ module Bundler
10
+ class FileParser < Dependabot::FileParsers::Base
11
+ module HelperDependencies
12
+ extend T::Sig
13
+
14
+ Source = T.type_alias { T::Hash[Symbol, T.nilable(String)] }
15
+ ObjectHash = T.type_alias { T::Hash[String, Object] }
16
+
17
+ class GemfileDependency < T::ImmutableStruct
18
+ const :name, String
19
+ const :requirement, String
20
+ const :groups, T::Array[String]
21
+ const :source, T.nilable(Source)
22
+ end
23
+
24
+ class GemspecDependency < T::ImmutableStruct
25
+ const :name, String
26
+ const :requirement, String
27
+ const :type, String
28
+ const :source, T.nilable(Source)
29
+ end
30
+
31
+ # Both helper versions serialize these fields in Functions::FileParser#serialize_bundler_dependency.
32
+ sig { params(result: Object, file: Dependabot::DependencyFile).returns(T::Array[GemfileDependency]) }
33
+ def self.from_gemfile_result(result, file:)
34
+ context = "parsed_gemfile result for #{file.path}"
35
+ array(result, context).each_with_index.map do |value, index|
36
+ entry_context = "#{context}[#{index}]"
37
+ entry = object(value, entry_context)
38
+
39
+ GemfileDependency.new(
40
+ name: string(entry["name"], "#{entry_context}.name"),
41
+ requirement: string(entry["requirement"], "#{entry_context}.requirement"),
42
+ groups: strings(entry["groups"], "#{entry_context}.groups"),
43
+ source: source(entry, entry_context)
44
+ )
45
+ end
46
+ end
47
+
48
+ sig { params(result: Object, file: Dependabot::DependencyFile).returns(T::Array[GemspecDependency]) }
49
+ def self.from_gemspec_result(result, file:)
50
+ context = "parsed_gemspec result for #{file.path}"
51
+ array(result, context).each_with_index.map do |value, index|
52
+ entry_context = "#{context}[#{index}]"
53
+ entry = object(value, entry_context)
54
+
55
+ GemspecDependency.new(
56
+ name: string(entry["name"], "#{entry_context}.name"),
57
+ requirement: string(entry["requirement"], "#{entry_context}.requirement"),
58
+ type: string(entry["type"], "#{entry_context}.type"),
59
+ source: source(entry, entry_context)
60
+ )
61
+ end
62
+ end
63
+
64
+ sig { params(entry: ObjectHash, context: String).returns(T.nilable(Source)) }
65
+ def self.source(entry, context)
66
+ raise DependencyFileNotEvaluatable, "#{context}.source must be present" unless entry.key?("source")
67
+
68
+ value = entry["source"]
69
+ return if value.nil?
70
+
71
+ fields = object(value, "#{context}.source")
72
+ string(fields["type"], "#{context}.source.type")
73
+ fields.to_h do |key, field|
74
+ [key.to_sym, field.nil? ? nil : string(field, "#{context}.source.#{key}")]
75
+ end
76
+ end
77
+ private_class_method :source
78
+
79
+ sig { params(value: Object, context: String).returns(ObjectHash) }
80
+ def self.object(value, context)
81
+ raise DependencyFileNotEvaluatable, "#{context} must be an object" unless value.is_a?(Hash)
82
+
83
+ value.to_h do |raw_key, raw_value|
84
+ key = T.cast(raw_key, Object)
85
+ raise DependencyFileNotEvaluatable, "#{context} keys must be strings" unless key.is_a?(String)
86
+
87
+ [key, T.cast(raw_value, Object)]
88
+ end
89
+ end
90
+ private_class_method :object
91
+
92
+ sig { params(value: Object, context: String).returns(T::Array[Object]) }
93
+ def self.array(value, context)
94
+ raise DependencyFileNotEvaluatable, "#{context} must be an array" unless value.is_a?(Array)
95
+
96
+ value.map { |entry| T.cast(entry, Object) }
97
+ end
98
+ private_class_method :array
99
+
100
+ sig { params(value: Object, context: String).returns(String) }
101
+ def self.string(value, context)
102
+ return value if value.is_a?(String)
103
+
104
+ raise DependencyFileNotEvaluatable, "#{context} must be a string"
105
+ end
106
+ private_class_method :string
107
+
108
+ sig { params(value: Object, context: String).returns(T::Array[String]) }
109
+ def self.strings(value, context)
110
+ array(value, context).each_with_index.map { |entry, index| string(entry, "#{context}[#{index}]") }
111
+ end
112
+ private_class_method :strings
113
+ end
114
+ end
115
+ end
116
+ end
@@ -22,6 +22,7 @@ module Dependabot
22
22
  extend T::Sig
23
23
 
24
24
  require "dependabot/file_parsers/base/dependency_set"
25
+ require "dependabot/bundler/file_parser/helper_dependencies"
25
26
  require "dependabot/bundler/file_parser/file_preparer"
26
27
  require "dependabot/bundler/file_parser/gemfile_declaration_finder"
27
28
  require "dependabot/bundler/file_parser/gemspec_declaration_finder"
@@ -121,15 +122,15 @@ module Dependabot
121
122
  gemfile_declaration_finder = GemfileDeclarationFinder.new(gemfile: file)
122
123
 
123
124
  parsed_gemfile.each do |dep|
124
- next unless gemfile_declaration_finder.gemfile_includes_dependency?(dep)
125
+ next unless gemfile_declaration_finder.gemfile_includes_dependency?(dep.name)
125
126
 
126
127
  dependencies << Dependency.new(
127
- name: dep.fetch("name"),
128
- version: dependency_version(dep.fetch("name"))&.to_s,
128
+ name: dep.name,
129
+ version: dependency_version(dep.name)&.to_s,
129
130
  requirements: [{
130
- requirement: gemfile_declaration_finder.enhanced_req_string(dep),
131
- groups: dep.fetch("groups").map(&:to_sym),
132
- source: dep.fetch("source")&.transform_keys(&:to_sym),
131
+ requirement: gemfile_declaration_finder.enhanced_req_string(dep.name, dep.requirement),
132
+ groups: dep.groups.map(&:to_sym),
133
+ source: dep.source&.dup,
133
134
  file: file.name
134
135
  }],
135
136
  package_manager: "bundler"
@@ -141,7 +142,7 @@ module Dependabot
141
142
  end
142
143
 
143
144
  sig { returns(DependencySet) }
144
- def gemspec_dependencies # rubocop:disable Metrics/PerceivedComplexity
145
+ def gemspec_dependencies
145
146
  @gemspec_dependencies = T.let(@gemspec_dependencies, T.nilable(DependencySet))
146
147
  return @gemspec_dependencies if @gemspec_dependencies
147
148
 
@@ -154,19 +155,19 @@ module Dependabot
154
155
  gemspec_declaration_finder = GemspecDeclarationFinder.new(gemspec: gemspec)
155
156
 
156
157
  parsed_gemspec(gemspec).each do |dependency|
157
- next unless gemspec_declaration_finder.gemspec_includes_dependency?(dependency)
158
+ next unless gemspec_declaration_finder.gemspec_includes_dependency?(dependency.name)
158
159
 
159
160
  queue << Dependency.new(
160
- name: dependency.fetch("name"),
161
- version: dependency_version(dependency.fetch("name"))&.to_s,
161
+ name: dependency.name,
162
+ version: dependency_version(dependency.name)&.to_s,
162
163
  requirements: [{
163
- requirement: dependency.fetch("requirement").to_s,
164
- groups: if dependency.fetch("type") == "runtime"
164
+ requirement: dependency.requirement,
165
+ groups: if dependency.type == "runtime"
165
166
  ["runtime"]
166
167
  else
167
168
  ["development"]
168
169
  end,
169
- source: dependency.fetch("source")&.transform_keys(&:to_sym),
170
+ source: dependency.source&.dup,
170
171
  file: gemspec.name
171
172
  }],
172
173
  package_manager: "bundler"
@@ -206,7 +207,7 @@ module Dependabot
206
207
  dependencies
207
208
  end
208
209
 
209
- sig { returns(T::Array[T::Hash[String, T.untyped]]) }
210
+ sig { returns(T::Array[HelperDependencies::GemfileDependency]) }
210
211
  def parsed_gemfile
211
212
  @parsed_gemfile ||= T.let(
212
213
  SharedHelpers.in_a_temporary_repo_directory(
@@ -215,7 +216,7 @@ module Dependabot
215
216
  ) do
216
217
  write_temporary_dependency_files
217
218
 
218
- NativeHelpers.run_bundler_subprocess(
219
+ result = NativeHelpers.run_bundler_subprocess(
219
220
  bundler_version: bundler_version,
220
221
  function: "parsed_gemfile",
221
222
  options: options,
@@ -225,8 +226,9 @@ module Dependabot
225
226
  dir: Dir.pwd
226
227
  }
227
228
  )
229
+ HelperDependencies.from_gemfile_result(result, file: T.must(gemfile))
228
230
  end,
229
- T.nilable(T::Array[T::Hash[String, T.untyped]])
231
+ T.nilable(T::Array[HelperDependencies::GemfileDependency])
230
232
  )
231
233
  rescue SharedHelpers::HelperSubprocessFailed => e
232
234
  handle_eval_error(e) if e.error_class == "JSON::ParserError"
@@ -242,9 +244,9 @@ module Dependabot
242
244
  raise Dependabot::DependencyFileNotEvaluatable, msg
243
245
  end
244
246
 
245
- sig { params(file: Dependabot::DependencyFile).returns(T::Array[T::Hash[String, T.untyped]]) }
247
+ sig { params(file: Dependabot::DependencyFile).returns(T::Array[HelperDependencies::GemspecDependency]) }
246
248
  def parsed_gemspec(file)
247
- NativeHelpers.run_bundler_subprocess(
249
+ result = NativeHelpers.run_bundler_subprocess(
248
250
  bundler_version: bundler_version,
249
251
  function: "parsed_gemspec",
250
252
  options: options,
@@ -254,6 +256,7 @@ module Dependabot
254
256
  dir: Dir.pwd
255
257
  }
256
258
  )
259
+ HelperDependencies.from_gemspec_result(result, file: file)
257
260
  rescue SharedHelpers::HelperSubprocessFailed => e
258
261
  msg = e.error_class + " with message: " + e.message
259
262
  raise Dependabot::DependencyFileNotEvaluatable, msg
@@ -21,6 +21,7 @@ module Dependabot
21
21
  include Dependabot::Bundler::UpdateChecker::SharedBundlerHelpers
22
22
 
23
23
  RELEASES_URL = "%s/api/v1/versions/%s.json"
24
+ COMPACT_INDEX_URL = "%s/info/%s"
24
25
  GEM_URL = "%s/gems/%s.gem"
25
26
  PACKAGE_TYPE = "gem"
26
27
  PACKAGE_LANGUAGE = "ruby"
@@ -142,7 +143,7 @@ module Dependabot
142
143
  error_msg = "Failed to fetch versions for '#{dependency.name}' from '#{registry_url}'. " \
143
144
  "Status: #{response.status}"
144
145
  log_error(error_msg)
145
- return package_details([])
146
+ return fetch_compact_index_response(registry_url)
146
147
  end
147
148
 
148
149
  return handle_empty_response(registry_url) if response.body.nil? || response.body.strip.empty?
@@ -150,6 +151,52 @@ module Dependabot
150
151
  parse_rubygems_response(response, registry_url)
151
152
  end
152
153
 
154
+ sig { params(registry_url: String).returns(Dependabot::Package::PackageDetails) }
155
+ def fetch_compact_index_response(registry_url)
156
+ response = Dependabot::RegistryClient.get(
157
+ url: format(COMPACT_INDEX_URL, registry_url, dependency.name),
158
+ headers: { "Accept" => "text/plain" }
159
+ )
160
+ return package_details([]) unless response.status == 200
161
+
162
+ lines = response.body.to_s.lines(chomp: true).drop_while { |line| line != "---" }.drop(1)
163
+ releases = lines.filter_map { |line| compact_index_release(line, registry_url) }
164
+ package_details(releases)
165
+ end
166
+
167
+ sig do
168
+ params(line: String, registry_url: String).returns(T.nilable(Dependabot::Package::PackageRelease))
169
+ end
170
+ def compact_index_release(line, registry_url)
171
+ version_and_platform = line.split(" ", 2).first
172
+ return nil unless version_and_platform && line.include?("|")
173
+
174
+ version = T.must(version_and_platform.split("-", 2).first)
175
+ return nil unless Dependabot::Bundler::Version.correct?(version)
176
+
177
+ metadata = line.partition("|").last.split(",").to_h do |field|
178
+ key, _, value = field.partition(":")
179
+ [key, value]
180
+ end
181
+
182
+ package_release(
183
+ version: version,
184
+ released_at: compact_index_release_date(metadata["created_at"]),
185
+ downloads: 0,
186
+ url: format(GEM_URL, registry_url, "#{dependency.name}-#{version_and_platform}"),
187
+ ruby_version: metadata["ruby"]&.tr("&", ",")
188
+ )
189
+ rescue Gem::Requirement::BadRequirementError
190
+ nil
191
+ end
192
+
193
+ sig { params(created_at: T.nilable(String)).returns(T.nilable(Time)) }
194
+ def compact_index_release_date(created_at)
195
+ Time.iso8601(created_at) if created_at
196
+ rescue ArgumentError
197
+ nil
198
+ end
199
+
153
200
  sig do
154
201
  params(response: Excon::Response, registry_url: String)
155
202
  .returns(Dependabot::Package::PackageDetails)
@@ -269,7 +316,7 @@ module Dependabot
269
316
  sig do
270
317
  params(
271
318
  version: String,
272
- released_at: Time,
319
+ released_at: T.nilable(Time),
273
320
  downloads: Integer,
274
321
  url: String,
275
322
  ruby_version: T.nilable(String),
@@ -216,10 +216,10 @@ module Dependabot
216
216
  git_commit_checker.git_dependency?
217
217
  end
218
218
 
219
- sig { params(version: Dependabot::Bundler::Version).returns(T.untyped) }
219
+ sig { params(version: Dependabot::Bundler::Version).returns(T::Boolean) }
220
220
  def resolvable?(version)
221
- @resolvable ||= T.let({}, T.nilable(T::Hash[T.untyped, T.untyped]))
222
- return @resolvable[version] if @resolvable.key?(version)
221
+ @resolvable ||= T.let({}, T.nilable(T::Hash[Dependabot::Bundler::Version, T::Boolean]))
222
+ return @resolvable.fetch(version) if @resolvable.key?(version)
223
223
 
224
224
  @resolvable[version] =
225
225
  begin
@@ -239,10 +239,10 @@ module Dependabot
239
239
  end
240
240
  end
241
241
 
242
- sig { params(tag: T.nilable(String)).returns(T.untyped) }
242
+ sig { params(tag: T.nilable(String)).returns(T::Boolean) }
243
243
  def git_tag_resolvable?(tag)
244
- @git_tag_resolvable ||= T.let({}, T.nilable(T::Hash[T.untyped, T.untyped]))
245
- return @git_tag_resolvable[tag] if @git_tag_resolvable.key?(tag)
244
+ @git_tag_resolvable ||= T.let({}, T.nilable(T::Hash[T.nilable(String), T::Boolean]))
245
+ return @git_tag_resolvable.fetch(tag) if @git_tag_resolvable.key?(tag)
246
246
 
247
247
  @git_tag_resolvable[tag] =
248
248
  begin
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.396.0
4
+ version: 0.397.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.396.0
18
+ version: 0.397.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.396.0
25
+ version: 0.397.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: parallel
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -327,6 +327,7 @@ files:
327
327
  - lib/dependabot/bundler/file_parser/file_preparer.rb
328
328
  - lib/dependabot/bundler/file_parser/gemfile_declaration_finder.rb
329
329
  - lib/dependabot/bundler/file_parser/gemspec_declaration_finder.rb
330
+ - lib/dependabot/bundler/file_parser/helper_dependencies.rb
330
331
  - lib/dependabot/bundler/file_updater.rb
331
332
  - lib/dependabot/bundler/file_updater/gemfile_updater.rb
332
333
  - lib/dependabot/bundler/file_updater/gemspec_dependency_name_finder.rb
@@ -360,7 +361,7 @@ licenses:
360
361
  - MIT
361
362
  metadata:
362
363
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
363
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.396.0
364
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.397.0
364
365
  rdoc_options: []
365
366
  require_paths:
366
367
  - lib