-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
InternalAffairs/StyleDetectedApiUse
This internal cop checks that the "style detected" API provided by `ConfigurableEnforcedStyle` is used correctly. This means that any cop using it must call both `correct_style_detected` and at least one of the "negative" methods (`opposite_style_detected`, `unexpected_style_detected`, `ambiguous_style_detected`, `conflicting_styles_detected`, `unrecognized_style_detected`, or `no_acceptable_style!`). It also checks that these methods are not used in conditionals as doing so is likely a mistake; they're not predicates for checking if something has happened, they're mutations for changing state.
- Loading branch information
Showing
3 changed files
with
218 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
145 changes: 145 additions & 0 deletions
145
lib/rubocop/cop/internal_affairs/style_detected_api_use.rb
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,145 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module InternalAffairs | ||
# Checks for correct use of the style_detected API provided by | ||
# `ConfigurableEnforcedStyle`. If `correct_style_detected` is used | ||
# then `opposite_style_detected`, `unexpected_style_detected`, | ||
# `ambiguous_style_detected`, `conflicting_styles_detected`, | ||
# `unrecognized_style_detected` or `no_acceptable_style!` should be | ||
# used too, and vice versa. The `xxx_style_detected` methods | ||
# should not be used as predicates either. | ||
# | ||
# @example | ||
# | ||
# # bad | ||
# def on_send(node) | ||
# return add_offense(node) if opposite_style_detected | ||
# | ||
# correct_style_detected | ||
# end | ||
# | ||
# def on_send(node) | ||
# if offense? | ||
# add_offense(node) | ||
# else | ||
# correct_style_detected | ||
# end | ||
# end | ||
# | ||
# def on_send(node) | ||
# return unless offense? | ||
# | ||
# add_offense(node) | ||
# opposite_style_detected | ||
# end | ||
# | ||
# # good | ||
# def on_send(node) | ||
# if offense? | ||
# add_offense(node) | ||
# opposite_style_detected | ||
# else | ||
# correct_style_detected | ||
# end | ||
# end | ||
# | ||
# def on_send(node) | ||
# add_offense(node) if offense? | ||
# end | ||
# | ||
class StyleDetectedApiUse < Base | ||
include RangeHelp | ||
|
||
MSG_FOR_POSITIVE_WITHOUT_NEGATIVE = | ||
'`correct_style_detected` method called without ' \ | ||
'calling a negative `*_style_detected` method.' | ||
MSG_FOR_NEGATIVE_WITHOUT_POSITIVE = | ||
'negative `*_style_detected` methods called without ' \ | ||
'calling `correct_style_detected` method.' | ||
MSG_FOR_CONDITIONAL_USE = | ||
'`*_style_detected` method called in conditional.' | ||
RESTRICT_ON_SEND = %i[ | ||
correct_style_detected opposite_style_detected | ||
unexpected_style_detected ambiguous_style_detected | ||
conflicting_styles_detected unrecognized_style_detected | ||
no_acceptable_style! style_detected | ||
].freeze | ||
|
||
def_node_matcher :correct_style_detected_check, <<~PATTERN | ||
(send nil? :correct_style_detected) | ||
PATTERN | ||
|
||
def_node_matcher :negative_style_detected_method_check, <<~PATTERN | ||
(send nil? /(?:opposite|unexpected|ambiguous|unrecognized)_style_detected|conflicting_styles_detected/ ...) | ||
PATTERN | ||
|
||
def_node_matcher :no_acceptable_style_check, <<~PATTERN | ||
(send nil? :no_acceptable_style!) | ||
PATTERN | ||
|
||
def_node_matcher :style_detected_check, <<~PATTERN | ||
(send nil? :style_detected ...) | ||
PATTERN | ||
|
||
def on_new_investigation | ||
@correct_style_detected_called = false | ||
@negative_style_detected_methods_called = false | ||
@style_detected_called = false | ||
end | ||
|
||
def on_investigation_end | ||
return if style_detected_called | ||
return unless correct_style_detected_called ^ negative_style_detected_methods_called | ||
|
||
add_global_offense(MSG_FOR_POSITIVE_WITHOUT_NEGATIVE) if positive_without_negative? | ||
add_global_offense(MSG_FOR_NEGATIVE_WITHOUT_POSITIVE) if negative_without_positive? | ||
end | ||
|
||
def on_send(node) | ||
if correct_style_detected_check(node) | ||
@correct_style_detected_called = true | ||
elsif negative_style_detected_method_check(node) || no_acceptable_style_check(node) | ||
@negative_style_detected_methods_called = true | ||
elsif style_detected_check(node) | ||
@style_detected_called = true | ||
end | ||
end | ||
|
||
def on_if(node) | ||
traverse_condition(node.condition) do |cond| | ||
add_offense(cond, message: MSG_FOR_CONDITIONAL_USE) if style_detected_api_used?(cond) | ||
end | ||
end | ||
|
||
private | ||
|
||
attr_reader :correct_style_detected_called, | ||
:negative_style_detected_methods_called, | ||
:style_detected_called | ||
|
||
def positive_without_negative? | ||
correct_style_detected_called && !negative_style_detected_methods_called | ||
end | ||
|
||
def negative_without_positive? | ||
negative_style_detected_methods_called && !correct_style_detected_called | ||
end | ||
|
||
def style_detected_api_used?(node) | ||
correct_style_detected_check(node) || | ||
negative_style_detected_method_check(node) || | ||
no_acceptable_style_check(node) || | ||
style_detected_check(node) | ||
end | ||
|
||
def traverse_condition(condition, &block) | ||
yield condition if condition.send_type? | ||
|
||
condition.each_child_node { |child| traverse_condition(child, &block) } | ||
end | ||
end | ||
end | ||
end | ||
end |
72 changes: 72 additions & 0 deletions
72
spec/rubocop/cop/internal_affairs/style_detected_api_use_spec.rb
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,72 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::InternalAffairs::StyleDetectedApiUse do | ||
subject(:cop) { described_class.new(config) } | ||
|
||
let(:config) { RuboCop::Config.new } | ||
|
||
it 'registers an offense when correct_style_detected is used without a negative *_style_detected follow up' do | ||
expect_offense(<<~RUBY) | ||
def on_send(node) | ||
^{} `correct_style_detected` method called without calling a negative `*_style_detected` method. | ||
if offense? | ||
add_offense(node) | ||
else | ||
correct_style_detected | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when correct_style_detected is used in a conditional expression' do | ||
expect_offense(<<~RUBY) | ||
def on_send(node) | ||
return if correct_style_detected | ||
^^^^^^^^^^^^^^^^^^^^^^ `*_style_detected` method called in conditional. | ||
add_offense(node) | ||
opposite_style_detected | ||
end | ||
RUBY | ||
end | ||
|
||
%i[opposite_style_detected unexpected_style_detected | ||
ambiguous_style_detected conflicting_styles_detected | ||
unrecognized_style_detected | ||
no_acceptable_style!].each do |negative_style_detected_method| | ||
it "registers an offense when #{negative_style_detected_method} is used in a conditional expression" do | ||
expect_offense(<<~RUBY) | ||
def on_send(node) | ||
return add_offense(node) if #{negative_style_detected_method} | ||
#{'^' * negative_style_detected_method.to_s.length} `*_style_detected` method called in conditional. | ||
correct_style_detected | ||
end | ||
RUBY | ||
end | ||
|
||
it "registers an offense when #{negative_style_detected_method} is used without a correct_style_detected follow up" do | ||
expect_offense(<<~RUBY) | ||
def on_send(node) | ||
^{} negative `*_style_detected` methods called without calling `correct_style_detected` method. | ||
return unless offense? | ||
add_offense(node) | ||
#{negative_style_detected_method} | ||
end | ||
RUBY | ||
end | ||
|
||
it "does not register an offense when correct_style_detected and a #{negative_style_detected_method} are both used" do | ||
expect_no_offenses(<<~RUBY) | ||
def on_send(node) | ||
if offense? | ||
add_offense(node) | ||
#{negative_style_detected_method} | ||
else | ||
correct_style_detected | ||
end | ||
end | ||
RUBY | ||
end | ||
end | ||
end |