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

Add helpers allowing to check whether the method is a nonmutating operator method or a nonmutating method #38

Merged
merged 1 commit into from Jun 19, 2020
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.md
Expand Up @@ -4,6 +4,7 @@

### New features

* [#38](https://github.com/rubocop-hq/rubocop-ast/pull/38): Add helpers allowing to check whether the method is a nonmutating operator method or a nonmutating method of several core classes. ([@fatkodima][])
* [#37](https://github.com/rubocop-hq/rubocop-ast/pull/37): Add `enumerable_method?` for `MethodIdentifierPredicates`. ([@fatkodima][])
* [#4](https://github.com/rubocop-hq/rubocop-ast/issues/4): Add `interpolation?` for `RegexpNode`. ([@tejasbubane][])
* [#20](https://github.com/rubocop-hq/rubocop-ast/pull/20): Add option predicates for `RegexpNode`. ([@owst][])
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/ast.rb
Expand Up @@ -2,6 +2,7 @@

require 'parser'
require 'forwardable'
require 'set'

require_relative 'ast/node_pattern'
require_relative 'ast/sexp'
Expand Down
95 changes: 91 additions & 4 deletions lib/rubocop/ast/node/mixin/method_identifier_predicates.rb
Expand Up @@ -6,17 +6,62 @@ module AST
# `send`, `csend`, `def`, `defs`, `super`, `zsuper`
#
# @note this mixin expects `#method_name` and `#receiver` to be implemented
module MethodIdentifierPredicates
module MethodIdentifierPredicates # rubocop:disable Metrics/ModuleLength
ENUMERATOR_METHODS = %i[collect collect_concat detect downto each
find find_all find_index inject loop map!
map reduce reject reject! reverse_each select
select! times upto].freeze
select! times upto].to_set.freeze

ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).freeze
ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).to_set.freeze

# http://phrogz.net/programmingruby/language.html#table_18.4
OPERATOR_METHODS = %i[| ^ & <=> == === =~ > >= < <= << >> + - * /
% ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].freeze
% ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].to_set.freeze

NONMUTATING_BINARY_OPERATOR_METHODS = %i[* / % + - == === != < > <= >= <=>].to_set.freeze
NONMUTATING_UNARY_OPERATOR_METHODS = %i[+@ -@ ~ !].to_set.freeze
NONMUTATING_OPERATOR_METHODS = (NONMUTATING_BINARY_OPERATOR_METHODS +
NONMUTATING_UNARY_OPERATOR_METHODS).freeze

NONMUTATING_ARRAY_METHODS = %i[
all? any? assoc at bsearch bsearch_index collect
combination compact count cycle deconstruct difference
dig drop drop_while each each_index empty? eql?
fetch filter find_index first flatten hash
include? index inspect intersection join
last length map max min minmax none? one? pack
permutation product rassoc reject
repeated_combination repeated_permutation reverse
reverse_each rindex rotate sample select shuffle
size slice sort sum take take_while
to_a to_ary to_h to_s transpose union uniq
values_at zip |
].to_set.freeze

NONMUTATING_HASH_METHODS = %i[
any? assoc compact dig each each_key each_pair
each_value empty? eql? fetch fetch_values filter
flatten has_key? has_value? hash include? inspect
invert key key? keys? length member? merge rassoc
rehash reject select size slice to_a to_h to_hash
to_proc to_s transform_keys transform_values value?
values values_at
].to_set.freeze

NONMUTATING_STRING_METHODS = %i[
ascii_only? b bytes bytesize byteslice capitalize
casecmp casecmp? center chars chomp chop chr codepoints
count crypt delete delete_prefix delete_suffix
downcase dump each_byte each_char each_codepoint
each_grapheme_cluster each_line empty? encode encoding
end_with? eql? getbyte grapheme_clusters gsub hash
hex include index inspect intern length lines ljust lstrip
match match? next oct ord partition reverse rindex rjust
rpartition rstrip scan scrub size slice squeeze start_with?
strip sub succ sum swapcase to_a to_c to_f to_i to_r to_s
to_str to_sym tr tr_s unicode_normalize unicode_normalized?
unpack unpack1 upcase upto valid_encoding?
].to_set.freeze

# Checks whether the method name matches the argument.
#
Expand All @@ -33,6 +78,48 @@ def operator_method?
OPERATOR_METHODS.include?(method_name)
end

# Checks whether the method is a nonmutating binary operator method.
#
# @return [Boolean] whether the method is a nonmutating binary operator method
def nonmutating_binary_operator_method?
NONMUTATING_BINARY_OPERATOR_METHODS.include?(method_name)
end

