From 2e85cac913fe97fbd5bd4618e85c230684a41db5 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Mon, 15 Jun 2020 01:45:49 +0300 Subject: [PATCH] Add helpers allowing to check whether the method is a nonmutating operator method or a nonmutating method --- CHANGELOG.md | 1 + .../mixin/method_identifier_predicates.rb | 97 +++++++++++++- spec/rubocop/ast/send_node_spec.rb | 120 ++++++++++++++++++ 3 files changed, 214 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c5ceaa4..cdb0940f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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][]) diff --git a/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb b/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb index 64be7ebbb..19c2a469b 100644 --- a/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb +++ b/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb @@ -1,22 +1,69 @@ # frozen_string_literal: true +require 'set' + module RuboCop module AST # Common predicates for nodes that reference method identifiers: # `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. # @@ -33,6 +80,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 diff --git a/spec/rubocop/ast/send_node_spec.rb b/spec/rubocop/ast/send_node_spec.rb index bc1458773..4acd3d12a 100644 --- a/spec/rubocop/ast/send_node_spec.rb +++ b/spec/rubocop/ast/send_node_spec.rb @@ -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' }