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

Detect repeated descriptions in example groups #259

Merged
merged 2 commits into from
Dec 28, 2016

Conversation

backus
Copy link
Collaborator

@backus backus commented Dec 26, 2016

No description provided.

@backus backus force-pushed the feature/RepeatedDescription branch 3 times, most recently from 86daf54 to 3f41d9c Compare December 26, 2016 19:46
@backus
Copy link
Collaborator Author

backus commented Dec 26, 2016

@bquorning @andyw8 this is reviewable

Copy link
Collaborator

@bquorning bquorning left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this on my project, and…

1199 files inspected, 56 offenses detected

So far, I haven’t found any false positives.

describe RuboCop::Cop::RSpec::RepeatedDescription do
subject(:cop) { described_class.new }

it 'registers an offense for repeated descriptions' do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps also add the scenario where the same description is used in different context blocks.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also test a scenario where two examples with the same description are separated by a scope change. I’m thinking e.g.

it "does x" do
end

context "something else" do
  it "works fine" do
  end
end

it "does x" do
end

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good catch. I actually thought I had more than one test here but I guess not. I'll add a few more

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bquorning the tests should be sufficient now

def find_hooks(node, &block)
return if scope_change?(node)

example(node, &block)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not sure what this method call does. It looks like it matches the node and block against a pattern, but the return value is neither stored nor returned.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about that. I added some doc comments and I also renamed the misleading name... It was a holdover from code I borrowed from #254. Didn't realize I forgot to change the name sorry about that

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method does a recursive search until it hits a scope changing node. RuboCop's node pattern methods will yield (unless you defined a predicate method ending in ? I believe) if you give it a block and the provided node matches. By writing

def_node_pattern :desired_node, '...'
def_node_pattern :node_should_stop_recursion?, '...'

def desired_nodes(node, &block)
  node.each_child_node { |child| desired_node_search(node, &block) }
end

def desired_node_search(node, &block)
  return unless node_should_stop_recursion?(node)

  desired_node(node)
  desired_nodes(node)
end

you get a recursive node search that stops when it hits a match for node_should_stop_recursion?. The end result is that I can write

desired_nodes(node) do |match|
  add_offense(match, ...)
end

which I like. I follow this pattern in a handful of cops

.reject { |args, _| args.empty? }
.values
.reject(&:one?)
.flatten
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be more readable using local variables?

hooks_with_arguments = hooks_in_group(node).group_by(&:method_args).reject { |args, _| args.empty? }
hooks_with_identical_arguments = hooks_with_arguments.values.reject(&:one?)
hooks_with_identical_arguments.flatten

If I got the names wrong, it’s because I didn’t understand the code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to clarify with inline comments. I prefer a functional style like this when possible but only if the code is still clear. Is the updated code clear?

backus added a commit to backus/rubocop that referenced this pull request Dec 26, 2016
Found these offending spec descriptions while testing out
rubocop/rubocop-rspec#259. Searching for duplicate spec descriptions
revealed a few categories of issues:

1. Examples with repeated descriptions that tested different cases
   but didn't describe the specific difference. For example:

    ```ruby
    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{}"')
      expect(new_source).to eq('"this is the "')
    end

    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{ }"')
      expect(new_source).to eq('"this is the "')
    end
    ```

2. Examples with repeated descriptions where one or more descriptions
   were wrong. For example:

    ```ruby
    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(a..10).each {}')
      expect(cop.offenses).to be_empty
    end

    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(0..b).each {}')
      expect(cop.offenses).to be_empty
    end
    ```

3. Redundant examples where the spec and its corresponding description
   showed up twice in the same context. For example:

    ```ruby
    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end

    # ...

    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end
    ```
@backus backus force-pushed the feature/RepeatedDescription branch 5 times, most recently from 6b75167 to b86bd70 Compare December 26, 2016 22:14
@backus
Copy link
Collaborator Author

backus commented Dec 26, 2016

@bquorning ready for another pass.

