Skip to content

Commit

Permalink
Add helpers allowing to check whether the method is a nonmutating ope…
Browse files Browse the repository at this point in the history
…rator method or a nonmutating method
  • Loading branch information
fatkodima committed Jun 15, 2020
1 parent 0a9c2b8 commit 0ca043e
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 }
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

0 comments on commit 0ca043e

Please sign in to comment.