-
-
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
Improve cops by using TopLevelGroup #977
Changes from all commits
4070d70
330929f
5f46a62
df0ea6e
6276370
5f715e3
130e720
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,16 +17,24 @@ module RSpec | |
# describe MyClass, '.my_class_method' do | ||
# end | ||
class DescribeMethod < Base | ||
include RuboCop::RSpec::TopLevelDescribe | ||
include RuboCop::RSpec::TopLevelGroup | ||
|
||
MSG = 'The second argument to describe should be the method '\ | ||
"being tested. '#instance' or '.class'." | ||
|
||
def on_top_level_describe(_node, (_, second_arg)) | ||
return unless second_arg&.str_type? | ||
return if second_arg.str_content.start_with?('#', '.') | ||
def_node_matcher :second_argument, <<~PATTERN | ||
(block | ||
(send #rspec? :describe _first_argument $(str _) ...) ... | ||
pirj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
PATTERN | ||
|
||
add_offense(second_arg) | ||
def on_top_level_group(node) | ||
second_argument = second_argument(node) | ||
pirj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return unless second_argument | ||
return if second_argument.str_content.start_with?('#', '.') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm ok to turn this into a method and pushing this into the node pattern, but decided not to fix everything in one go. |
||
|
||
add_offense(second_argument) | ||
end | ||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,34 +57,42 @@ module RSpec | |
# my_class_spec.rb # describe MyClass, '#method' | ||
# | ||
class FilePath < Base | ||
include RuboCop::RSpec::TopLevelDescribe | ||
include RuboCop::RSpec::TopLevelGroup | ||
|
||
MSG = 'Spec path should end with `%<suffix>s`.' | ||
|
||
def_node_search :const_described?, '(send _ :describe (const ...) ...)' | ||
def_node_matcher :const_described, <<~PATTERN | ||
(block | ||
$(send #rspec? _example_group $(const ...) $...) ... | ||
pirj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
PATTERN | ||
|
||
def_node_search :routing_metadata?, '(pair (sym :type) (sym :routing))' | ||
|
||
def on_top_level_describe(node, args) | ||
return unless const_described?(node) && single_top_level_describe? | ||
pirj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return if routing_spec?(args) | ||
def on_top_level_group(node) | ||
return unless top_level_groups.one? | ||
|
||
glob = glob_for(args) | ||
const_described(node) do |send_node, described_class, arguments| | ||
next if routing_spec?(arguments) | ||
bquorning marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return if filename_ends_with?(glob) | ||
|
||
add_offense( | ||
node, | ||
message: format(MSG, suffix: glob) | ||
) | ||
ensure_correct_file_path(send_node, described_class, arguments) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code climate insisted on this extraction. |
||
end | ||
end | ||
|
||
private | ||
|
||
def ensure_correct_file_path(send_node, described_class, arguments) | ||
glob = glob_for(described_class, arguments.first) | ||
return if filename_ends_with?(glob) | ||
|
||
add_offense(send_node, message: format(MSG, suffix: glob)) | ||
end | ||
|
||
def routing_spec?(args) | ||
args.any?(&method(:routing_metadata?)) | ||
end | ||
|
||
def glob_for((described_class, method_name)) | ||
def glob_for(described_class, method_name) | ||
return glob_for_spec_suffix_only? if spec_suffix_only? | ||
|
||
"#{expected_path(described_class)}#{name_glob(method_name)}*_spec.rb" | ||
|
@@ -94,10 +102,10 @@ def glob_for_spec_suffix_only? | |
'*_spec.rb' | ||
end | ||
|
||
def name_glob(name) | ||
return unless name&.str_type? | ||
def name_glob(method_name) | ||
return unless method_name&.str_type? | ||
|
||
"*#{name.str_content.gsub(/\W/, '')}" unless ignore_methods? | ||
"*#{method_name.str_content.gsub(/\W/, '')}" unless ignore_methods? | ||
end | ||
|
||
def expected_path(constant) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,29 +10,40 @@ module TopLevelGroup | |
def_node_matcher :example_or_shared_group?, | ||
(ExampleGroups::ALL + SharedGroups::ALL).block_pattern | ||
|
||
def on_block(node) | ||
return unless respond_to?(:on_top_level_group) | ||
return unless top_level_group?(node) | ||
def on_new_investigation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a hair more efficient than |
||
super | ||
|
||
on_top_level_group(node) | ||
return unless root_node | ||
|
||
top_level_groups.each do |node| | ||
example_group?(node, &method(:on_top_level_example_group)) | ||
on_top_level_group(node) | ||
end | ||
pirj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
|
||
def top_level_groups | ||
@top_level_groups ||= | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The memoization needs to be reset on each new investigation, right?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was following this note:
There might have been some changes after the doc has been introduced. As a quick experiment, I've set a breakpoint in random cop's
@marcandre Can you please shed light on this? Isn't this doc misleading? I used to think it would be quite hard to reuse cop instances since depending on the directory they may receive different config (due to directory-local There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The cop mentioned in the referenced discussion, def on_new_investigation
@token_table = nil There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's even more strange: def on_new_investigation
print "N"
super
end
def initialize(*args)
print "#"
super
end outputs:
reduced for better visibility:
🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @pirj Currently, cops being persisted is opt-in. That being said, I'm wondering if a cop that implements There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, that was my thinking too.
Absolutely :-)
You mean the recommendation to clear the data with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would remove the whole "If your class uses instance variables" section, as it's more confusing than helpful considering the current "one source" - "one cop instance" behaviour. Even for those cops that need to be persisted, I can think of By the way, there's one more thing that can impact those persisted cops - There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not quite... See rubocop/rubocop#7968 if interested
I imagine it's by source, but we could segregate those cops that need it; I suspect they simply run them without There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bquorning As a bottom-line: I wouldn't worry about |
||
top_level_nodes(root_node).select { |n| example_or_shared_group?(n) } | ||
end | ||
|
||
private | ||
|
||
# Dummy methods to be overridden in the consumer | ||
def on_top_level_example_group; end | ||
|
||
def on_top_level_group; end | ||
|
||
def top_level_group?(node) | ||
top_level_groups.include?(node) | ||
end | ||
|
||
def top_level_groups | ||
@top_level_groups ||= | ||
top_level_nodes.select { |n| example_or_shared_group?(n) } | ||
end | ||
|
||
def top_level_nodes | ||
if root_node.begin_type? | ||
root_node.children | ||
def top_level_nodes(node) | ||
if node.begin_type? | ||
bquorning marked this conversation as resolved.
Show resolved
Hide resolved
|
||
node.children | ||
elsif node.module_type? || node.class_type? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously, module A
class B
RSpec.describe 'C' do was missed. |
||
top_level_nodes(node.body) | ||
else | ||
[root_node] | ||
[node] | ||
end | ||
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.
Collapsed several significantly overlapping node patterns into one.
Moved guard statements into node pattern as well.
There still seems to be some redundancy in this node pattern, similarly to
SubjectStub
:I'll open a ticket for
rubocop-ast
.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.
Is
[ ...]
a new syntax for{ ... }
? Does it work on the minimum supported rubocop version or should we bump the requirements?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.
[ ]
is logical "and", it's around for a while, just not too frequently used.I find it too hard to reason about boolean login, this node pattern sounds like:
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 confused by the
(send #rspec? :describe
part in the second argument of[ ]
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.
@Darhazer Why?
It's basically the same as previously (which I forgot to remove during merge resolution):
!const
check is not necessary anymore, it's checked in the first part of[ ]
.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 too find this node pattern a bit cryptic. Perhaps a bit more duplication can be justified if it makes this code easier to read?
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 point guys. Split this node pattern into two parts. Looks more readable now.
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.
FYI the shiny newly released
rubocop-ast
allows comments in patterns