dependabot-bundler 0.95.6 → 0.95.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot/bundler/file_fetcher/child_gemfile_finder.rb +68 -0
  3. data/lib/dependabot/bundler/file_fetcher/gemspec_finder.rb +96 -0
  4. data/lib/dependabot/bundler/file_fetcher/path_gemspec_finder.rb +112 -0
  5. data/lib/dependabot/bundler/file_fetcher/require_relative_finder.rb +65 -0
  6. data/lib/dependabot/bundler/file_fetcher.rb +216 -0
  7. data/lib/dependabot/bundler/file_parser/file_preparer.rb +84 -0
  8. data/lib/dependabot/bundler/file_parser/gemfile_checker.rb +46 -0
  9. data/lib/dependabot/bundler/file_parser.rb +297 -0
  10. data/lib/dependabot/bundler/file_updater/gemfile_updater.rb +114 -0
  11. data/lib/dependabot/bundler/file_updater/gemspec_dependency_name_finder.rb +50 -0
  12. data/lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb +298 -0
  13. data/lib/dependabot/bundler/file_updater/gemspec_updater.rb +62 -0
  14. data/lib/dependabot/bundler/file_updater/git_pin_replacer.rb +78 -0
  15. data/lib/dependabot/bundler/file_updater/git_source_remover.rb +100 -0
  16. data/lib/dependabot/bundler/file_updater/lockfile_updater.rb +387 -0
  17. data/lib/dependabot/bundler/file_updater/requirement_replacer.rb +221 -0
  18. data/lib/dependabot/bundler/file_updater.rb +125 -0
  19. data/lib/dependabot/bundler/metadata_finder.rb +204 -0
  20. data/lib/dependabot/bundler/requirement.rb +29 -0
  21. data/lib/dependabot/bundler/update_checker/file_preparer.rb +279 -0
  22. data/lib/dependabot/bundler/update_checker/force_updater.rb +259 -0
  23. data/lib/dependabot/bundler/update_checker/latest_version_finder.rb +165 -0
  24. data/lib/dependabot/bundler/update_checker/requirements_updater.rb +281 -0
  25. data/lib/dependabot/bundler/update_checker/ruby_requirement_setter.rb +113 -0
  26. data/lib/dependabot/bundler/update_checker/shared_bundler_helpers.rb +244 -0
  27. data/lib/dependabot/bundler/update_checker/version_resolver.rb +272 -0
  28. data/lib/dependabot/bundler/update_checker.rb +334 -0
  29. data/lib/dependabot/bundler/version.rb +13 -0
  30. data/lib/dependabot/bundler.rb +27 -0
  31. data/lib/dependabot/monkey_patches/bundler/definition_bundler_version_patch.rb +15 -0
  32. data/lib/dependabot/monkey_patches/bundler/definition_ruby_version_patch.rb +14 -0
  33. data/lib/dependabot/monkey_patches/bundler/git_source_patch.rb +27 -0
  34. metadata +37 -5
