diff --git a/README.md b/README.md index aea632e..80a6999 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Name | Default value | Configurable values --- | --- | --- EnforcedStyle | `'alphabetical'` | `'alphabetical'` IgnoredMethods | `['initialize']` | Array +MethodQualifiers | `[]` | Array Signature | `nil` | `'sorbet'`, `nil` #### Example @@ -84,7 +85,10 @@ Signature | `nil` | `'sorbet'`, `nil` # .rubocop.yml Layout/OrderedMethods: EnforcedStyle: alphabetical - IgnoredMethods: initialize + IgnoredMethods: + - initialize + MethodQualifiers: + - memoize Signature: sorbet ``` @@ -136,6 +140,16 @@ protected :instance_a public :instance_a ``` +#### Method qualifiers +Some gems (like `memery`, `memoist`, etc.) provide a DSL that modifies the method (e.g. for memoization). +Those DSL methods can be added to the `MethodQualifiers` configuration, and they will be respected. + +E.g. the following source can be correctly ordered: +```ruby +def b; end; +memoize def a;end +``` + #### Method signatures Support for (Sorbet) method signatures was added to the corrector by diff --git a/lib/rubocop/cop/layout/ordered_methods.rb b/lib/rubocop/cop/layout/ordered_methods.rb index acd6b75..d402b1a 100644 --- a/lib/rubocop/cop/layout/ordered_methods.rb +++ b/lib/rubocop/cop/layout/ordered_methods.rb @@ -35,8 +35,8 @@ class OrderedMethods < Cop include RangeHelp COMPARISONS = { - 'alphabetical' => lambda do |left_method, right_method| - (left_method.method_name <=> right_method.method_name) != 1 + 'alphabetical' => lambda do |left_node, right_node| + (method_name(left_node) <=> method_name(right_node)) != 1 end }.freeze ERR_INVALID_COMPARISON = 'Invalid "Comparison" config for ' \ @@ -104,10 +104,7 @@ def consecutive_methods(nodes) def filter_relevant_nodes(nodes) nodes.select do |node| - ( - (node.defs_type? || node.def_type?) && - !ignored_method?(node.method_name) - ) || (node.send_type? && node.bare_access_modifier?) + relevant_node?(node) || (node.send_type? && qualifier_macro?(node)) end end @@ -133,12 +130,29 @@ def group_methods_by_access_modifier(nodes) end end + def self.method_name(node) + return node.method_name unless node.send_type? + + node.first_argument.method_name + end + def ordered?(left_method, right_method) comparison = COMPARISONS[cop_config['EnforcedStyle']] raise Error, ERR_INVALID_COMPARISON if comparison.nil? comparison.call(left_method, right_method) end + + def relevant_node?(node) + (node.defs_type? || node.def_type?) && !ignored_method?(node.method_name) + end + + def qualifier_macro?(node) + return true if node.bare_access_modifier? + + cop_config['MethodQualifiers'].to_a.include?(node.method_name.to_s) && + relevant_node?(node.first_argument) + end end end end diff --git a/lib/rubocop/cop/qualifier_node_matchers.rb b/lib/rubocop/cop/qualifier_node_matchers.rb index 97b9162..1fbe1c4 100644 --- a/lib/rubocop/cop/qualifier_node_matchers.rb +++ b/lib/rubocop/cop/qualifier_node_matchers.rb @@ -20,9 +20,16 @@ module QualifierNodeMatchers def_node_matcher :alias_method?, '(send nil? {:alias_method} ... (sym $_method_name))' def_node_matcher :qualifier?, <<-PATTERN - (send nil? {#{QUALIFIERS.map(&:inspect).join(' ')}} - ... (sym $_method_name)) + (send nil? #method_qualifier? ... (sym $_method_name)) PATTERN + + def method_qualifier?(name) + qualifiers.include?(name) + end + + def qualifiers + @qualifiers ||= QUALIFIERS + @cop_config['MethodQualifiers'].to_a.map(&:to_sym) + end end end end diff --git a/spec/rubocop/cop/layout/ordered_methods_spec.rb b/spec/rubocop/cop/layout/ordered_methods_spec.rb index 37ab9bc..75d2e84 100644 --- a/spec/rubocop/cop/layout/ordered_methods_spec.rb +++ b/spec/rubocop/cop/layout/ordered_methods_spec.rb @@ -375,6 +375,46 @@ def b; end end end + context 'with configured method qualiifers' do + let(:cop_config) { {'MethodQualifiers' => %w[memoize]} } + + it 'recognizes the qualifier as a class method as well' do + expect_offense(<<~RUBY) + class Foo + def b; end + def a; end + ^^^^^^^^^^ Methods should be sorted in alphabetical order. + memoize :a + end + RUBY + + expect_correction(<<~RUBY) + class Foo + def a; end + memoize :a + def b; end + end + RUBY + end + + it 'registers an offense when methods are not in alphabetical order' do + expect_offense(<<~RUBY) + class Foo + def b; end + memoize def a; end + ^^^^^^^^^^^^^^^^^^ Methods should be sorted in alphabetical order. + end + RUBY + + expect_correction(<<~RUBY) + class Foo + memoize def a; end + def b; end + end + RUBY + end + end + # We integration-test our cop via `::RuboCop::CLI`. This is quite close to an # end-to-end test, with the normal pros and cons that entails. We exercise # more of our code, but our assertions are more fragile, for example asserting