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

[Fix #4885] Fix false offense detected by `Style/MixinUsage` cop #4941

Conversation

Projects
None yet
5 participants
@koic
Copy link
Member

commented Oct 26, 2017

Fixes #4885.

This change makes the following modification based on Issue's report.

Using include inside block

The follwing does not register an offense.

Class.new do
  include IsNameValueType
end

and

RSpec::Matchers.define :do_something do
  include Mixin
end

Multiple definition classes in one

The follwing does not register an offense.

class C1
  include M
end

class C2
  include M
end

Nested module

The follwing register an offense.

include M::M2::M3

class C
end

Before submitting the PR make sure the following are checked:

  • Wrote good commit messages.
  • Commit message starts with [Fix #issue-number] (if the related issue exists).
  • Used the same coding conventions as the rest of the project.
  • Feature branch is up-to-date with master (if not - rebase it).
  • Squashed related commits together.
  • Added tests.
  • Added an entry to the Changelog if the new code introduces user-observable changes. See changelog entry format.
  • All tests(rake spec) are passing.
  • The new code doesn't generate RuboCop offenses that are checked by rake internal_investigation.
  • The PR relates to only one subject with a clear title
    and description in grammatically correct, complete sentences.
  • Updated cop documentation with rake generate_cops_documentation (required only when you've added a new cop or changed the configuration/documentation of an existing cop).
@@ -48,7 +48,7 @@ class MixinUsage < Cop

def_node_matcher :include_statement, <<-PATTERN
(send nil? ${:include :extend :prepend}
(const nil? _))
(const _ _))

This comment has been minimized.

Copy link
@pocke

pocke Oct 27, 2017

Member

We can replace (const _ _) with const

This comment has been minimized.

Copy link
@koic

koic Oct 27, 2017

Author Member

I fixed it. Thanks!

@koic koic force-pushed the koic:fix_false_offense_detected_by_style_mixin_usage branch from 89b89b9 to 8459e20 Oct 27, 2017

@@ -61,6 +61,8 @@ def on_send(node)
private

def top_level_node?(node)
return if node.class_type?

This comment has been minimized.

Copy link
@pocke

pocke Oct 27, 2017

Member

node.class_type? || node.module_type?

@@ -61,6 +61,8 @@ def on_send(node)
private

def top_level_node?(node)
return if node.class_type?

if node.parent.parent.nil?

This comment has been minimized.

Copy link
@pocke

pocke Oct 27, 2017

Member

[nitpick] node.parent.parent raises an error if code contains only a include A.

include A
$ rubocop --debug
An error occurred while Style/MixinUsage cop was inspecting /tmp/tmp.h0SVHsKwqw/test.rb:1:0.

1 error occurred:
An error occurred while Style/MixinUsage cop was inspecting /tmp/tmp.h0SVHsKwqw/test.rb:1:0.
Errors are usually caused by RuboCop bugs.
Please, report your problems to RuboCop's issue tracker.
Mention the following information in the issue report:
0.51.0 (using Parser 2.4.0.0, running on ruby 2.4.2 x86_64-linux)
For /tmp/tmp.h0SVHsKwqw: configuration from /home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/config/default.yml
Inheriting configuration from /home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/config/enabled.yml
Inheriting configuration from /home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/config/disabled.yml
Inspecting 1 file
Scanning /tmp/tmp.h0SVHsKwqw/test.rb
undefined method `parent' for nil:NilClass
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cop/style/mixin_usage.rb:66:in `top_level_node?'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cop/style/mixin_usage.rb:56:in `on_send'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cop/commissioner.rb:44:in `block (2 levels) in on_send'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cop/commissioner.rb:109:in `with_cop_error_handling'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cop/commissioner.rb:43:in `block in on_send'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cop/commissioner.rb:42:in `each'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cop/commissioner.rb:42:in `on_send'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/ast/traversal.rb:12:in `walk'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cop/commissioner.rb:60:in `investigate'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cop/team.rb:114:in `investigate'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cop/team.rb:102:in `offenses'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cop/team.rb:44:in `inspect_file'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:258:in `inspect_file'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:205:in `block in do_inspection_loop'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:237:in `block in iterate_until_no_changes'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:230:in `loop'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:230:in `iterate_until_no_changes'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:201:in `do_inspection_loop'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:111:in `block in file_offenses'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:121:in `file_offense_cache'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:109:in `file_offenses'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:100:in `process_file'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:78:in `block in each_inspected_file'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:75:in `each'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:75:in `reduce'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:75:in `each_inspected_file'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:67:in `inspect_files'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/runner.rb:39:in `run'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cli.rb:87:in `execute_runner'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/lib/rubocop/cli.rb:32:in `run'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/bin/rubocop:13:in `block in <top (required)>'
/usr/lib/ruby/2.4.0/benchmark.rb:308:in `realtime'
/home/pocke/.gem/ruby/2.4.0/gems/rubocop-0.51.0/bin/rubocop:12:in `<top (required)>'
/home/pocke/.gem/ruby/2.4.0/bin/rubocop:23:in `load'
/home/pocke/.gem/ruby/2.4.0/bin/rubocop:23:in `<main>'
.

1 file inspected, no offenses detected
Finished in 0.17337912297807634 seconds

This comment has been minimized.

Copy link
@koic

koic Oct 27, 2017

Author Member

Surely the consideration of this case was missing 💦

@@ -61,6 +61,8 @@ def on_send(node)
private

def top_level_node?(node)
return if node.class_type?

if node.parent.parent.nil?
node.sibling_index.zero?

This comment has been minimized.

Copy link
@pocke

pocke Oct 27, 2017

Member

I guess using sibling_index is not correct to check top level.
For example, this cop does not add offenses to the code.

def foo
end
include A # This include's `.parent.parent` is a nil, and the sibling index is not zero.
$ rubocop --only Style/MixinUsage
Inspecting 1 file
.

1 file inspected, no offenses detected

Maybe we can use SendNode#macro? instead.

https://github.com/bbatsov/rubocop/blob/8459e201400f852df7c8ac01dd2ad5e760c092d5/lib/rubocop/ast/node/mixin/method_dispatch_node.rb#L32-L41

This comment has been minimized.

Copy link
@koic

koic Oct 27, 2017

Author Member

It was this method that I really wanted🙏 This also can remove some unnecessary conditions.

@koic koic force-pushed the koic:fix_false_offense_detected_by_style_mixin_usage branch from 8459e20 to 93ed617 Oct 27, 2017

@@ -48,24 +48,22 @@ class MixinUsage < Cop

def_node_matcher :include_statement, <<-PATTERN
(send nil? ${:include :extend :prepend}
(const nil? _))
const)
PATTERN

def on_send(node)
return unless (statement = include_statement(node))

This comment has been minimized.

Copy link
@Drenmi

Drenmi Oct 27, 2017

Collaborator

We will normally use a block here, i.e.:

include_statement(node) do |statement|
  return unless implicit_style?(node)

  add_offense(node, message: format(MSG, statement: statement))
end

Ideally the node matcher would integrate the call to #implicit_style? through a funcall as well. 🙂

@@ -5,7 +5,7 @@
subject(:cop) { described_class.new(config) }

context 'include' do
it 'registers an offense when using outside class' do
it 'registers an offense when using outside class (used above)' do

This comment has been minimized.

Copy link
@Drenmi

Drenmi Oct 27, 2017

Collaborator

Could you please add a test case for this:

expect_no_offenses(<<-RUBY.strip_indent)
  Foo.include B
  class Bar; end
end

This comment has been minimized.

Copy link
@koic

koic Oct 30, 2017

Author Member

I added this test case.This is an important case 💦

else
top_level_node?(node.parent)
end
def implicit_style?(node)

This comment has been minimized.

Copy link
@Drenmi

Drenmi Oct 27, 2017

Collaborator

Not sure about the name #implicit_style?. I don't see how it being inside a context is "implicit" and in the top level "explicit"? 🙂

This comment has been minimized.

Copy link
@koic

koic Oct 30, 2017

Author Member

Indeed. It was named with an implicit assumption that 3 types of support will be done in the future 💦 I changed it to accepted_include? proposed by the following comment.

top_level_node?(node.parent)
end
def implicit_style?(node)
return true if node.parent.nil?

This comment has been minimized.

Copy link
@Drenmi

Drenmi Oct 27, 2017

Collaborator

I think:

def implicit_style?
  node.parent || !node.macro?
end

is slightly easier to understand. WDYT?

This comment has been minimized.

Copy link
@koic

koic Oct 30, 2017

Author Member

Right! I changed the method name to accepted_include?, so I reversed the condition, but I think it is easy to understand 😃

else
top_level_node?(node.parent)
end
def implicit_style?(node)

This comment has been minimized.

Copy link
@pocke

pocke Oct 27, 2017

Member

I think it should be recursive, and check class/module types.
For example: (but the example does not pass Class.new test case.)

def implicit_style?(node)
  parent = node.parent
  return true unless parent
  return false if parent.respond_to?(:macro?) && parent.macro?
  return false if parent.class_type? || parent.module_type?
  implicit_style?(parent)
end

Currently the cop does not add offence for the following code.

some_method do
  include A
end

This comment has been minimized.

Copy link
@Drenmi

Drenmi Oct 27, 2017

Collaborator

How about something like this?

def accepted_include?(node)
  return true unless node.macro?
 
  node.each_ancestor(:class, :module, :send, :block).any? do |ancestor|
    accepted_include_context?(ancestor)
  end
end

def accepted_include_context?(ancestor)
  ancestor.class_type? || ancestor.module_type? || dynamic_class_definition?(ancestor)
end

def dynamic_class_definition?(ancestor)
  # Check for Class.new, class_eval, etc.
end

This comment has been minimized.

Copy link
@koic

koic Oct 30, 2017

Author Member

I don't understand 2 things 😓

  1. Test cases that need recursive and class/module types
  2. About the following use case
some_method do
  include A
end

1. Test cases that need recursive and class/module types

Even without recursive and class/module types, there is no problem with existing bug cases (The test cases that currently exists passes). I could not make a test cases that fails without recursive and class/module types.

2. About the following use case

some_method do
  include A
end

For this use case, I'd like to support with another PR at another opportunity. First of all, in this PR, I'd like to solve the known issue of informing what is not a offense as a offense.

@koic koic force-pushed the koic:fix_false_offense_detected_by_style_mixin_usage branch 3 times, most recently from 8a44aa7 to f654290 Oct 30, 2017

@bbatsov

This comment has been minimized.

Copy link
Collaborator

commented Nov 4, 2017

Looks good to me. Rebase & squash.

[Fix #4885] Fix false offense detected by `Style/MixinUsage` cop
Fixes #4885.

This change makes the following modification based on Issue's report.

## Using `include` inside block

The follwing does not register an offense.

```ruby
Class.new do
  include IsNameValueType
end
```

and

```ruby
RSpec::Matchers.define :do_something do
  include Mixin
end
```

## Multiple definition classes in one

The follwing does not register an offense.

```ruby
class C1
  include M
end

class C2
  include M
end
```

## Nested module

The follwing register an offense.

```ruby
include M1::M2::M3

class C
end
```

@koic koic force-pushed the koic:fix_false_offense_detected_by_style_mixin_usage branch from f654290 to 6551f59 Nov 4, 2017

@koic

This comment has been minimized.

Copy link
Member Author

commented Nov 4, 2017

Thanks. I've done rebase and squash.

@bbatsov bbatsov merged commit 852177f into rubocop-hq:master Nov 5, 2017

1 of 2 checks passed

continuous-integration/appveyor/pr AppVeyor build failed
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@bbatsov

This comment has been minimized.

Copy link
Collaborator

commented Nov 5, 2017

👍

@koic koic deleted the koic:fix_false_offense_detected_by_style_mixin_usage branch Nov 6, 2017

@timoschilling

This comment has been minimized.

Copy link
Contributor

commented Nov 20, 2017

@bbatsov can you release a version containing this fix?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.