Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autofix for Style/StructInheritance #7499

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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

Expand Down Expand Up @@ -4411,3 +4412,4 @@
[@nikitasakov]: https://github.com/nikitasakov
[@dmolesUC]: https://github.com/dmolesUC
[@yuritomanek]: https://github.com/yuritomanek
[@bubaflub]: https://github.com/bubaflub
2 changes: 2 additions & 0 deletions config/default.yml
Expand Up @@ -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.'
Expand Down
4 changes: 2 additions & 2 deletions lib/rubocop/ast/node/class_node.rb
Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions lib/rubocop/cop/style/struct_inheritance.rb
Expand Up @@ -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.'

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion manual/cops_style.md
Expand Up @@ -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.

Expand Down
62 changes: 62 additions & 0 deletions spec/rubocop/cop/style/struct_inheritance_spec.rb
Expand Up @@ -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