Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check gemspec required_ruby_version before .ruby-version and other sources #12645

Merged
merged 1 commit into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/change_source_order_for_target_ruby.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#12645](https://github.com/rubocop/rubocop/pull/12645): Change source order for target ruby to check gemspec after RuboCop configuration. ([@jenshenny][])
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ AllCops:

Otherwise, RuboCop will then check your project for a series of files where
the version may be specified already. The files that will be looked for are
`.ruby-version`, `.tool-versions`, `Gemfile.lock`, and `*.gemspec`.
`*.gemspec`, `.ruby-version`, `.tool-versions`, and `Gemfile.lock`.
If Gemspec file has an array for `required_ruby_version`, the lowest version will be used.
If none of the files are found a default version value will be used.

Expand Down
158 changes: 79 additions & 79 deletions lib/rubocop/target_ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,84 @@ def find_version
end
end

# The target ruby version may be found in a .gemspec file.
# @api private
class GemspecFile < Source
extend NodePattern::Macros

GEMSPEC_EXTENSION = '.gemspec'

# @!method required_ruby_version(node)
def_node_search :required_ruby_version, <<~PATTERN
(send _ :required_ruby_version= $_)
PATTERN

# @!method gem_requirement_versions(node)
def_node_matcher :gem_requirement_versions, <<~PATTERN
(send (const(const _ :Gem):Requirement) :new
{$str+ | (send $str :freeze)+ | (array $str+) | (array (send $str :freeze)+)}
)
PATTERN

def name
"`required_ruby_version` parameter (in #{gemspec_filename})"
end

private

def find_version
file = gemspec_filepath
return unless file && File.file?(file)

right_hand_side = version_from_gemspec_file(file)
return if right_hand_side.nil?

find_default_minimal_known_ruby(right_hand_side)
end

def gemspec_filename
@gemspec_filename ||= begin
basename = Pathname.new(@config.base_dir_for_path_parameters).basename.to_s
"#{basename}#{GEMSPEC_EXTENSION}"
end
end

def gemspec_filepath
@gemspec_filepath ||=
@config.find_file_upwards(gemspec_filename, @config.base_dir_for_path_parameters)
end

def version_from_gemspec_file(file)
processed_source = ProcessedSource.from_file(file, DEFAULT_VERSION)
required_ruby_version(processed_source.ast).first
end

def version_from_right_hand_side(right_hand_side)
gem_requirement_versions = gem_requirement_versions(right_hand_side)

if right_hand_side.array_type?
version_from_array(right_hand_side)
elsif gem_requirement_versions
gem_requirement_versions.map(&:value)
else
right_hand_side.value
end
end

def version_from_array(array)
array.children.map(&:value)
end

def find_default_minimal_known_ruby(right_hand_side)
version = version_from_right_hand_side(right_hand_side)
requirement = Gem::Requirement.new(version)

KNOWN_RUBIES.detect do |v|
v >= DEFAULT_VERSION && requirement.satisfied_by?(Gem::Version.new("#{v}.99"))
end
end
end

# The target ruby version may be found in a .ruby-version file.
# @api private
class RubyVersionFile < Source
Expand Down Expand Up @@ -143,84 +221,6 @@ def bundler_lock_file_path
end
end

# The target ruby version may be found in a .gemspec file.
# @api private
class GemspecFile < Source
extend NodePattern::Macros

GEMSPEC_EXTENSION = '.gemspec'

# @!method required_ruby_version(node)
def_node_search :required_ruby_version, <<~PATTERN
(send _ :required_ruby_version= $_)
PATTERN

# @!method gem_requirement_versions(node)
def_node_matcher :gem_requirement_versions, <<~PATTERN
(send (const(const _ :Gem):Requirement) :new
{$str+ | (send $str :freeze)+ | (array $str+) | (array (send $str :freeze)+)}
)
PATTERN

def name
"`required_ruby_version` parameter (in #{gemspec_filename})"
end

private

def find_version
file = gemspec_filepath
return unless file && File.file?(file)

right_hand_side = version_from_gemspec_file(file)
return if right_hand_side.nil?

find_default_minimal_known_ruby(right_hand_side)
end

def gemspec_filename
@gemspec_filename ||= begin
basename = Pathname.new(@config.base_dir_for_path_parameters).basename.to_s
"#{basename}#{GEMSPEC_EXTENSION}"
end
end

def gemspec_filepath
@gemspec_filepath ||=
@config.find_file_upwards(gemspec_filename, @config.base_dir_for_path_parameters)
end

def version_from_gemspec_file(file)
processed_source = ProcessedSource.from_file(file, DEFAULT_VERSION)
required_ruby_version(processed_source.ast).first
end

def version_from_right_hand_side(right_hand_side)
gem_requirement_versions = gem_requirement_versions(right_hand_side)

if right_hand_side.array_type?
version_from_array(right_hand_side)
elsif gem_requirement_versions
gem_requirement_versions.map(&:value)
else
right_hand_side.value
end
end

def version_from_array(array)
array.children.map(&:value)
end

def find_default_minimal_known_ruby(right_hand_side)
version = version_from_right_hand_side(right_hand_side)
requirement = Gem::Requirement.new(version)

KNOWN_RUBIES.detect do |v|
v >= DEFAULT_VERSION && requirement.satisfied_by?(Gem::Version.new("#{v}.99"))
end
end
end

# If all else fails, a default version will be picked.
# @api private
class Default < Source
Expand All @@ -241,10 +241,10 @@ def self.supported_versions

SOURCES = [
RuboCopConfig,
GemspecFile,
RubyVersionFile,
ToolVersionsFile,
BundlerLockFile,
GemspecFile,
Default
].freeze

Expand Down