@@ -0,0 +1,298 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parser/current"
4
+ require "dependabot/bundler/file_updater"
5
+
6
+ module Dependabot
7
+ module Bundler
8
+ class FileUpdater
9
+ class GemspecSanitizer
10
+ UNNECESSARY_ASSIGNMENTS = %i(
11
+ bindir=
12
+ cert_chain=
13
+ email=
14
+ executables=
15
+ extra_rdoc_files=
16
+ homepage=
17
+ license=
18
+ licenses=
19
+ metadata=
20
+ post_install_message=
21
+ rdoc_options=
22
+ ).freeze
23
+
24
+ attr_reader :replacement_version
25
+
26
+ def initialize(replacement_version:)
27
+ @replacement_version = replacement_version
28
+ end
29
+
30
+ def rewrite(content)
31
+ buffer = Parser::Source::Buffer.new("(gemspec_content)")
32
+ buffer.source = content
33
+ ast = Parser::CurrentRuby.new.parse(buffer)
34
+
35
+ Rewriter.
36
+ new(replacement_version: replacement_version).
37
+ rewrite(buffer, ast)
38
+ end
39
+
40
+ class Rewriter < Parser::TreeRewriter
41
+ def initialize(replacement_version:)
42
+ @replacement_version = replacement_version
43
+ end
44
+
45
+ def on_send(node)
46
+ # Wrap any `require` or `require_relative` calls in a rescue
47
+ # block, as we might not have the required files
48
+ wrap_require(node) if requires_file?(node)
49
+
50
+ # Remove any assignments to a VERSION constant (or similar), as
51
+ # that constant probably comes from a required file
52
+ replace_version_assignments(node)
53
+
54
+ # Replace the `s.files= ...` assignment with a blank array, as
55
+ # occassionally a File.open(..).readlines pattern is used
56
+ replace_file_assignments(node)
57
+
58
+ # Replace the `s.require_path= ...` assignment, as
59
+ # occassionally a Dir['lib'] pattern is used
60
+ replace_require_paths_assignments(node)
61
+
62
+ # Replace any `File.read(...)` calls with a dummy string
63
+ replace_file_reads(node)
64
+
65
+ # Remove the arguments from any `Find.find(...)` calls
66
+ remove_find_dot_find_args(node)
67
+
68
+ remove_unnecessary_assignments(node)
69
+ end
70
+
71
+ private
72
+
73
+ attr_reader :replacement_version
74
+
75
+ def requires_file?(node)
76
+ %i(require require_relative).include?(node.children[1])
77
+ end
78
+
79
+ def wrap_require(node)
80
+ replace(
81
+ node.loc.expression,
82
+ "begin\n"\
83
+ "#{node.loc.expression.source_line}\n"\
84
+ "rescue LoadError\n"\
85
+ "end"
86
+ )
87
+ end
88
+
89
+ def replace_version_assignments(node)
90
+ return unless node.is_a?(Parser::AST::Node)
91
+
92
+ if node_assigns_to_version_constant?(node)
93
+ return replace_constant(node)
94
+ end
95
+
96
+ node.children.each { |child| replace_version_assignments(child) }
97
+ end
98
+
99
+ def replace_file_assignments(node)
100
+ return unless node.is_a?(Parser::AST::Node)
101
+
102
+ if node_assigns_files_to_var?(node)
103
+ return replace_file_assignment(node)
104
+ end
105
+
106
+ node.children.each { |child| replace_file_assignments(child) }
107
+ end
108
+
109
+ def replace_require_paths_assignments(node)
110
+ return unless node.is_a?(Parser::AST::Node)
111
+
112
+ if node_assigns_require_paths?(node)
113
+ return replace_require_paths_assignment(node)
114
+ end
115
+
116
+ node.children.each do |child|
117
+ replace_require_paths_assignments(child)
118
+ end
119
+ end
120
+
121
+ def node_assigns_to_version_constant?(node)
122
+ return false unless node.is_a?(Parser::AST::Node)
123
+ return false unless node.children.first.is_a?(Parser::AST::Node)
124
+ return false unless node.children.first&.type == :lvar
125
+
126
+ return true if node.children[1] == :version=
127
+ return true if node_is_version_constant?(node.children.last)
128
+ return true if node_calls_version_constant?(node.children.last)
129
+
130
+ node_interpolates_version_constant?(node.children.last)
131
+ end
132
+
133
+ def node_assigns_files_to_var?(node)
134
+ return false unless node.is_a?(Parser::AST::Node)
135
+ return false unless node.children.first.is_a?(Parser::AST::Node)
136
+ return false unless node.children.first&.type == :lvar
137
+ return false unless node.children[1] == :files=
138
+
139
+ node.children[2]&.type == :send
140
+ end
141
+
142
+ def node_assigns_require_paths?(node)
143
+ return false unless node.is_a?(Parser::AST::Node)
144
+ return false unless node.children.first.is_a?(Parser::AST::Node)
145
+ return false unless node.children.first&.type == :lvar
146
+
147
+ node.children[1] == :require_paths=
148
+ end
149
+
150
+ def replace_file_reads(node)
151
+ return unless node.is_a?(Parser::AST::Node)
152
+ return if node.children[1] == :version=
153
+ return replace_file_read(node) if node_reads_a_file?(node)
154
+ return replace_file_readlines(node) if node_uses_readlines?(node)
155
+
156
+ node.children.each { |child| replace_file_reads(child) }
157
+ end
158
+
159
+ def node_reads_a_file?(node)
160
+ return false unless node.is_a?(Parser::AST::Node)
161
+ return false unless node.children.first.is_a?(Parser::AST::Node)
162
+ return false unless node.children.first&.type == :const
163
+ return false unless node.children.first.children.last == :File
164
+
165
+ node.children[1] == :read
166
+ end
167
+
168
+ def node_uses_readlines?(node)
169
+ return false unless node.is_a?(Parser::AST::Node)
170
+ return false unless node.children.first.is_a?(Parser::AST::Node)
171
+ return false unless node.children.first&.type == :const
172
+ return false unless node.children.first.children.last == :File
173
+
174
+ node.children[1] == :readlines
175
+ end
176
+
177
+ def remove_find_dot_find_args(node)
178
+ return unless node.is_a?(Parser::AST::Node)
179
+ return if node.children[1] == :version=
180
+ return remove_find_args(node) if node_calls_find_dot_find?(node)
181
+
182
+ node.children.each { |child| remove_find_dot_find_args(child) }
183
+ end
184
+
185
+ def node_calls_find_dot_find?(node)
186
+ return false unless node.is_a?(Parser::AST::Node)
187
+ return false unless node.children.first.is_a?(Parser::AST::Node)
188
+ return false unless node.children.first&.type == :const
189
+ return false unless node.children.first.children.last == :Find
190
+
191
+ node.children[1] == :find
192
+ end
193
+
194
+ def remove_unnecessary_assignments(node)
195
+ return unless node.is_a?(Parser::AST::Node)
196
+
197
+ if unnecessary_assignment?(node) &&
198
+ node.children.last&.location&.respond_to?(:heredoc_end)
199
+ range_to_remove = node.loc.expression.join(
200
+ node.children.last.location.heredoc_end
201
+ )
202
+ return replace(range_to_remove, '"sanitized"')
203
+ elsif unnecessary_assignment?(node)
204
+ return replace(node.loc.expression, '"sanitized"')
205
+ end
206
+
207
+ node.children.each do |child|
208
+ remove_unnecessary_assignments(child)
209
+ end
210
+ end
211
+
212
+ def unnecessary_assignment?(node)
213
+ return false unless node.is_a?(Parser::AST::Node)
214
+ return false unless node.children.first.is_a?(Parser::AST::Node)
215
+
216
+ return true if node.children.first.type == :lvar &&
217
+ UNNECESSARY_ASSIGNMENTS.include?(node.children[1])
218
+
219
+ node.children[1] == :[]= && node.children.first.children.last
220
+ end
221
+
222
+ def node_is_version_constant?(node)
223
+ return false unless node.is_a?(Parser::AST::Node)
224
+ return false unless node.type == :const
225
+
226
+ node.children.last.to_s.match?(/version/i)
227
+ end
228
+
229
+ def node_calls_version_constant?(node)
230
+ return false unless node.is_a?(Parser::AST::Node)
231
+ return false unless node.type == :send
232
+
233
+ node.children.any? { |n| node_is_version_constant?(n) }
234
+ end
235
+
236
+ def node_interpolates_version_constant?(node)
237
+ return false unless node.is_a?(Parser::AST::Node)
238
+ return false unless node.type == :dstr
239
+
240
+ node.children.
241
+ select { |n| n.type == :begin }.
242
+ flat_map(&:children).
243
+ any? { |n| node_is_version_constant?(n) }
244
+ end
245
+
246
+ def replace_constant(node)
247
+ case node.children.last&.type
248
+ when :str, :int then nil # no-op
249
+ when :const, :send, :lvar
250
+ replace(
251
+ node.children.last.loc.expression,
252
+ %("#{replacement_version}")
253
+ )
254
+ when :dstr
255
+ node.children.last.children.
256
+ select { |n| n.type == :begin }.
257
+ flat_map(&:children).
258
+ select { |n| node_is_version_constant?(n) }.
259
+ each do |n|
260
+ replace(
261
+ n.loc.expression,
262
+ %("#{replacement_version}")
263
+ )
264
+ end
265
+ else
266
+ raise "Unexpected node type #{node.children.last&.type}"
267
+ end
268
+ end
269
+
270
+ def replace_file_assignment(node)
271
+ replace(node.children.last.loc.expression, "[]")
272
+ end
273
+
274
+ def replace_require_paths_assignment(node)
275
+ replace(node.children.last.loc.expression, "['lib']")
276
+ end
277
+
278
+ def replace_file_read(node)
279
+ replace(node.loc.expression, '"text"')
280
+ end
281
+
282
+ def replace_file_readlines(node)
283
+ replace(node.loc.expression, '["text"]')
284
+ end
285
+
286
+ def remove_find_args(node)
287
+ last_arg = node.children.last
288
+
289
+ range_to_remove =
290
+ last_arg.loc.expression.join(node.children[2].loc.begin.begin)
291
+
292
+ remove(range_to_remove)
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/bundler/file_updater"
4
+
5
+ module Dependabot
6
+ module Bundler
7
+ class FileUpdater
8
+ class GemspecUpdater
9
+ require_relative "requirement_replacer"
10
+
11
+ def initialize(dependencies:, gemspec:)
12
+ @dependencies = dependencies
13
+ @gemspec = gemspec
14
+ end
15
+
16
+ def updated_gemspec_content
17
+ content = gemspec.content
18
+
19
+ dependencies.each do |dependency|
20
+ content = replace_gemspec_version_requirement(
21
+ gemspec, dependency, content
22
+ )
23
+ end
24
+
25
+ content
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :dependencies, :gemspec
31
+
32
+ def replace_gemspec_version_requirement(gemspec, dependency, content)
33
+ return content unless requirement_changed?(gemspec, dependency)
34
+
35
+ updated_requirement =
36
+ dependency.requirements.
37
+ find { |r| r[:file] == gemspec.name }.
38
+ fetch(:requirement)
39
+
40
+ previous_requirement =
41
+ dependency.previous_requirements.
42
+ find { |r| r[:file] == gemspec.name }.
43
+ fetch(:requirement)
44
+
45
+ RequirementReplacer.new(
46
+ dependency: dependency,
47
+ file_type: :gemspec,
48
+ updated_requirement: updated_requirement,
49
+ previous_requirement: previous_requirement
50
+ ).rewrite(content)
51
+ end
52
+
53
+ def requirement_changed?(file, dependency)
54
+ changed_requirements =
55
+ dependency.requirements - dependency.previous_requirements
56
+
57
+ changed_requirements.any? { |f| f[:file] == file.name }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parser/current"
4
+ require "dependabot/bundler/file_updater"
5
+
6
+ module Dependabot
7
+ module Bundler
8
+ class FileUpdater
9
+ class GitPinReplacer
10
+ attr_reader :dependency, :new_pin
11
+
12
+ def initialize(dependency:, new_pin:)
13
+ @dependency = dependency
14
+ @new_pin = new_pin
15
+ end
16
+
17
+ def rewrite(content)
18
+ buffer = Parser::Source::Buffer.new("(gemfile_content)")
19
+ buffer.source = content
20
+ ast = Parser::CurrentRuby.new.parse(buffer)
21
+
22
+ Rewriter.
23
+ new(dependency: dependency, new_pin: new_pin).
24
+ rewrite(buffer, ast)
25
+ end
26
+
27
+ class Rewriter < Parser::TreeRewriter
28
+ PIN_KEYS = %i(ref tag).freeze
29
+ attr_reader :dependency, :new_pin
30
+
31
+ def initialize(dependency:, new_pin:)
32
+ @dependency = dependency
33
+ @new_pin = new_pin
34
+ end
35
+
36
+ def on_send(node)
37
+ return unless declares_targeted_gem?(node)
38
+ return unless node.children.last.type == :hash
39
+
40
+ kwargs_node = node.children.last
41
+ kwargs_node.children.each do |hash_pair|
42
+ next unless PIN_KEYS.include?(key_from_hash_pair(hash_pair))
43
+
44
+ update_value(hash_pair)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def declares_targeted_gem?(node)
51
+ return false unless node.children[1] == :gem
52
+
53
+ node.children[2].children.first == dependency.name
54
+ end
55
+
56
+ def key_from_hash_pair(node)
57
+ node.children.first.children.first.to_sym
58
+ end
59
+
60
+ def update_value(hash_pair)
61
+ value_node = hash_pair.children.last
62
+ open_quote_character, close_quote_character =
63
+ extract_quote_characters_from(value_node)
64
+
65
+ replace(
66
+ value_node.loc.expression,
67
+ %(#{open_quote_character}#{new_pin}#{close_quote_character})
68
+ )
69
+ end
70
+
71
+ def extract_quote_characters_from(value_node)
72
+ [value_node.loc.begin.source, value_node.loc.end.source]
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parser/current"
4
+ require "dependabot/bundler/file_updater"
5
+
6
+ module Dependabot
7
+ module Bundler
8
+ class FileUpdater
9
+ class GitSourceRemover
10
+ attr_reader :dependency
11
+
12
+ def initialize(dependency:)
13
+ @dependency = dependency
14
+ end
15
+
16
+ def rewrite(content)
17
+ buffer = Parser::Source::Buffer.new("(gemfile_content)")
18
+ buffer.source = content
19
+ ast = Parser::CurrentRuby.new.parse(buffer)
20
+
21
+ Rewriter.new(dependency: dependency).rewrite(buffer, ast)
22
+ end
23
+
24
+ class Rewriter < Parser::TreeRewriter
25
+ # TODO: Hack until Bundler 1.16.0 is available on Heroku
26
+ GOOD_KEYS = %i(
27
+ group groups path glob name require platform platforms type
28
+ source install_if
29
+ ).freeze
30
+
31
+ attr_reader :dependency
32
+
33
+ def initialize(dependency:)
34
+ @dependency = dependency
35
+ end
36
+
37
+ def on_send(node)
38
+ return unless declares_targeted_gem?(node)
39
+ return unless node.children.last.type == :hash
40
+
41
+ kwargs_node = node.children.last
42
+ keys = kwargs_node.children.map do |hash_pair|
43
+ key_from_hash_pair(hash_pair)
44
+ end
45
+
46
+ if keys.none? { |key| GOOD_KEYS.include?(key) }
47
+ remove_all_kwargs(node)
48
+ else
49
+ remove_git_related_kwargs(kwargs_node)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def declares_targeted_gem?(node)
56
+ return false unless node.children[1] == :gem
57
+
58
+ node.children[2].children.first == dependency.name
59
+ end
60
+
61
+ def key_from_hash_pair(node)
62
+ node.children.first.children.first.to_sym
63
+ end
64
+
65
+ def remove_all_kwargs(node)
66
+ kwargs_node = node.children.last
67
+
68
+ range_to_remove =
69
+ kwargs_node.loc.expression.join(node.children[-2].loc.end.end)
70
+
71
+ remove(range_to_remove)
72
+ end
73
+
74
+ def remove_git_related_kwargs(kwargs_node)
75
+ good_key_index = nil
76
+ hash_pairs = kwargs_node.children
77
+
78
+ hash_pairs.each_with_index do |hash_pair, index|
79
+ if GOOD_KEYS.include?(key_from_hash_pair(hash_pair))
80
+ good_key_index = index
81
+ next
82
+ end
83
+
84
+ range_to_remove =
85
+ if good_key_index.nil?
86
+ next_arg_start = hash_pairs[index + 1].loc.expression.begin
87
+ hash_pair.loc.expression.join(next_arg_start)
88
+ else
89
+ last_arg_end = hash_pairs[good_key_index].loc.expression.end
90
+ hash_pair.loc.expression.join(last_arg_end)
91
+ end
92
+
93
+ remove(range_to_remove)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end