# Checks whether the method is a nonmutating unary operator method.
#
# @return [Boolean] whether the method is a nonmutating unary operator method
def nonmutating_unary_operator_method?
NONMUTATING_UNARY_OPERATOR_METHODS.include?(method_name)
end

# Checks whether the method is a nonmutating operator method.
#
# @return [Boolean] whether the method is a nonmutating operator method
def nonmutating_operator_method?
NONMUTATING_OPERATOR_METHODS.include?(method_name)
end

# Checks whether the method is a nonmutating Array method.
#
# @return [Boolean] whether the method is a nonmutating Array method
def nonmutating_array_method?
NONMUTATING_ARRAY_METHODS.include?(method_name)
end

# Checks whether the method is a nonmutating Hash method.
#
# @return [Boolean] whether the method is a nonmutating Hash method
def nonmutating_hash_method?
NONMUTATING_HASH_METHODS.include?(method_name)
end

# Checks whether the method is a nonmutating String method.
#
# @return [Boolean] whether the method is a nonmutating String method
def nonmutating_string_method?
NONMUTATING_STRING_METHODS.include?(method_name)
end

# Checks whether the method is a comparison method.
#
# @return [Boolean] whether the method is a comparison
Expand Down
120 changes: 120 additions & 0 deletions spec/rubocop/ast/send_node_spec.rb
Expand Up @@ -736,6 +736,126 @@ module Foo
end
end

describe '#nonmutating_binary_operator_method?' do
context 'with a nonmutating binary operator method' do
let(:source) { 'foo + bar' }

it { expect(send_node.nonmutating_binary_operator_method?).to be_truthy }
fatkodima marked this conversation as resolved.
Show resolved Hide resolved
end

context 'with a mutating binary operator method' do
let(:source) { 'foo << bar' }

it { expect(send_node.nonmutating_binary_operator_method?).to be_falsey }
end

context 'with a regular method' do
let(:source) { 'foo.bar(:baz)' }

it { expect(send_node.nonmutating_binary_operator_method?).to be_falsey }
end
end

describe '#nonmutating_unary_operator_method?' do
context 'with a nonmutating unary operator method' do
let(:source) { '!foo' }

it { expect(send_node.nonmutating_unary_operator_method?).to be_truthy }
end

context 'with a regular method' do
let(:source) { 'foo.bar(:baz)' }

it { expect(send_node.nonmutating_unary_operator_method?).to be_falsey }
end
end

describe '#nonmutating_operator_method?' do
context 'with a nonmutating binary operator method' do
let(:source) { 'foo + bar' }

it { expect(send_node.nonmutating_operator_method?).to be_truthy }
end

context 'with a nonmutating unary operator method' do
let(:source) { '!foo' }

it { expect(send_node.nonmutating_operator_method?).to be_truthy }
end

context 'with a mutating binary operator method' do
let(:source) { 'foo << bar' }

it { expect(send_node.nonmutating_operator_method?).to be_falsey }
end

context 'with a regular method' do
let(:source) { 'foo.bar(:baz)' }

it { expect(send_node.nonmutating_operator_method?).to be_falsey }
end
end

describe '#nonmutating_array_method?' do
context 'with a nonmutating Array method' do
let(:source) { 'array.reverse' }

it { expect(send_node.nonmutating_array_method?).to be_truthy }
end

context 'with a mutating Array method' do
let(:source) { 'array.push(foo)' }

it { expect(send_node.nonmutating_array_method?).to be_falsey }
end

context 'with a regular method' do
let(:source) { 'foo.bar(:baz)' }

it { expect(send_node.nonmutating_array_method?).to be_falsey }
end
end

describe '#nonmutating_hash_method?' do
context 'with a nonmutating Hash method' do
let(:source) { 'hash.slice(:foo, :bar)' }

it { expect(send_node.nonmutating_hash_method?).to be_truthy }
end

context 'with a mutating Hash method' do
let(:source) { 'hash.delete(:foo)' }

it { expect(send_node.nonmutating_hash_method?).to be_falsey }
end

context 'with a regular method' do
let(:source) { 'foo.bar(:baz)' }

it { expect(send_node.nonmutating_hash_method?).to be_falsey }
end
end

describe '#nonmutating_string_method?' do
context 'with a nonmutating String method' do
let(:source) { 'string.squeeze' }

it { expect(send_node.nonmutating_string_method?).to be_truthy }
end

context 'with a mutating String method' do
let(:source) { 'string.lstrip!' }

it { expect(send_node.nonmutating_string_method?).to be_falsey }
end

context 'with a regular method' do
let(:source) { 'foo.bar(:baz)' }

it { expect(send_node.nonmutating_string_method?).to be_falsey }
end
end

describe '#comparison_method?' do
context 'with a comparison method' do
let(:source) { 'foo.bar >= :baz' }
Expand Down