def_node_matcher :scope_change?,
(ExampleGroups::ALL + SharedGroups::ALL).block_pattern

MSG = "Don't repeat descriptions withhin an example group.".freeze
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in "withhin".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch thanks. Fixed now

@andyw8
Copy link
Collaborator

andyw8 commented Dec 27, 2016

Ran it over a large codebase and it caught 74 issues, all appear to be genuine. Nice work on this!

bbatsov pushed a commit to rubocop/rubocop that referenced this pull request Dec 27, 2016
Found these offending spec descriptions while testing out
rubocop/rubocop-rspec#259. Searching for duplicate spec descriptions
revealed a few categories of issues:

1. Examples with repeated descriptions that tested different cases
   but didn't describe the specific difference. For example:

    ```ruby
    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{}"')
      expect(new_source).to eq('"this is the "')
    end

    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{ }"')
      expect(new_source).to eq('"this is the "')
    end
    ```

2. Examples with repeated descriptions where one or more descriptions
   were wrong. For example:

    ```ruby
    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(a..10).each {}')
      expect(cop.offenses).to be_empty
    end

    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(0..b).each {}')
      expect(cop.offenses).to be_empty
    end
    ```

3. Redundant examples where the spec and its corresponding description
   showed up twice in the same context. For example:

    ```ruby
    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end

    # ...

    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end
    ```
@bquorning
Copy link
Collaborator

This looks good. Add a CHANGELOG entry (and perhaps copy it to the PR description for future reference), and it’s a 👍 from me.

@backus
Copy link
Collaborator Author

backus commented Dec 28, 2016

@bquorning whoops good catch on the changelog. Added. Will merge when build is green 😄

@backus backus merged commit 853af10 into master Dec 28, 2016
@backus backus deleted the feature/RepeatedDescription branch December 28, 2016 02:22
markburns pushed a commit to markburns/rubocop-rails-ddd that referenced this pull request Nov 7, 2017
Detect repeated descriptions in example groups
koic pushed a commit to koic/rubocop-performance that referenced this pull request Oct 12, 2018
Found these offending spec descriptions while testing out
rubocop/rubocop-rspec#259. Searching for duplicate spec descriptions
revealed a few categories of issues:

1. Examples with repeated descriptions that tested different cases
   but didn't describe the specific difference. For example:

    ```ruby
    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{}"')
      expect(new_source).to eq('"this is the "')
    end

    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{ }"')
      expect(new_source).to eq('"this is the "')
    end
    ```

2. Examples with repeated descriptions where one or more descriptions
   were wrong. For example:

    ```ruby
    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(a..10).each {}')
      expect(cop.offenses).to be_empty
    end

    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(0..b).each {}')
      expect(cop.offenses).to be_empty
    end
    ```

3. Redundant examples where the spec and its corresponding description
   showed up twice in the same context. For example:

    ```ruby
    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end

    # ...

    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end
    ```
koic pushed a commit to rubocop/rubocop-rails that referenced this pull request Oct 26, 2018
Found these offending spec descriptions while testing out
rubocop/rubocop-rspec#259. Searching for duplicate spec descriptions
revealed a few categories of issues:

1. Examples with repeated descriptions that tested different cases
   but didn't describe the specific difference. For example:

    ```ruby
    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{}"')
      expect(new_source).to eq('"this is the "')
    end

    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{ }"')
      expect(new_source).to eq('"this is the "')
    end
    ```

2. Examples with repeated descriptions where one or more descriptions
   were wrong. For example:

    ```ruby
    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(a..10).each {}')
      expect(cop.offenses).to be_empty
    end

    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(0..b).each {}')
      expect(cop.offenses).to be_empty
    end
    ```

3. Redundant examples where the spec and its corresponding description
   showed up twice in the same context. For example:

    ```ruby
    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end

    # ...

    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end
    ```
patrickm53 pushed a commit to patrickm53/performance-develop-rubyonrails that referenced this pull request Sep 23, 2022
Found these offending spec descriptions while testing out
rubocop/rubocop-rspec#259. Searching for duplicate spec descriptions
revealed a few categories of issues:

