Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
1 parent
ea1d48f
commit 4dacec0
Showing
7 changed files
with
178 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Lint | ||
# This cop checks unexpected overrides of the `Struct` built-in methods | ||
# via `Struct.new`. | ||
# | ||
# @example | ||
# # bad | ||
# Bad = Struct.new(:members, :clone, :count) | ||
# b = Bad.new([], true, 1) | ||
# b.members #=> [] (overriding `Struct#members`) | ||
# b.clone #=> true (overriding `Object#clone`) | ||
# b.count #=> 1 (overriding `Enumerable#count`) | ||
# | ||
# # good | ||
# Good = Struct.new(:id, :name) | ||
# g = Good.new(1, "foo") | ||
# g.members #=> [:id, :name] | ||
# g.clone #=> #<struct Good id=1, name="foo"> | ||
# g.count #=> 2 | ||
# | ||
class StructNewOverride < Cop | ||
MSG = '`%<member_name>s` member overrides `Struct#%<method_name>s`' \ | ||
' and it may be unexpected.' | ||
|
||
STRUCT_METHOD_NAMES = Struct.instance_methods | ||
STRUCT_MEMBER_NAME_TYPES = %i[sym str].freeze | ||
|
||
def_node_matcher :struct_new, <<~PATTERN | ||
(send | ||
(const ${nil? cbase} :Struct) :new ...) | ||
PATTERN | ||
|
||
def on_send(node) | ||
return unless struct_new(node) do | ||
node.arguments.each_with_index do |arg, index| | ||
# Ignore if the first argument is a class name | ||
next if index.zero? && arg.str_type? | ||
|
||
# Ignore if the argument is not a member name | ||
next unless STRUCT_MEMBER_NAME_TYPES.include?(arg.type) | ||
|
||
member_name = arg.value | ||
|
||
next unless STRUCT_METHOD_NAMES.include?(member_name.to_sym) | ||
|
||
message = format(MSG, member_name: member_name.inspect, | ||
method_name: member_name.to_s) | ||
add_offense(arg, message: message) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Lint::StructNewOverride do | ||
subject(:cop) { described_class.new(config) } | ||
|
||
let(:config) { RuboCop::Config.new } | ||
|
||
it 'registers an offense using `Struct.new(symbol)`' do | ||
expect_offense(<<~RUBY) | ||
Bad = Struct.new(:members) | ||
^^^^^^^^ `:members` member overrides `Struct#members` and it may be unexpected. | ||
RUBY | ||
end | ||
|
||
it 'registers an offense using `::Struct.new(symbol)`' do | ||
expect_offense(<<~RUBY) | ||
Bad = ::Struct.new(:members) | ||
^^^^^^^^ `:members` member overrides `Struct#members` and it may be unexpected. | ||
RUBY | ||
end | ||
|
||
it 'registers an offense using `Struct.new(string, ...symbols)`' do | ||
expect_offense(<<~RUBY) | ||
Struct.new('Bad', :members, :name) | ||
^^^^^^^^ `:members` member overrides `Struct#members` and it may be unexpected. | ||
RUBY | ||
end | ||
|
||
it 'registers an offense using `Struct.new(...symbols)`' do | ||
expect_offense(<<~RUBY) | ||
Bad = Struct.new(:name, :members, :address) | ||
^^^^^^^^ `:members` member overrides `Struct#members` and it may be unexpected. | ||
RUBY | ||
end | ||
|
||
it 'registers an offense using `Struct.new(symbol, string)`' do | ||
expect_offense(<<~RUBY) | ||
Bad = Struct.new(:name, "members") | ||
^^^^^^^^^ `"members"` member overrides `Struct#members` and it may be unexpected. | ||
RUBY | ||
end | ||
|
||
it 'registers an offense using `Struct.new(...)` with a block' do | ||
expect_offense(<<~RUBY) | ||
Struct.new(:members) do | ||
^^^^^^^^ `:members` member overrides `Struct#members` and it may be unexpected. | ||
def members? | ||
!members.empty? | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense using `Struct.new(...)` with multiple overrides' do | ||
expect_offense(<<~RUBY) | ||
Struct.new(:members, :clone, :zip) | ||
^^^^ `:zip` member overrides `Struct#zip` and it may be unexpected. | ||
^^^^^^ `:clone` member overrides `Struct#clone` and it may be unexpected. | ||
^^^^^^^^ `:members` member overrides `Struct#members` and it may be unexpected. | ||
RUBY | ||
end | ||
|
||
it 'registers an offense using `Struct.new(...)` with an option argument' do | ||
expect_offense(<<~RUBY) | ||
Struct.new(:members, keyword_init: true) | ||
^^^^^^^^ `:members` member overrides `Struct#members` and it may be unexpected. | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense with no overrides' do | ||
expect_no_offenses(<<~RUBY) | ||
Good = Struct.new(:id, :name) | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense with an override within a given block' do | ||
expect_no_offenses(<<~RUBY) | ||
Good = Struct.new(:id, :name) do | ||
def members | ||
super.tap { |ret| pp "members: " + ret.to_s } | ||
end | ||
end | ||
RUBY | ||
end | ||
end |