Skip to content

Commit

Permalink
[Fix #8761] Read required_ruby_version from gemspec file if it exis…
Browse files Browse the repository at this point in the history
…ts (#8885)
  • Loading branch information
HeroProtagonist committed Nov 8, 2020
1 parent c9f0389 commit 406d0e3
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 3 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## master (unreleased)

### New features

* [#8761](https://github.com/rubocop-hq/rubocop/issues/8761): Read `required_ruby_version` from gemspec file if it exists #8761. ([@HeroProtagonist][])

### Bug fixes

* [#8499](https://github.com/rubocop-hq/rubocop/issues/8499): Fix `Style/IfUnlessModifier` and `Style/WhileUntilModifier` to prevent an offense if there are both first-line comment and code after `end` block. ([@dsavochkin][])
Expand Down Expand Up @@ -5084,3 +5088,4 @@
[@miry]: https://github.com/miry
[@lautis]: https://github.com/lautis
[@pdobb]: https://github.com/pdobb
[@HeroProtagonist]: https://github.com/HeroProtagonist
7 changes: 5 additions & 2 deletions docs/modules/ROOT/pages/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,11 @@ AllCops:
TargetRubyVersion: 2.5
----

Otherwise, RuboCop will then check your project for `.ruby-version` and
use the version specified by it.
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`, `Gemfile.lock`, and `*.gemspec`. 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.

== Automatically Generated Configuration

Expand Down
58 changes: 57 additions & 1 deletion lib/rubocop/target_ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,62 @@ 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'

def_node_search :required_ruby_version, <<~PATTERN
(send _ :required_ruby_version= $_)
PATTERN

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

private

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

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

if version.array_type?
versions = version.children.map { |v| version_from_str(v.str_content) }
return versions.compact.min
end

version_from_str(version.str_content)
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_str(str)
str.match(/^(?:>=|<=)?\s*(?<version>\d+(?:\.\d+)*)/) do |md|
md[:version].to_f
end
end
end

# If all else fails, a default version will be picked.
# @api private
class Default < Source
Expand All @@ -130,7 +186,7 @@ def self.supported_versions
KNOWN_RUBIES
end

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

def initialize(config)
Expand Down
116 changes: 116 additions & 0 deletions spec/rubocop/target_ruby_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,122 @@
expect(target_ruby.version).to eq default_version
end
end

context 'gemspec file' do
context 'when file contains `required_ruby_version` as a string' do
let(:base_path) { configuration.base_dir_for_path_parameters }
let(:gemspec_file_path) { File.join(base_path, 'example.gemspec') }

it 'sets target_ruby from required_ruby_version from exact version' do
content =
<<-HEREDOC
Gem::Specification.new do |s|
s.name = 'test'
s.required_ruby_version = '2.7.4'
s.licenses = ['MIT']
end
HEREDOC

create_file(gemspec_file_path, content)
expect(target_ruby.version).to eq 2.7
end

it 'sets target_ruby from required_ruby_version from inclusive range' do
content =
<<-HEREDOC
Gem::Specification.new do |s|
s.name = 'test'
s.required_ruby_version = '>= 3.0.0'
s.licenses = ['MIT']
end
HEREDOC

create_file(gemspec_file_path, content)
expect(target_ruby.version).to eq 3.0
end

it 'sets default target_ruby from exclusive range' do
content =
<<-HEREDOC
Gem::Specification.new do |s|
s.name = 'test'
s.required_ruby_version = '< 3.0.0'
s.licenses = ['MIT']
end
HEREDOC

create_file(gemspec_file_path, content)
expect(target_ruby.version).to eq default_version
end
end

context 'when file contains `required_ruby_version` as an array' do
let(:base_path) { configuration.base_dir_for_path_parameters }
let(:gemspec_file_path) { File.join(base_path, 'example.gemspec') }

it 'sets target_ruby from the lowest value' do
content =
<<-HEREDOC
Gem::Specification.new do |s|
s.name = 'test'
s.required_ruby_version = ['<=2.7.4', '>=2.6.5']
s.licenses = ['MIT']
end
HEREDOC

create_file(gemspec_file_path, content)
expect(target_ruby.version).to eq 2.6
end

it 'sets target_ruby from required_ruby_version with inclusive range' do
content =
<<-HEREDOC
Gem::Specification.new do |s|
s.name = 'test'
s.required_ruby_version = ['<=2.7.4', '>2.6.5']
s.licenses = ['MIT']
end
HEREDOC

create_file(gemspec_file_path, content)
expect(target_ruby.version).to eq 2.7
end

it 'sets default target_ruby with all exclusive ranges' do
content =
<<-HEREDOC
Gem::Specification.new do |s|
s.name = 'test'
s.required_ruby_version = ['<2.7.4', '>2.6.5']
s.licenses = ['MIT']
end
HEREDOC

create_file(gemspec_file_path, content)
expect(target_ruby.version).to eq default_version
end
end

context 'when file does not contain `required_ruby_version`' do
let(:base_path) { configuration.base_dir_for_path_parameters }
let(:gemspec_file_path) { File.join(base_path, 'example.gemspec') }

it 'sets default target_ruby' do
content =
<<-HEREDOC
Gem::Specification.new do |s|
s.name = 'test'
s.platform = Gem::Platform::RUBY
s.licenses = ['MIT']
s.summary = 'test tool.'
end
HEREDOC

create_file(gemspec_file_path, content)
expect(target_ruby.version).to eq default_version
end
end
end
end

context 'when .ruby-version is in a parent directory' do
Expand Down

0 comments on commit 406d0e3

Please sign in to comment.