Skip to content

Commit 3ac79b9

Browse files
committed
[Fixes rubocop#22] AutoConstToSet builds Set variants of constants automatically
1 parent c787af3 commit 3ac79b9

File tree

6 files changed

+77
-30
lines changed

6 files changed

+77
-30
lines changed

lib/rubocop/ast.rb

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require 'forwardable'
55
require 'set'
66

7+
require_relative 'ast/auto_const_to_set'
78
require_relative 'ast/node_pattern'
89
require_relative 'ast/sexp'
910
require_relative 'ast/node'

lib/rubocop/ast/auto_const_to_set.rb

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module AST
5+
# If a module extends this, then `SOME_CONSTANT_SET` will be a set created
6+
# automatically from `SOME_CONSTANT`
7+
#
8+
# class Foo
9+
# extend AutoConstToSet
10+
#
11+
# WORDS = %w[hello world].freeze
12+
# end
13+
#
14+
# Foo::WORDS_SET # => Set['hello', 'world']
15+
module AutoConstToSet
16+
def const_missing(name)
17+
return super unless name =~ /(?<array_name>.*)_SET/
18+
19+
array = const_get(Regexp.last_match(:array_name))
20+
raise TypeError, "Already a set!" if array.is_a?(Set)
21+
const_set(name, array.to_set.freeze)
22+
end
23+
end
24+
end
25+
end

lib/rubocop/ast/node.rb

+21-20
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module AST
2121
class Node < Parser::AST::Node # rubocop:disable Metrics/ClassLength
2222
include RuboCop::AST::Sexp
2323
extend NodePattern::Macros
24+
extend AutoConstToSet
2425

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

362363
def literal?
363-
LITERALS.include?(type)
364+
LITERALS_SET.include?(type)
364365
end
365366

366367
def basic_literal?
367-
BASIC_LITERALS.include?(type)
368+
BASIC_LITERALS_SET.include?(type)
368369
end
369370

370371
def truthy_literal?
371-
TRUTHY_LITERALS.include?(type)
372+
TRUTHY_LITERALS_SET.include?(type)
372373
end
373374

374375
def falsey_literal?
375-
FALSEY_LITERALS.include?(type)
376+
FALSEY_LITERALS_SET.include?(type)
376377
end
377378

378379
def mutable_literal?
379-
MUTABLE_LITERALS.include?(type)
380+
MUTABLE_LITERALS_SET.include?(type)
380381
end
381382

382383
def immutable_literal?
383-
IMMUTABLE_LITERALS.include?(type)
384+
IMMUTABLE_LITERALS_SET.include?(type)
384385
end
385386

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

403404
def variable?
404-
VARIABLES.include?(type)
405+
VARIABLES_SET.include?(type)
405406
end
406407

407408
def reference?
408-
REFERENCES.include?(type)
409+
REFERENCES_SET.include?(type)
409410
end
410411

411412
def equals_asgn?
412-
EQUALS_ASSIGNMENTS.include?(type)
413+
EQUALS_ASSIGNMENTS_SET.include?(type)
413414
end
414415

415416
def shorthand_asgn?
416-
SHORTHAND_ASSIGNMENTS.include?(type)
417+
SHORTHAND_ASSIGNMENTS_SET.include?(type)
417418
end
418419

419420
def assignment?
420-
ASSIGNMENTS.include?(type)
421+
ASSIGNMENTS_SET.include?(type)
421422
end
422423

423424
def basic_conditional?
424-
BASIC_CONDITIONALS.include?(type)
425+
BASIC_CONDITIONALS_SET.include?(type)
425426
end
426427

427428
def conditional?
428-
CONDITIONALS.include?(type)
429+
CONDITIONALS_SET.include?(type)
429430
end
430431

431432
def post_condition_loop?
432-
POST_CONDITION_LOOP_TYPES.include?(type)
433+
POST_CONDITION_LOOP_TYPES_SET.include?(type)
433434
end
434435

435436
# Note: `loop { }` is a normal method call and thus not a loop keyword.
436437
def loop_keyword?
437-
LOOP_TYPES.include?(type)
438+
LOOP_TYPES_SET.include?(type)
438439
end
439440

440441
def keyword?
441442
return true if special_keyword? || send_type? && prefix_not?
442-
return false unless KEYWORDS.include?(type)
443+
return false unless KEYWORDS_SET.include?(type)
443444

444-
!OPERATOR_KEYWORDS.include?(type) || loc.operator.is?(type.to_s)
445+
!OPERATOR_KEYWORDS_SET.include?(type) || loc.operator.is?(type.to_s)
445446
end
446447

447448
def special_keyword?
448-
SPECIAL_KEYWORDS.include?(source)
449+
SPECIAL_KEYWORDS_SET.include?(source)
449450
end
450451

451452
def operator_keyword?
452-
OPERATOR_KEYWORDS.include?(type)
453+
OPERATOR_KEYWORDS_SET.include?(type)
453454
end
454455

455456
def parenthesized_call?
@@ -469,7 +470,7 @@ def argument?
469470
end
470471

471472
def argument_type?
472-
ARGUMENT_TYPES.include?(type)
473+
ARGUMENT_TYPES_SET.include?(type)
473474
end
474475

475476
def boolean_type?

lib/rubocop/ast/node/mixin/method_dispatch_node.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module AST
88
module MethodDispatchNode
99
extend NodePattern::Macros
1010
include MethodIdentifierPredicates
11+
extend AutoConstToSet
1112

1213
ARITHMETIC_OPERATORS = %i[+ - * / % **].freeze
1314
SPECIAL_MODIFIERS = %w[private protected].freeze
@@ -167,7 +168,7 @@ def block_literal?
167168
# @return [Boolean] whether the dispatched method is an arithmetic
168169
# operation
169170
def arithmetic_operation?
170-
ARITHMETIC_OPERATORS.include?(method_name)
171+
ARITHMETIC_OPERATORS_SET.include?(method_name)
171172
end
172173

173174
# Checks if this node is part of a chain of `def` modifiers.

lib/rubocop/ast/node/mixin/method_identifier_predicates.rb

+11-9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ module AST
77
#
88
# @note this mixin expects `#method_name` and `#receiver` to be implemented
99
module MethodIdentifierPredicates # rubocop:disable Metrics/ModuleLength
10+
extend AutoConstToSet
11+
1012
ENUMERATOR_METHODS = %i[collect collect_concat detect downto each
1113
find find_all find_index inject loop map!
1214
map reduce reject reject! reverse_each select
@@ -75,49 +77,49 @@ def method?(name)
7577
#
7678
# @return [Boolean] whether the method is an operator
7779
def operator_method?
78-
OPERATOR_METHODS.include?(method_name)
80+
OPERATOR_METHODS_SET.include?(method_name)
7981
end
8082

8183
# Checks whether the method is a nonmutating binary operator method.
8284
#
8385
# @return [Boolean] whether the method is a nonmutating binary operator method
8486
def nonmutating_binary_operator_method?
85-
NONMUTATING_BINARY_OPERATOR_METHODS.include?(method_name)
87+
NONMUTATING_BINARY_OPERATOR_METHODS_SET.include?(method_name)
8688
end
8789

8890
# Checks whether the method is a nonmutating unary operator method.
8991
#
9092
# @return [Boolean] whether the method is a nonmutating unary operator method
9193
def nonmutating_unary_operator_method?
92-
NONMUTATING_UNARY_OPERATOR_METHODS.include?(method_name)
94+
NONMUTATING_UNARY_OPERATOR_METHODS_SET.include?(method_name)
9395
end
9496

9597
# Checks whether the method is a nonmutating operator method.
9698
#
9799
# @return [Boolean] whether the method is a nonmutating operator method
98100
def nonmutating_operator_method?
99-
NONMUTATING_OPERATOR_METHODS.include?(method_name)
101+
NONMUTATING_OPERATOR_METHODS_SET.include?(method_name)
100102
end
101103

102104
# Checks whether the method is a nonmutating Array method.
103105
#
104106
# @return [Boolean] whether the method is a nonmutating Array method
105107
def nonmutating_array_method?
106-
NONMUTATING_ARRAY_METHODS.include?(method_name)
108+
NONMUTATING_ARRAY_METHODS_SET.include?(method_name)
107109
end
108110

109111
# Checks whether the method is a nonmutating Hash method.
110112
#
111113
# @return [Boolean] whether the method is a nonmutating Hash method
112114
def nonmutating_hash_method?
113-
NONMUTATING_HASH_METHODS.include?(method_name)
115+
NONMUTATING_HASH_METHODS_SET.include?(method_name)
114116
end
115117

116118
# Checks whether the method is a nonmutating String method.
117119
#
118120
# @return [Boolean] whether the method is a nonmutating String method
119121
def nonmutating_string_method?
120-
NONMUTATING_STRING_METHODS.include?(method_name)
122+
NONMUTATING_STRING_METHODS_SET.include?(method_name)
121123
end
122124

123125
# Checks whether the method is a comparison method.
@@ -138,15 +140,15 @@ def assignment_method?
138140
#
139141
# @return [Boolean] whether the method is an enumerator
140142
def enumerator_method?
141-
ENUMERATOR_METHODS.include?(method_name) ||
143+
ENUMERATOR_METHODS_SET.include?(method_name) ||
142144
method_name.to_s.start_with?('each_')
143145
end
144146

145147
# Checks whether the method is an Enumerable method.
146148
#
147149
# @return [Boolean] whether the method is an Enumerable method
148150
def enumerable_method?
149-
ENUMERABLE_METHODS.include?(method_name)
151+
ENUMERABLE_METHODS_SET.include?(method_name)
150152
end
151153

152154
# Checks whether the method is a predicate method.
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::AST::AutoConstToSet do
4+
let(:mod) do
5+
Module.new do
6+
extend RuboCop::AST::AutoConstToSet
7+
8+
WORDS = %w[hello world].freeze
9+
end
10+
end
11+
12+
it 'automatically creates set variants for array constants' do
13+
expect(mod.constants).not_to include :WORDS_SET
14+
expect(mod::WORDS_SET).to eq Set['hello', 'world']
15+
expect { mod::WORDS_SET_SET }.to raise_error(TypeError)
16+
end
17+
end

0 commit comments

Comments
 (0)