-
-
Notifications
You must be signed in to change notification settings - Fork 276
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
Conversation
099f523
to
ccd9402
Compare
86daf54
to
3f41d9c
Compare
@bquorning @andyw8 this is reviewable |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
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 ```
6b75167
to
b86bd70
Compare
@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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in "withhin".
There was a problem hiding this comment.
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
Ran it over a large codebase and it caught 74 issues, all appear to be genuine. Nice work on this! |
b86bd70
to
5567b75
Compare
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 ```
This looks good. Add a CHANGELOG entry (and perhaps copy it to the PR description for future reference), and it’s a 👍 from me. |
5567b75
to
69ce703
Compare
@bquorning whoops good catch on the changelog. Added. Will merge when build is green 😄 |
69ce703
to
e3a62fc
Compare
Detect repeated descriptions in example groups
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 ```
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 ```
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 ```
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 ```
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 ```
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 ```
No description provided.