1. Examples with repeated descriptions that tested different cases
   but didn't describe the specific difference. For example:

    ```ruby
    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{}"')
      expect(new_source).to eq('"this is the "')
    end

    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{ }"')
      expect(new_source).to eq('"this is the "')
    end
    ```

2. Examples with repeated descriptions where one or more descriptions
   were wrong. For example:

    ```ruby
    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(a..10).each {}')
      expect(cop.offenses).to be_empty
    end

    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(0..b).each {}')
      expect(cop.offenses).to be_empty
    end
    ```

3. Redundant examples where the spec and its corresponding description
   showed up twice in the same context. For example:

    ```ruby
    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end

    # ...

    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end
    ```
richardstewart0213 added a commit to richardstewart0213/performance-build-Performance-optimization-analysis- that referenced this pull request Nov 4, 2022
Found these offending spec descriptions while testing out
rubocop/rubocop-rspec#259. Searching for duplicate spec descriptions
revealed a few categories of issues:

1. Examples with repeated descriptions that tested different cases
   but didn't describe the specific difference. For example:

    ```ruby
    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{}"')
      expect(new_source).to eq('"this is the "')
    end

    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{ }"')
      expect(new_source).to eq('"this is the "')
    end
    ```

2. Examples with repeated descriptions where one or more descriptions
   were wrong. For example:

    ```ruby
    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(a..10).each {}')
      expect(cop.offenses).to be_empty
    end

    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(0..b).each {}')
      expect(cop.offenses).to be_empty
    end
    ```

3. Redundant examples where the spec and its corresponding description
   showed up twice in the same context. For example:

    ```ruby
    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end

    # ...

    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end
    ```
Cute0110 added a commit to Cute0110/Rubocop-Performance that referenced this pull request Sep 28, 2023
Found these offending spec descriptions while testing out
rubocop/rubocop-rspec#259. Searching for duplicate spec descriptions
revealed a few categories of issues:

1. Examples with repeated descriptions that tested different cases
   but didn't describe the specific difference. For example:

    ```ruby
    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{}"')
      expect(new_source).to eq('"this is the "')
    end

    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{ }"')
      expect(new_source).to eq('"this is the "')
    end
    ```

2. Examples with repeated descriptions where one or more descriptions
   were wrong. For example:

    ```ruby
    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(a..10).each {}')
      expect(cop.offenses).to be_empty
    end

    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(0..b).each {}')
      expect(cop.offenses).to be_empty
    end
    ```

3. Redundant examples where the spec and its corresponding description
   showed up twice in the same context. For example:

    ```ruby
    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end

    # ...

    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end
    ```
SerhiiMisiura added a commit to SerhiiMisiura/Rubocop-Performance that referenced this pull request Oct 5, 2023
Found these offending spec descriptions while testing out
rubocop/rubocop-rspec#259. Searching for duplicate spec descriptions
revealed a few categories of issues:

1. Examples with repeated descriptions that tested different cases
   but didn't describe the specific difference. For example:

    ```ruby
    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{}"')
      expect(new_source).to eq('"this is the "')
    end

    it 'autocorrects' do
      new_source = autocorrect_source(cop, '"this is the #{ }"')
      expect(new_source).to eq('"this is the "')
    end
    ```

2. Examples with repeated descriptions where one or more descriptions
   were wrong. For example:

    ```ruby
    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(a..10).each {}')
      expect(cop.offenses).to be_empty
    end

    it 'does not register offense if range startpoint is not constant' do
      inspect_source(cop, '(0..b).each {}')
      expect(cop.offenses).to be_empty
    end
    ```

3. Redundant examples where the spec and its corresponding description
   showed up twice in the same context. For example:

    ```ruby
    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end

    # ...

    it 'picks ruby executable files with no extension' do
      expect(found_basenames).to include('executable')
    end
    ```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants