Skip to content

Commit

Permalink
[Fixes rubocop#22] AutoConstToSet builds Set variants of constants au…
Browse files Browse the repository at this point in the history
…tomatically
  • Loading branch information
marcandre committed Jul 7, 2020
1 parent c787af3 commit 3ac79b9
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 30 deletions.
1 change: 1 addition & 0 deletions lib/rubocop/ast.rb
Expand Up @@ -4,6 +4,7 @@
require 'forwardable'
require 'set'

require_relative 'ast/auto_const_to_set'
require_relative 'ast/node_pattern'
require_relative 'ast/sexp'
require_relative 'ast/node'
Expand Down
25 changes: 25 additions & 0 deletions lib/rubocop/ast/auto_const_to_set.rb
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module RuboCop
module AST
# If a module extends this, then `SOME_CONSTANT_SET` will be a set created
# automatically from `SOME_CONSTANT`
#
# class Foo
# extend AutoConstToSet
#
# WORDS = %w[hello world].freeze
# end
#
# Foo::WORDS_SET # => Set['hello', 'world']
module AutoConstToSet
def const_missing(name)
return super unless name =~ /(?<array_name>.*)_SET/

array = const_get(Regexp.last_match(:array_name))
raise TypeError, "Already a set!" if array.is_a?(Set)
const_set(name, array.to_set.freeze)
end
end
end
end
41 changes: 21 additions & 20 deletions lib/rubocop/ast/node.rb
Expand Up @@ -21,6 +21,7 @@ module AST
class Node < Parser::AST::Node # rubocop:disable Metrics/ClassLength
include RuboCop::AST::Sexp
extend NodePattern::Macros
extend AutoConstToSet

# <=> isn't included here, because it doesn't return a boolean.
COMPARISON_OPERATORS = %i[== === != <= >= > <].freeze
Expand Down Expand Up @@ -360,27 +361,27 @@ def empty_source?
PATTERN

def literal?
LITERALS.include?(type)
LITERALS_SET.include?(type)
end

def basic_literal?
BASIC_LITERALS.include?(type)
BASIC_LITERALS_SET.include?(type)
end

def truthy_literal?
TRUTHY_LITERALS.include?(type)
TRUTHY_LITERALS_SET.include?(type)
end

def falsey_literal?
FALSEY_LITERALS.include?(type)
FALSEY_LITERALS_SET.include?(type)
end

def mutable_literal?
MUTABLE_LITERALS.include?(type)
MUTABLE_LITERALS_SET.include?(type)
end

def immutable_literal?
IMMUTABLE_LITERALS.include?(type)
IMMUTABLE_LITERALS_SET.include?(type)
end

%i[literal basic_literal].each do |kind|
Expand All @@ -401,55 +402,55 @@ def immutable_literal?
end

def variable?
VARIABLES.include?(type)
VARIABLES_SET.include?(type)
end

def reference?
REFERENCES.include?(type)
REFERENCES_SET.include?(type)
end

def equals_asgn?
EQUALS_ASSIGNMENTS.include?(type)
EQUALS_ASSIGNMENTS_SET.include?(type)
end

def shorthand_asgn?
SHORTHAND_ASSIGNMENTS.include?(type)
SHORTHAND_ASSIGNMENTS_SET.include?(type)
end

def assignment?
ASSIGNMENTS.include?(type)
ASSIGNMENTS_SET.include?(type)
end

def basic_conditional?
BASIC_CONDITIONALS.include?(type)
BASIC_CONDITIONALS_SET.include?(type)
end

def conditional?
CONDITIONALS.include?(type)
CONDITIONALS_SET.include?(type)
end

def post_condition_loop?
POST_CONDITION_LOOP_TYPES.include?(type)
POST_CONDITION_LOOP_TYPES_SET.include?(type)
end

# Note: `loop { }` is a normal method call and thus not a loop keyword.
def loop_keyword?
LOOP_TYPES.include?(type)
LOOP_TYPES_SET.include?(type)
end

def keyword?
return true if special_keyword? || send_type? && prefix_not?
return false unless KEYWORDS.include?(type)
return false unless KEYWORDS_SET.include?(type)

!OPERATOR_KEYWORDS.include?(type) || loc.operator.is?(type.to_s)
!OPERATOR_KEYWORDS_SET.include?(type) || loc.operator.is?(type.to_s)
end

def special_keyword?
SPECIAL_KEYWORDS.include?(source)
SPECIAL_KEYWORDS_SET.include?(source)
end

def operator_keyword?
OPERATOR_KEYWORDS.include?(type)
OPERATOR_KEYWORDS_SET.include?(type)
end

def parenthesized_call?
Expand All @@ -469,7 +470,7 @@ def argument?
end

def argument_type?
ARGUMENT_TYPES.include?(type)
ARGUMENT_TYPES_SET.include?(type)
end

def boolean_type?
Expand Down
3 changes: 2 additions & 1 deletion lib/rubocop/ast/node/mixin/method_dispatch_node.rb
Expand Up @@ -8,6 +8,7 @@ module AST
module MethodDispatchNode
extend NodePattern::Macros
include MethodIdentifierPredicates
extend AutoConstToSet

ARITHMETIC_OPERATORS = %i[+ - * / % **].freeze
SPECIAL_MODIFIERS = %w[private protected].freeze
Expand Down Expand Up @@ -167,7 +168,7 @@ def block_literal?
# @return [Boolean] whether the dispatched method is an arithmetic
# operation
def arithmetic_operation?
ARITHMETIC_OPERATORS.include?(method_name)
ARITHMETIC_OPERATORS_SET.include?(method_name)
end

# Checks if this node is part of a chain of `def` modifiers.
Expand Down
20 changes: 11 additions & 9 deletions lib/rubocop/ast/node/mixin/method_identifier_predicates.rb
Expand Up @@ -7,6 +7,8 @@ module AST
#
# @note this mixin expects `#method_name` and `#receiver` to be implemented
module MethodIdentifierPredicates # rubocop:disable Metrics/ModuleLength
extend AutoConstToSet

ENUMERATOR_METHODS = %i[collect collect_concat detect downto each
find find_all find_index inject loop map!
map reduce reject reject! reverse_each select
Expand Down Expand Up @@ -75,49 +77,49 @@ def method?(name)
#
# @return [Boolean] whether the method is an operator
def operator_method?
OPERATOR_METHODS.include?(method_name)
OPERATOR_METHODS_SET.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)
NONMUTATING_BINARY_OPERATOR_METHODS_SET.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)
NONMUTATING_UNARY_OPERATOR_METHODS_SET.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)
NONMUTATING_OPERATOR_METHODS_SET.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)
NONMUTATING_ARRAY_METHODS_SET.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)
NONMUTATING_HASH_METHODS_SET.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)
NONMUTATING_STRING_METHODS_SET.include?(method_name)
end

# Checks whether the method is a comparison method.
Expand All @@ -138,15 +140,15 @@ def assignment_method?
#
# @return [Boolean] whether the method is an enumerator
def enumerator_method?
ENUMERATOR_METHODS.include?(method_name) ||
ENUMERATOR_METHODS_SET.include?(method_name) ||
method_name.to_s.start_with?('each_')
end

# Checks whether the method is an Enumerable method.
#
# @return [Boolean] whether the method is an Enumerable method
def enumerable_method?
ENUMERABLE_METHODS.include?(method_name)
ENUMERABLE_METHODS_SET.include?(method_name)
end

# Checks whether the method is a predicate method.
Expand Down
17 changes: 17 additions & 0 deletions spec/rubocop/ast/auto_const_to_set_spec.rb
@@ -0,0 +1,17 @@
# frozen_string_literal: true

RSpec.describe RuboCop::AST::AutoConstToSet do
let(:mod) do
Module.new do
extend RuboCop::AST::AutoConstToSet

WORDS = %w[hello world].freeze
end
end

it 'automatically creates set variants for array constants' do
expect(mod.constants).not_to include :WORDS_SET
expect(mod::WORDS_SET).to eq Set['hello', 'world']
expect { mod::WORDS_SET_SET }.to raise_error(TypeError)
end
end

0 comments on commit 3ac79b9

Please sign in to comment.