From 02c8b445a1198942793de259f297d1200945f437 Mon Sep 17 00:00:00 2001 From: Bob Kuo Date: Mon, 11 Nov 2019 17:30:25 -0600 Subject: [PATCH] Autofix for Style/StructInheritance --- CHANGELOG.md | 2 + config/default.yml | 2 + lib/rubocop/ast/node/class_node.rb | 4 +- lib/rubocop/cop/style/struct_inheritance.rb | 44 +++++++++++++ manual/cops_style.md | 2 +- .../cop/style/struct_inheritance_spec.rb | 62 +++++++++++++++++++ 6 files changed, 113 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c76789651c..960a500183d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * [#7786](https://github.com/rubocop-hq/rubocop/pull/7786): Support Ruby 2.7's pattern match for `Layout/ElseAlignment` cop. ([@koic][]) * [#7784](https://github.com/rubocop-hq/rubocop/pull/7784): Support Ruby 2.7's numbered parameter for `Lint/SafeNavigationChain`. ([@koic][]) * [#7331](https://github.com/rubocop-hq/rubocop/issues/7331): Add `forbidden` option to `Style/ModuleFunction` cop. ([@weh][]) +* [#7499](https://github.com/rubocop-hq/rubocop/pull/7499): Autocorrection for `Style/StructInheritance`. ([@bubaflub][]) ### Bug fixes @@ -4411,3 +4412,4 @@ [@nikitasakov]: https://github.com/nikitasakov [@dmolesUC]: https://github.com/dmolesUC [@yuritomanek]: https://github.com/yuritomanek +[@bubaflub]: https://github.com/bubaflub diff --git a/config/default.yml b/config/default.yml index 13f10a22ae9..a9c817efd3d 100644 --- a/config/default.yml +++ b/config/default.yml @@ -3758,7 +3758,9 @@ Style/StructInheritance: Description: 'Checks for inheritance from Struct.new.' StyleGuide: '#no-extend-struct-new' Enabled: true + SafeAutoCorrect: false VersionAdded: '0.29' + VersionChanged: '0.81' Style/SymbolArray: Description: 'Use %i or %I for arrays of symbols.' diff --git a/lib/rubocop/ast/node/class_node.rb b/lib/rubocop/ast/node/class_node.rb index aa3623b92a6..a9769344a7f 100644 --- a/lib/rubocop/ast/node/class_node.rb +++ b/lib/rubocop/ast/node/class_node.rb @@ -6,9 +6,9 @@ module AST # node when the builder constructs the AST, making its methods available # to all `class` nodes within RuboCop. class ClassNode < Node - # The identifer for this `class` node. + # The identifier for this `class` node. # - # @return [Node] the identifer of the class + # @return [Node] the identifier of the class def identifier node_parts[0] end diff --git a/lib/rubocop/cop/style/struct_inheritance.rb b/lib/rubocop/cop/style/struct_inheritance.rb index f7ff18c886a..4b9563933f8 100644 --- a/lib/rubocop/cop/style/struct_inheritance.rb +++ b/lib/rubocop/cop/style/struct_inheritance.rb @@ -20,6 +20,8 @@ module Style # end # end class StructInheritance < Cop + include RangeHelp + MSG = "Don't extend an instance initialized by `Struct.new`. " \ 'Use a block to customize the struct.' @@ -33,6 +35,48 @@ def on_class(node) {(send (const nil? :Struct) :new ...) (block (send (const nil? :Struct) :new ...) ...)} PATTERN + + def autocorrect(class_node) + lambda do |corrector| + return nil if struct_definition_has_block?(class_node) + + remove_class_keyword(class_node, corrector) + + corrector.replace(class_node.loc.operator, '=') + + transform_class_body(class_node, corrector) + end + end + + private + + def struct_definition_has_block?(class_node) + class_node.parent_class.is_a?(RuboCop::AST::BlockNode) + end + + def remove_class_keyword(class_node, corrector) + corrector.remove( + range_with_surrounding_space( + range: class_node.loc.keyword, + side: :right + ) + ) + end + + def transform_class_body(class_node, corrector) + body = class_node.body + + if body + corrector.insert_after(class_node.parent_class.source_range, ' do') + else + corrector.remove( + range_with_surrounding_space( + range: class_node.loc.end, + side: :right + ) + ) + end + end end end end diff --git a/manual/cops_style.md b/manual/cops_style.md index cba3722fc7d..77261ab521b 100644 --- a/manual/cops_style.md +++ b/manual/cops_style.md @@ -6747,7 +6747,7 @@ This cop identifies places where `lstrip.rstrip` can be replaced by Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged --- | --- | --- | --- | --- -Enabled | Yes | No | 0.29 | - +Enabled | Yes | Yes (Unsafe) | 0.29 | 0.81 This cop checks for inheritance from Struct.new. diff --git a/spec/rubocop/cop/style/struct_inheritance_spec.rb b/spec/rubocop/cop/style/struct_inheritance_spec.rb index 81656563c9c..8f3b91bed6c 100644 --- a/spec/rubocop/cop/style/struct_inheritance_spec.rb +++ b/spec/rubocop/cop/style/struct_inheritance_spec.rb @@ -46,4 +46,66 @@ def age end RUBY end + + it 'autocorrects simple inline class with no block' do + new_source = autocorrect_source(<<~RUBY) + class Person < Struct.new(:first_name, :last_name) + end + RUBY + + expect(new_source).to eq(<<~RUBY) + Person = Struct.new(:first_name, :last_name) + RUBY + end + + it 'autocorrects a class with a body' do + new_source = autocorrect_source(<<~RUBY) + class Person < Struct.new(:first_name, :last_name) + def age + 42 + end + end + RUBY + + expect(new_source).to eq(<<~RUBY) + Person = Struct.new(:first_name, :last_name) do + def age + 42 + end + end + RUBY + end + + it 'ignores a class with an empty block and empty body' do + original_source = <<~RUBY + class Person < Struct.new(:first_name, :last_name) do end + end + RUBY + + expect(autocorrect_source(original_source)).to eq(original_source) + end + + it 'ignores a class with a body and an empty block' do + original_source = <<~RUBY + class Person < Struct.new(:first_name, :last_name) do end + def age + 42 + end + end + RUBY + + expect(autocorrect_source(original_source)).to eq(original_source) + end + + it 'ignores a class with a body and a block' do + original_source = <<~RUBY + class Person < Struct.new(:first_name, :last_name) do def baz; end end + def age + 42 + end + end + RUBY + + expect(autocorrect_source(original_source)).to eq(original_source) + end end