bundler-gem_bytes 0.2.0 → 0.2.2
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 +4 -4
- data/.release-please-manifest.json +3 -0
- data/CHANGELOG.md +21 -0
- data/README.md +92 -10
- data/Rakefile +8 -0
- data/lib/bundler/gem_bytes/actions/gemspec/attribute.rb +51 -0
- data/lib/bundler/gem_bytes/actions/gemspec/attribute_node.rb +32 -0
- data/lib/bundler/gem_bytes/actions/gemspec/delete_dependency.rb +106 -0
- data/lib/bundler/gem_bytes/actions/gemspec/dependency.rb +73 -0
- data/lib/bundler/gem_bytes/actions/gemspec/dependency_node.rb +32 -0
- data/lib/bundler/gem_bytes/actions/gemspec/gem_specification.rb +121 -0
- data/lib/bundler/gem_bytes/actions/gemspec/upsert_dependency.rb +210 -0
- data/lib/bundler/gem_bytes/actions/gemspec.rb +281 -0
- data/lib/bundler/gem_bytes/actions.rb +22 -32
- data/lib/bundler/gem_bytes/bundler_command.rb +5 -7
- data/lib/bundler/gem_bytes/script_executor.rb +26 -6
- data/lib/bundler/gem_bytes/version.rb +1 -1
- data/lib/bundler/gem_bytes.rb +1 -1
- data/release-please-config.json +22 -0
- metadata +15 -11
- data/lib/bundler/gem_bytes/gemspec/delete_dependency.rb +0 -216
- data/lib/bundler/gem_bytes/gemspec/upsert_dependency.rb +0 -336
- data/lib/bundler/gem_bytes/gemspec.rb +0 -11
@@ -0,0 +1,22 @@
|
|
1
|
+
{
|
2
|
+
"bootstrap-sha": "28bcb17c41c9f9888c79c353731f557478f400f6",
|
3
|
+
"packages": {
|
4
|
+
".": {
|
5
|
+
"release-type": "ruby",
|
6
|
+
"package-name": "bundler-gem_bytes",
|
7
|
+
"changelog-path": "CHANGELOG.md",
|
8
|
+
"version-file": "lib/bundler/gem_bytes/version.rb",
|
9
|
+
"bump-minor-pre-major": true,
|
10
|
+
"bump-patch-for-minor-pre-major": true,
|
11
|
+
"draft": false,
|
12
|
+
"prerelease": false,
|
13
|
+
"include-component-in-tag": false
|
14
|
+
}
|
15
|
+
},
|
16
|
+
"plugins": [
|
17
|
+
{
|
18
|
+
"type": "sentence-case"
|
19
|
+
}
|
20
|
+
],
|
21
|
+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
|
22
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bundler-gem_bytes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Couball
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activesupport
|
@@ -261,6 +260,7 @@ extra_rdoc_files: []
|
|
261
260
|
files:
|
262
261
|
- ".commitlintrc.yml"
|
263
262
|
- ".husky/commit-msg"
|
263
|
+
- ".release-please-manifest.json"
|
264
264
|
- ".rspec"
|
265
265
|
- ".rubocop.yml"
|
266
266
|
- CHANGELOG.md
|
@@ -270,14 +270,20 @@ files:
|
|
270
270
|
- Rakefile
|
271
271
|
- lib/bundler/gem_bytes.rb
|
272
272
|
- lib/bundler/gem_bytes/actions.rb
|
273
|
+
- lib/bundler/gem_bytes/actions/gemspec.rb
|
274
|
+
- lib/bundler/gem_bytes/actions/gemspec/attribute.rb
|
275
|
+
- lib/bundler/gem_bytes/actions/gemspec/attribute_node.rb
|
276
|
+
- lib/bundler/gem_bytes/actions/gemspec/delete_dependency.rb
|
277
|
+
- lib/bundler/gem_bytes/actions/gemspec/dependency.rb
|
278
|
+
- lib/bundler/gem_bytes/actions/gemspec/dependency_node.rb
|
279
|
+
- lib/bundler/gem_bytes/actions/gemspec/gem_specification.rb
|
280
|
+
- lib/bundler/gem_bytes/actions/gemspec/upsert_dependency.rb
|
273
281
|
- lib/bundler/gem_bytes/bundler_command.rb
|
274
|
-
- lib/bundler/gem_bytes/gemspec.rb
|
275
|
-
- lib/bundler/gem_bytes/gemspec/delete_dependency.rb
|
276
|
-
- lib/bundler/gem_bytes/gemspec/upsert_dependency.rb
|
277
282
|
- lib/bundler/gem_bytes/script_executor.rb
|
278
283
|
- lib/bundler/gem_bytes/version.rb
|
279
284
|
- package.json
|
280
285
|
- plugins.rb
|
286
|
+
- release-please-config.json
|
281
287
|
homepage: https://github.com/main-branch/bundler-gem_bytes
|
282
288
|
licenses:
|
283
289
|
- MIT
|
@@ -285,11 +291,10 @@ metadata:
|
|
285
291
|
allowed_push_host: https://rubygems.org
|
286
292
|
homepage_uri: https://github.com/main-branch/bundler-gem_bytes
|
287
293
|
source_code_uri: https://github.com/main-branch/bundler-gem_bytes
|
288
|
-
documentation_uri: https://rubydoc.info/gems/bundler-gem_bytes/0.2.
|
289
|
-
changelog_uri: https://rubydoc.info/gems/bundler-gem_bytes/0.2.
|
294
|
+
documentation_uri: https://rubydoc.info/gems/bundler-gem_bytes/0.2.2
|
295
|
+
changelog_uri: https://rubydoc.info/gems/bundler-gem_bytes/0.2.2/file/CHANGELOG.md
|
290
296
|
bug_tracker_uri: https://github.com/main-branch/bundler-gem_bytes/issues
|
291
297
|
rubygems_mfa_required: 'true'
|
292
|
-
post_install_message:
|
293
298
|
rdoc_options: []
|
294
299
|
require_paths:
|
295
300
|
- lib
|
@@ -304,8 +309,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
304
309
|
- !ruby/object:Gem::Version
|
305
310
|
version: '0'
|
306
311
|
requirements: []
|
307
|
-
rubygems_version: 3.
|
308
|
-
signing_key:
|
312
|
+
rubygems_version: 3.6.7
|
309
313
|
specification_version: 4
|
310
314
|
summary: A bundler plugin that adds features to your existing Ruby Gems project
|
311
315
|
test_files: []
|
@@ -1,216 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'parser/current'
|
4
|
-
require 'rubocop-ast'
|
5
|
-
require 'active_support/core_ext/object'
|
6
|
-
|
7
|
-
module Bundler
|
8
|
-
module GemBytes
|
9
|
-
module Gemspec
|
10
|
-
# Delete a dependency in a gemspec file
|
11
|
-
#
|
12
|
-
# This class works by parsing the gemspec file into an AST and then walking the
|
13
|
-
# AST to find the Gem::Specification block (via #on_block). Once the block is
|
14
|
-
# found, AST within that block is walked to locate the dependency declarations
|
15
|
-
# (via #on_send). Any dependency declaration that matches the given gem name is
|
16
|
-
# collected into the found_dependencies array.
|
17
|
-
#
|
18
|
-
# Once the Gem::Specification block is fully processed, any dependencies on the
|
19
|
-
# given gem are deleted from the gemspec source.
|
20
|
-
#
|
21
|
-
# If the dependency is not found, the gemspec source is returned unmodified.
|
22
|
-
#
|
23
|
-
# @example
|
24
|
-
# require 'bundler/gem_bytes'
|
25
|
-
#
|
26
|
-
# delete_dependency = Bundler::GemBytes::Gemspec::DeleteDependency.new('test_tool')
|
27
|
-
#
|
28
|
-
# gemspec_file = 'foo.gemspec'
|
29
|
-
# gemspec = File.read(gemspec_file)
|
30
|
-
# updated_gemspec = delete_dependency.call(gemspec, path: gemspec_file)
|
31
|
-
# File.write(gemspec_file, updated_gemspec)
|
32
|
-
#
|
33
|
-
# @!attribute [r] gem_name
|
34
|
-
# The name of the gem to add a dependency on (i.e. 'rubocop')
|
35
|
-
# @return [String]
|
36
|
-
# @api private
|
37
|
-
#
|
38
|
-
# @!attribute [r] receiver_name
|
39
|
-
# The name of the receiver for the Gem::Specification block
|
40
|
-
#
|
41
|
-
# i.e. 'spec' in `spec.add_dependency 'rubocop', '~> 1.0'`
|
42
|
-
#
|
43
|
-
# @return [Symbol]
|
44
|
-
# @api private
|
45
|
-
#
|
46
|
-
# @!attribute [r] found_gemspec_block
|
47
|
-
# Whether the Gem::Specification block was found in the gemspec file
|
48
|
-
#
|
49
|
-
# Only valid after calling `#call`.
|
50
|
-
# @return [Boolean]
|
51
|
-
# @api private
|
52
|
-
#
|
53
|
-
# @!attribute [r] found_dependencies
|
54
|
-
# The dependencies found in the gemspec file
|
55
|
-
#
|
56
|
-
# Only valid after calling `#call`.
|
57
|
-
# @return [Array<Hash>]
|
58
|
-
# @api private
|
59
|
-
#
|
60
|
-
# @api public
|
61
|
-
class DeleteDependency < Parser::TreeRewriter
|
62
|
-
# Create a new instance of a dependency upserter
|
63
|
-
# @example
|
64
|
-
# command = Bundler::GemBytes::Gemspec::DeleteDependency.new('my_gem')
|
65
|
-
# @param gem_name [String] The name of the gem to add a dependency on
|
66
|
-
def initialize(gem_name)
|
67
|
-
super()
|
68
|
-
|
69
|
-
@gem_name = gem_name
|
70
|
-
|
71
|
-
@found_dependencies = []
|
72
|
-
end
|
73
|
-
|
74
|
-
# Returns the content of the gemspec file with the dependency deleted
|
75
|
-
#
|
76
|
-
# @param code [String] The content of the gemspec file
|
77
|
-
# @param path [String] This should be the path to the gemspspec file
|
78
|
-
#
|
79
|
-
# path is used to generate error messages only
|
80
|
-
#
|
81
|
-
# @return [String] The updated gemspec content with the dependency deleted
|
82
|
-
#
|
83
|
-
# @raise [ArgumentError] if the Gem Specification block is not found in the given gemspec
|
84
|
-
#
|
85
|
-
# @example
|
86
|
-
# code = File.read('project.gemspec')
|
87
|
-
# command = Bundler::GemBytes::DeleteDependency.new('my_gem')
|
88
|
-
# updated_code = command.call(code)
|
89
|
-
# puts updated_code
|
90
|
-
#
|
91
|
-
def call(code, path: '(string)')
|
92
|
-
@found_gemspec_block = false
|
93
|
-
rewrite(*parse(code, path)).tap do |_result|
|
94
|
-
raise ArgumentError, 'Gem::Specification block not found' unless found_gemspec_block
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
attr_reader :gem_name, :receiver_name, :found_gemspec_block, :found_dependencies
|
99
|
-
|
100
|
-
# Handles block nodes within the AST to locate the Gem Specification block
|
101
|
-
#
|
102
|
-
# @param node [Parser::AST::Node] The block node within the AST
|
103
|
-
# @return [void]
|
104
|
-
# @api private
|
105
|
-
def on_block(node)
|
106
|
-
return if receiver_name # already processing the Gem Specification block
|
107
|
-
|
108
|
-
@found_gemspec_block = true
|
109
|
-
@receiver_name = gem_specification_pattern.match(node)
|
110
|
-
|
111
|
-
return unless receiver_name
|
112
|
-
|
113
|
-
super # process the children of this node to find the existing dependencies
|
114
|
-
|
115
|
-
delete_dependencies
|
116
|
-
|
117
|
-
@receiver_name = nil
|
118
|
-
end
|
119
|
-
|
120
|
-
# Handles `send` nodes within the AST to locate dependency calls
|
121
|
-
#
|
122
|
-
# If receiver_name is not present then we are not in a Gem Specification block.
|
123
|
-
#
|
124
|
-
# @param node [Parser::AST::Node] The `send` node to check for dependency patterns
|
125
|
-
# @return [void]
|
126
|
-
# @api private
|
127
|
-
def on_send(node)
|
128
|
-
return unless receiver_name.present?
|
129
|
-
return unless (match = dependency_pattern.match(node))
|
130
|
-
|
131
|
-
found_dependencies << { node:, match: }
|
132
|
-
end
|
133
|
-
|
134
|
-
private
|
135
|
-
|
136
|
-
# Parses the given code into an AST
|
137
|
-
# @param code [String] The code to parse
|
138
|
-
# @param path [String] The path to the file being parsed (used for error messages only)
|
139
|
-
# @return [Array<Parser::AST::Node, Parser::Source::Buffer>] The AST and buffer
|
140
|
-
# @api private
|
141
|
-
def parse(code, path)
|
142
|
-
buffer = Parser::Source::Buffer.new(path, source: code)
|
143
|
-
processed_source = RuboCop::AST::ProcessedSource.new(code, ruby_version, path)
|
144
|
-
unless processed_source.valid_syntax?
|
145
|
-
raise "Invalid syntax in #{path}\n#{processed_source.diagnostics.map(&:render).join("\n")}"
|
146
|
-
end
|
147
|
-
|
148
|
-
ast = processed_source.ast
|
149
|
-
[buffer, ast]
|
150
|
-
end
|
151
|
-
|
152
|
-
# Deletes any dependency on the given gem within the Gem::Specification block
|
153
|
-
# @param node [Parser::AST::Node] The block node within the AST
|
154
|
-
# @return [void]
|
155
|
-
# @api private
|
156
|
-
def delete_dependencies
|
157
|
-
found_dependencies.each do |found_dependency|
|
158
|
-
dependency_node = found_dependency[:node]
|
159
|
-
remove(range_including_leading_spaces(dependency_node))
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# Returns the range of the dependency node including any leading spaces & newline
|
164
|
-
# @param node [Parser::AST::Node] The node
|
165
|
-
# @return [Parser::Source::Range] The range of the dependency node
|
166
|
-
# @api private
|
167
|
-
def range_including_leading_spaces(node)
|
168
|
-
leading_spaces = leading_whitespace_count(node)
|
169
|
-
range = node.loc.expression
|
170
|
-
range.with(begin_pos: range.begin_pos - leading_spaces - 1, end_pos: range.end_pos)
|
171
|
-
end
|
172
|
-
|
173
|
-
# Returns the # of leading whitespace chars in the source line before the node
|
174
|
-
# @param node [Parser::AST::Node] The node
|
175
|
-
# @return [Integer] The number of leading whitespace characters
|
176
|
-
# @api private
|
177
|
-
def leading_whitespace_count(node)
|
178
|
-
match_data = node.loc.expression.source_line.match(/^\s*/)
|
179
|
-
match_data ? match_data[0].size : 0
|
180
|
-
end
|
181
|
-
|
182
|
-
# Returns the Ruby version in use as a float (MAJOR.MINOR only)
|
183
|
-
# @return [Float] The Ruby version number, e.g., 3.0
|
184
|
-
# @api private
|
185
|
-
def ruby_version = RUBY_VERSION.match(/^(?<version>\d+\.\d+)/)['version'].to_f
|
186
|
-
|
187
|
-
# The pattern to match a dependency declaration in the AST
|
188
|
-
# @return [RuboCop::AST::NodePattern] The dependency pattern
|
189
|
-
# @api private
|
190
|
-
def dependency_pattern
|
191
|
-
# :nocov: JRuby give false positive for this line being uncovered by tests
|
192
|
-
@dependency_pattern ||=
|
193
|
-
RuboCop::AST::NodePattern.new(<<~PATTERN)
|
194
|
-
(send
|
195
|
-
{ (send _ :#{receiver_name}) | (lvar :#{receiver_name}) }
|
196
|
-
${ :add_dependency :add_runtime_dependency :add_development_dependency }
|
197
|
-
(str #{gem_name ? "$\"#{gem_name}\"" : '$_gem_name'})
|
198
|
-
<(str $_version_constraint) ...>
|
199
|
-
)
|
200
|
-
PATTERN
|
201
|
-
# :nocov:
|
202
|
-
end
|
203
|
-
|
204
|
-
# The pattern to match the Gem::Specification block in the AST
|
205
|
-
# @return [RuboCop::AST::NodePattern] The Gem::Specification pattern
|
206
|
-
# @api private
|
207
|
-
def gem_specification_pattern
|
208
|
-
@gem_specification_pattern ||=
|
209
|
-
RuboCop::AST::NodePattern.new(<<~PATTERN)
|
210
|
-
(block (send (const (const nil? :Gem) :Specification) :new)(args (arg $_)) ...)
|
211
|
-
PATTERN
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
@@ -1,336 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'parser/current'
|
4
|
-
require 'rubocop-ast'
|
5
|
-
require 'active_support/core_ext/object'
|
6
|
-
|
7
|
-
module Bundler
|
8
|
-
module GemBytes
|
9
|
-
module Gemspec
|
10
|
-
# Add or update a dependency in a gemspec file
|
11
|
-
#
|
12
|
-
# This class allows the addition of a new dependency or the updating of an
|
13
|
-
# existing dependency in a gemspec file.
|
14
|
-
#
|
15
|
-
# This class works by parsing the gemspec file into an AST and then walking the
|
16
|
-
# AST to find the Gem::Specification block (via #on_block). Once the block is
|
17
|
-
# found, AST within that block is walked to locate the dependency declarations
|
18
|
-
# (via #on_send). Any dependency declaration that matches the given gem name is
|
19
|
-
# collected into the found_dependencies array.
|
20
|
-
#
|
21
|
-
# Once the Gem::Specification block is fully processed, if a dependency on the
|
22
|
-
# given gem is not found, a new dependency is added to the end of the
|
23
|
-
# Gem::Specification block.
|
24
|
-
#
|
25
|
-
# If one or more dependencies are found, the version constraint is updated to
|
26
|
-
# the given version constraint. If the dependency type is different from the
|
27
|
-
# existing dependency, an error is raised unless the `force` option is set to
|
28
|
-
# true.
|
29
|
-
#
|
30
|
-
# @example
|
31
|
-
# require 'bundler/gem_bytes'
|
32
|
-
#
|
33
|
-
# add_dependency = Bundler::GemBytes::Gemspec::UpsertDependency.new(:dependency, 'test_tool', '~> 2.1')
|
34
|
-
#
|
35
|
-
# gemspec = File.read('bundler-gem_bytes.gemspec')
|
36
|
-
# updated_gemspec = add_dependency.call(gemspec)
|
37
|
-
# File.write('project.gemspec', updated_gemspec)
|
38
|
-
#
|
39
|
-
# @!attribute [r] dependency_type
|
40
|
-
# The type of dependency to add
|
41
|
-
# @return [:runtime, :depenncy]
|
42
|
-
# @api private
|
43
|
-
#
|
44
|
-
# @!attribute [r] gem_name
|
45
|
-
# The name of the gem to add a dependency on (i.e. 'rubocop')
|
46
|
-
# @return [String]
|
47
|
-
# @api private
|
48
|
-
#
|
49
|
-
# @!attribute [r] version_constraint
|
50
|
-
# The version constraint for the gem (i.e. '~> 2.1')
|
51
|
-
# @return [String]
|
52
|
-
# @api private
|
53
|
-
#
|
54
|
-
# @!attribute [r] force
|
55
|
-
# Whether to update the dependency even if the type is different
|
56
|
-
# @return [Boolean]
|
57
|
-
# @api private
|
58
|
-
#
|
59
|
-
# @!attribute [r] receiver_name
|
60
|
-
# The name of the receiver for the Gem::Specification block
|
61
|
-
#
|
62
|
-
# i.e. 'spec' in `spec.add_dependency 'rubocop', '~> 1.0'`
|
63
|
-
#
|
64
|
-
# @return [Symbol]
|
65
|
-
# @api private
|
66
|
-
#
|
67
|
-
# @!attribute [r] found_gemspec_block
|
68
|
-
# Whether the Gem::Specification block was found in the gemspec file
|
69
|
-
#
|
70
|
-
# Only valid after calling `#call`.
|
71
|
-
# @return [Boolean]
|
72
|
-
# @api private
|
73
|
-
#
|
74
|
-
# @!attribute [r] found_dependencies
|
75
|
-
# The dependencies found in the gemspec file
|
76
|
-
#
|
77
|
-
# Only valid after calling `#call`.
|
78
|
-
# @return [Array<Hash>]
|
79
|
-
# @api private
|
80
|
-
#
|
81
|
-
# @api public
|
82
|
-
class UpsertDependency < Parser::TreeRewriter # rubocop:disable Metrics/ClassLength
|
83
|
-
# Create a new instance of a dependency upserter
|
84
|
-
# @example
|
85
|
-
# add_dependency = Bundler::GemBytes::Gemspec::UpsertDependency.new(:runtime, 'my_gem', '~> 1.0')
|
86
|
-
# @param dependency_type [Symbol] The type of dependency to add
|
87
|
-
# @param gem_name [String] The name of the gem to add a dependency on
|
88
|
-
# @param version_constraint [String] The version constraint for the gem
|
89
|
-
# @param force [Boolean] Whether to update the dependency even if the type is different
|
90
|
-
def initialize(dependency_type, gem_name, version_constraint, force: false)
|
91
|
-
super()
|
92
|
-
|
93
|
-
self.dependency_type = dependency_type
|
94
|
-
@gem_name = gem_name
|
95
|
-
self.version_constraint = version_constraint
|
96
|
-
@force = force
|
97
|
-
|
98
|
-
@found_dependencies = []
|
99
|
-
end
|
100
|
-
|
101
|
-
# Returns the content of the gemspec file with the new/updated dependency
|
102
|
-
#
|
103
|
-
# @param code [String] The content of the gemspec file
|
104
|
-
# @param path [String] This should be the path to the gemspspec file
|
105
|
-
#
|
106
|
-
# path is used to generate error messages only
|
107
|
-
#
|
108
|
-
# @return [String] The updated gemspec content with the new/added dependency
|
109
|
-
#
|
110
|
-
# @raise [ArgumentError] if the Gem Specification block is not found in the given gemspec
|
111
|
-
#
|
112
|
-
# @example
|
113
|
-
# code = File.read('project.gemspec')
|
114
|
-
# add_dependency = Bundler::GemBytes::AddDependency.new(:runtime, 'my_gem', '~> 1.0')
|
115
|
-
# updated_code = add_dependency.call(code)
|
116
|
-
# puts updated_code
|
117
|
-
#
|
118
|
-
def call(code, path: '(string)')
|
119
|
-
@found_gemspec_block = false
|
120
|
-
rewrite(*parse(code, path)).tap do |_result|
|
121
|
-
raise ArgumentError, 'Gem::Specification block not found' unless found_gemspec_block
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
attr_reader :dependency_type, :gem_name, :version_constraint, :force,
|
126
|
-
:receiver_name, :found_gemspec_block, :found_dependencies
|
127
|
-
|
128
|
-
# Handles block nodes within the AST to locate the Gem Specification block
|
129
|
-
#
|
130
|
-
# @param node [Parser::AST::Node] The block node within the AST
|
131
|
-
# @return [void]
|
132
|
-
# @api private
|
133
|
-
def on_block(node)
|
134
|
-
return if receiver_name # already processing the Gem Specification block
|
135
|
-
|
136
|
-
@found_gemspec_block = true
|
137
|
-
@receiver_name = gem_specification_pattern.match(node)
|
138
|
-
|
139
|
-
return unless receiver_name
|
140
|
-
|
141
|
-
super # process the children of this node to find the existing dependencies
|
142
|
-
|
143
|
-
upsert_dependency(node)
|
144
|
-
|
145
|
-
@receiver_name = nil
|
146
|
-
end
|
147
|
-
|
148
|
-
# Handles `send` nodes within the AST to locate dependency calls
|
149
|
-
#
|
150
|
-
# If receiver_name is not present then we are not in a Gem Specification block.
|
151
|
-
#
|
152
|
-
# @param node [Parser::AST::Node] The `send` node to check for dependency patterns
|
153
|
-
# @return [void]
|
154
|
-
# @api private
|
155
|
-
def on_send(node)
|
156
|
-
return unless receiver_name.present?
|
157
|
-
return unless (match = dependency_pattern.match(node))
|
158
|
-
|
159
|
-
found_dependencies << { node:, match: }
|
160
|
-
end
|
161
|
-
|
162
|
-
private
|
163
|
-
|
164
|
-
# Parses the given code into an AST
|
165
|
-
# @param code [String] The code to parse
|
166
|
-
# @param path [String] The path to the file being parsed (used for error messages only)
|
167
|
-
# @return [Array<Parser::AST::Node, Parser::Source::Buffer>] The AST and buffer
|
168
|
-
# @api private
|
169
|
-
def parse(code, path)
|
170
|
-
buffer = Parser::Source::Buffer.new(path, source: code)
|
171
|
-
processed_source = RuboCop::AST::ProcessedSource.new(code, ruby_version, path)
|
172
|
-
unless processed_source.valid_syntax?
|
173
|
-
raise "Invalid syntax in #{path}\n#{processed_source.diagnostics.map(&:render).join("\n")}"
|
174
|
-
end
|
175
|
-
|
176
|
-
ast = processed_source.ast
|
177
|
-
[buffer, ast]
|
178
|
-
end
|
179
|
-
|
180
|
-
# Adds or updates the given dependency in the Gem::Specification block
|
181
|
-
# @param node [Parser::AST::Node] The block node within the AST
|
182
|
-
# @return [void]
|
183
|
-
# @api private
|
184
|
-
def upsert_dependency(node)
|
185
|
-
if found_dependencies.empty?
|
186
|
-
add_dependency(node)
|
187
|
-
else
|
188
|
-
update_dependency
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
# Adds a new dependency to the Gem::Specification block
|
193
|
-
# @param node [Parser::AST::Node] The Gem::Specification block node within the AST
|
194
|
-
# @return [void]
|
195
|
-
# @api private
|
196
|
-
def add_dependency(node)
|
197
|
-
insert_after(node.children[2].children.last.loc.expression, "\n #{dependency_source_code}")
|
198
|
-
end
|
199
|
-
|
200
|
-
# The dependency type (:runtime or :development) based on a given method name
|
201
|
-
# @param method [Symbol] The method name to convert to a dependency type
|
202
|
-
# @return [Symbol] The dependency type
|
203
|
-
# @api private
|
204
|
-
def dependency_method_to_type(method)
|
205
|
-
method == :add_development_dependency ? :development : :runtime
|
206
|
-
end
|
207
|
-
|
208
|
-
# Error message for a dependency type conflict
|
209
|
-
# @param node [Parser::AST::Node] The existing dependency node
|
210
|
-
# @return [String] The error message
|
211
|
-
# @api private
|
212
|
-
def dependency_type_conflict_error(node)
|
213
|
-
# :nocov: JRuby give false positive for this line being uncovered by tests
|
214
|
-
<<~MESSAGE.chomp.gsub("\n", ' ')
|
215
|
-
Trying to add a
|
216
|
-
#{dependency_method_to_type(dependency_type_method).upcase}
|
217
|
-
dependency on "#{gem_name}" which conflicts with the existing
|
218
|
-
#{dependency_method_to_type(node.children[1]).upcase}
|
219
|
-
dependency.
|
220
|
-
Pass force: true to update dependencies where the
|
221
|
-
dependency type is different.
|
222
|
-
MESSAGE
|
223
|
-
# :nocov:
|
224
|
-
end
|
225
|
-
|
226
|
-
# Checks if the given dependency type conflicts with the existing dependency type
|
227
|
-
#
|
228
|
-
# Returns false if {#force} is true.
|
229
|
-
#
|
230
|
-
# @param dependency_node [Parser::AST::Node] The existing dependency node
|
231
|
-
# @return [Boolean] Whether the dependency type conflicts
|
232
|
-
# @api private
|
233
|
-
def dependency_type_conflict?(dependency_node)
|
234
|
-
dependency_node.children[1] != dependency_type_method && !force
|
235
|
-
end
|
236
|
-
|
237
|
-
# The source code for the updated dependency declaration
|
238
|
-
# @param existing_dependency_node [Parser::AST::Node] The existing dependency node
|
239
|
-
# @return [String] The source code for the dependency declaration
|
240
|
-
# @api private
|
241
|
-
def dependency_source_code(existing_dependency_node = nil)
|
242
|
-
# Use existing quote character for string literals
|
243
|
-
q = existing_dependency_node ? existing_dependency_node.children[3].loc.expression.source[0] : "'"
|
244
|
-
"#{receiver_name}.#{dependency_type_method} #{q}#{gem_name}#{q}, #{q}#{version_constraint}#{q}"
|
245
|
-
end
|
246
|
-
|
247
|
-
# Replaces the existing dependency node with the updated dependency declaration
|
248
|
-
# @param dependency_node [Parser::AST::Node] The existing dependency node
|
249
|
-
# @return [void]
|
250
|
-
# @api private
|
251
|
-
def replace_dependency_node(dependency_node)
|
252
|
-
replace(dependency_node.loc.expression, dependency_source_code(dependency_node))
|
253
|
-
end
|
254
|
-
|
255
|
-
# Updates the found_dependencies from the Gem::Specification block
|
256
|
-
# @return [void]
|
257
|
-
# @api private
|
258
|
-
def update_dependency
|
259
|
-
found_dependencies.each do |found_dependency|
|
260
|
-
dependency_node = found_dependency[:node]
|
261
|
-
raise(dependency_type_conflict_error(dependency_node)) if dependency_type_conflict?(dependency_node)
|
262
|
-
|
263
|
-
replace_dependency_node(dependency_node)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
# Validates and sets the dependency type
|
268
|
-
# @param dependency_type [Symbol] The type of dependency to add (must be :runtime or :development)
|
269
|
-
# @raise [ArgumentError] if the dependency type is not :runtime or :development
|
270
|
-
# @return [Symbol] The dependency type
|
271
|
-
# @api private
|
272
|
-
def dependency_type=(dependency_type)
|
273
|
-
unless %i[runtime development].include?(dependency_type)
|
274
|
-
message = "Invalid dependency type: #{dependency_type.inspect}"
|
275
|
-
raise(ArgumentError, message)
|
276
|
-
end
|
277
|
-
@dependency_type = dependency_type
|
278
|
-
end
|
279
|
-
|
280
|
-
# Validates and sets the version constraint
|
281
|
-
# @param version_constraint [String] The version constraint to set
|
282
|
-
# @raise [ArgumentError] if the version constraint is invalid
|
283
|
-
# @return [String] The version constraint
|
284
|
-
# @api private
|
285
|
-
def version_constraint=(version_constraint)
|
286
|
-
begin
|
287
|
-
Gem::Requirement.new(version_constraint)
|
288
|
-
true
|
289
|
-
rescue Gem::Requirement::BadRequirementError
|
290
|
-
raise ArgumentError, "Invalid version constraint: #{version_constraint.inspect}"
|
291
|
-
end
|
292
|
-
@version_constraint = version_constraint
|
293
|
-
end
|
294
|
-
|
295
|
-
# Returns the Ruby version in use as a float (MAJOR.MINOR only)
|
296
|
-
# @return [Float] The Ruby version number, e.g., 3.0
|
297
|
-
# @api private
|
298
|
-
def ruby_version = RUBY_VERSION.match(/^(?<version>\d+\.\d+)/)['version'].to_f
|
299
|
-
|
300
|
-
# Determines the dependency method based on the dependency type
|
301
|
-
# @return [Symbol] Either :add_development_dependency or :add_dependency
|
302
|
-
# @api private
|
303
|
-
def dependency_type_method
|
304
|
-
dependency_type == :development ? :add_development_dependency : :add_dependency
|
305
|
-
end
|
306
|
-
|
307
|
-
# The pattern to match a dependency declaration in the AST
|
308
|
-
# @return [RuboCop::AST::NodePattern] The dependency pattern
|
309
|
-
# @api private
|
310
|
-
def dependency_pattern
|
311
|
-
# :nocov: JRuby give false positive for this line being uncovered by tests
|
312
|
-
@dependency_pattern ||=
|
313
|
-
RuboCop::AST::NodePattern.new(<<~PATTERN)
|
314
|
-
(send
|
315
|
-
{ (send _ :#{receiver_name}) | (lvar :#{receiver_name}) }
|
316
|
-
${ :add_dependency :add_runtime_dependency :add_development_dependency }
|
317
|
-
(str #{gem_name ? "$\"#{gem_name}\"" : '$_gem_name'})
|
318
|
-
<(str $_version_constraint) ...>
|
319
|
-
)
|
320
|
-
PATTERN
|
321
|
-
# :nocov:
|
322
|
-
end
|
323
|
-
|
324
|
-
# The pattern to match the Gem::Specification block in the AST
|
325
|
-
# @return [RuboCop::AST::NodePattern] The Gem::Specification pattern
|
326
|
-
# @api private
|
327
|
-
def gem_specification_pattern
|
328
|
-
@gem_specification_pattern ||=
|
329
|
-
RuboCop::AST::NodePattern.new(<<~PATTERN)
|
330
|
-
(block (send (const (const nil? :Gem) :Specification) :new)(args (arg $_)) ...)
|
331
|
-
PATTERN
|
332
|
-
end
|
333
|
-
end
|
334
|
-
end
|
335
|
-
end
|
336
|
-
end
|