rubocop 1.7.0 → 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +4 -3
- data/config/default.yml +137 -31
- data/config/obsoletion.yml +4 -0
- data/lib/rubocop.rb +14 -1
- data/lib/rubocop/cli/command/auto_genenerate_config.rb +5 -4
- data/lib/rubocop/comment_config.rb +6 -6
- data/lib/rubocop/config.rb +5 -2
- data/lib/rubocop/config_loader.rb +7 -14
- data/lib/rubocop/config_store.rb +12 -1
- data/lib/rubocop/cop/base.rb +2 -1
- data/lib/rubocop/cop/exclude_limit.rb +26 -0
- data/lib/rubocop/cop/gemspec/date_assignment.rb +56 -0
- data/lib/rubocop/cop/generator.rb +1 -3
- data/lib/rubocop/cop/internal_affairs.rb +5 -1
- data/lib/rubocop/cop/internal_affairs/empty_line_between_expect_offense_and_correction.rb +68 -0
- data/lib/rubocop/cop/internal_affairs/example_description.rb +89 -0
- data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +61 -0
- data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +64 -0
- data/lib/rubocop/cop/layout/class_structure.rb +7 -2
- data/lib/rubocop/cop/layout/empty_line_between_defs.rb +38 -18
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +16 -2
- data/lib/rubocop/cop/layout/line_length.rb +2 -1
- data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +26 -0
- data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -2
- data/lib/rubocop/cop/layout/space_before_brackets.rb +19 -16
- data/lib/rubocop/cop/lint/debugger.rb +58 -14
- data/lib/rubocop/cop/lint/deprecated_constants.rb +80 -0
- data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +13 -4
- data/lib/rubocop/cop/lint/duplicate_require.rb +2 -2
- data/lib/rubocop/cop/lint/else_layout.rb +1 -1
- data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +44 -0
- data/lib/rubocop/cop/lint/multiple_comparison.rb +4 -4
- data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +10 -6
- data/lib/rubocop/cop/lint/number_conversion.rb +41 -6
- data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +47 -0
- data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +39 -0
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +2 -1
- data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +50 -0
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +5 -3
- data/lib/rubocop/cop/lint/symbol_conversion.rb +103 -0
- data/lib/rubocop/cop/lint/triple_quotes.rb +71 -0
- data/lib/rubocop/cop/message_annotator.rb +4 -1
- data/lib/rubocop/cop/metrics/block_nesting.rb +2 -2
- data/lib/rubocop/cop/metrics/parameter_lists.rb +5 -2
- data/lib/rubocop/cop/mixin/allowed_identifiers.rb +18 -0
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -0
- data/lib/rubocop/cop/mixin/code_length.rb +3 -1
- data/lib/rubocop/cop/mixin/comments_help.rb +1 -11
- data/lib/rubocop/cop/mixin/configurable_max.rb +1 -0
- data/lib/rubocop/cop/mixin/first_element_line_break.rb +1 -1
- data/lib/rubocop/cop/mixin/method_complexity.rb +3 -1
- data/lib/rubocop/cop/mixin/preferred_delimiters.rb +2 -2
- data/lib/rubocop/cop/mixin/uncommunicative_name.rb +5 -1
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +38 -5
- data/lib/rubocop/cop/naming/variable_name.rb +2 -0
- data/lib/rubocop/cop/naming/variable_number.rb +2 -9
- data/lib/rubocop/cop/registry.rb +1 -1
- data/lib/rubocop/cop/severity.rb +3 -3
- data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
- data/lib/rubocop/cop/style/constant_visibility.rb +27 -0
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +49 -9
- data/lib/rubocop/cop/style/double_negation.rb +2 -2
- data/lib/rubocop/cop/style/empty_literal.rb +6 -2
- data/lib/rubocop/cop/style/endless_method.rb +102 -0
- data/lib/rubocop/cop/style/eval_with_location.rb +138 -49
- data/lib/rubocop/cop/style/explicit_block_argument.rb +11 -1
- data/lib/rubocop/cop/style/exponential_notation.rb +6 -7
- data/lib/rubocop/cop/style/float_division.rb +3 -0
- data/lib/rubocop/cop/style/format_string_token.rb +18 -2
- data/lib/rubocop/cop/style/hash_conversion.rb +81 -0
- data/lib/rubocop/cop/style/hash_like_case.rb +2 -1
- data/lib/rubocop/cop/style/if_inside_else.rb +22 -10
- data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +120 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +4 -0
- data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +4 -0
- data/lib/rubocop/cop/style/nil_comparison.rb +3 -0
- data/lib/rubocop/cop/style/non_nil_check.rb +23 -13
- data/lib/rubocop/cop/style/numeric_literals.rb +6 -9
- data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
- data/lib/rubocop/cop/style/raise_args.rb +3 -2
- data/lib/rubocop/cop/style/redundant_return.rb +1 -1
- data/lib/rubocop/cop/style/single_line_methods.rb +32 -2
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +29 -5
- data/lib/rubocop/cop/style/special_global_vars.rb +3 -3
- data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
- data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/while_until_modifier.rb +2 -4
- data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -0
- data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
- data/lib/rubocop/formatter/simple_text_formatter.rb +2 -1
- data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
- data/lib/rubocop/magic_comment.rb +30 -1
- data/lib/rubocop/options.rb +1 -1
- data/lib/rubocop/rspec/expect_offense.rb +5 -2
- data/lib/rubocop/runner.rb +1 -0
- data/lib/rubocop/target_ruby.rb +47 -11
- data/lib/rubocop/version.rb +2 -2
- metadata +24 -7
@@ -9,6 +9,7 @@ module RuboCop
|
|
9
9
|
self.command_name = :auto_gen_config
|
10
10
|
|
11
11
|
AUTO_GENERATED_FILE = '.rubocop_todo.yml'
|
12
|
+
YAML_OPTIONAL_DOC_START = /\A---(\s+#|\s*\z)/.freeze
|
12
13
|
|
13
14
|
PHASE_1 = 'Phase 1 of 2: run Layout/LineLength cop'
|
14
15
|
PHASE_2 = 'Phase 2 of 2: run all cops'
|
@@ -130,10 +131,10 @@ module RuboCop
|
|
130
131
|
end
|
131
132
|
|
132
133
|
def write_config_file(file_name, file_string, rubocop_yml_contents)
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
134
|
+
lines = /\S/.match?(rubocop_yml_contents) ? rubocop_yml_contents.split("\n", -1) : []
|
135
|
+
doc_start_index = lines.index { |line| YAML_OPTIONAL_DOC_START.match?(line) } || -1
|
136
|
+
lines.insert(doc_start_index + 1, "inherit_from:#{file_string}\n")
|
137
|
+
File.open(file_name, 'w') { |f| f.write lines.join("\n") }
|
137
138
|
end
|
138
139
|
end
|
139
140
|
end
|
@@ -47,6 +47,12 @@ module RuboCop
|
|
47
47
|
)
|
48
48
|
end
|
49
49
|
|
50
|
+
def comment_only_line?(line_number)
|
51
|
+
non_comment_token_line_numbers.none? do |non_comment_line_number|
|
52
|
+
non_comment_line_number == line_number
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
50
56
|
private
|
51
57
|
|
52
58
|
def extra_enabled_comments_with_names(extras:, names:)
|
@@ -166,12 +172,6 @@ module RuboCop
|
|
166
172
|
@all_cop_names ||= Cop::Registry.global.names - [REDUNDANT_DISABLE]
|
167
173
|
end
|
168
174
|
|
169
|
-
def comment_only_line?(line_number)
|
170
|
-
non_comment_token_line_numbers.none? do |non_comment_line_number|
|
171
|
-
non_comment_line_number == line_number
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
175
|
def non_comment_token_line_numbers
|
176
176
|
@non_comment_token_line_numbers ||= begin
|
177
177
|
non_comment_tokens = processed_source.tokens.reject(&:comment?)
|
data/lib/rubocop/config.rb
CHANGED
@@ -33,8 +33,11 @@ module RuboCop
|
|
33
33
|
@validator = ConfigValidator.new(self)
|
34
34
|
end
|
35
35
|
|
36
|
-
def self.create(hash, path)
|
37
|
-
new(hash, path)
|
36
|
+
def self.create(hash, path, check: true)
|
37
|
+
config = new(hash, path)
|
38
|
+
config.check if check
|
39
|
+
|
40
|
+
config
|
38
41
|
end
|
39
42
|
|
40
43
|
def loaded_features
|
@@ -25,17 +25,18 @@ module RuboCop
|
|
25
25
|
attr_accessor :debug, :ignore_parent_exclusion,
|
26
26
|
:disable_pending_cops, :enable_pending_cops
|
27
27
|
attr_writer :default_configuration, :project_root
|
28
|
+
attr_reader :loaded_features
|
28
29
|
|
29
30
|
alias debug? debug
|
30
31
|
alias ignore_parent_exclusion? ignore_parent_exclusion
|
31
32
|
|
32
33
|
def clear_options
|
33
34
|
@debug = nil
|
34
|
-
@loaded_features =
|
35
|
+
@loaded_features = Set.new
|
35
36
|
FileFinder.root_level = nil
|
36
37
|
end
|
37
38
|
|
38
|
-
def load_file(file)
|
39
|
+
def load_file(file, check: true)
|
39
40
|
path = file_path(file)
|
40
41
|
|
41
42
|
hash = load_yaml_configuration(path)
|
@@ -52,7 +53,7 @@ module RuboCop
|
|
52
53
|
|
53
54
|
hash.delete('inherit_from')
|
54
55
|
|
55
|
-
Config.create(hash, path)
|
56
|
+
Config.create(hash, path, check: check)
|
56
57
|
end
|
57
58
|
|
58
59
|
def load_yaml_configuration(absolute_path)
|
@@ -99,10 +100,10 @@ module RuboCop
|
|
99
100
|
find_user_xdg_config || DEFAULT_FILE
|
100
101
|
end
|
101
102
|
|
102
|
-
def configuration_from_file(config_file)
|
103
|
+
def configuration_from_file(config_file, check: true)
|
103
104
|
return default_configuration if config_file == DEFAULT_FILE
|
104
105
|
|
105
|
-
config = load_file(config_file)
|
106
|
+
config = load_file(config_file, check: check)
|
106
107
|
if ignore_parent_exclusion?
|
107
108
|
print 'Ignoring AllCops/Exclude from parent folders' if debug?
|
108
109
|
else
|
@@ -174,19 +175,11 @@ module RuboCop
|
|
174
175
|
resolver.merge_with_default(config, config_file, unset_nil: unset_nil)
|
175
176
|
end
|
176
177
|
|
177
|
-
def loaded_features
|
178
|
-
@loaded_features.flatten.compact
|
179
|
-
end
|
180
|
-
|
181
178
|
# @api private
|
182
179
|
# Used to add features that were required inside a config or from
|
183
180
|
# the CLI using `--require`.
|
184
181
|
def add_loaded_features(loaded_features)
|
185
|
-
|
186
|
-
instance_variable_get(:@loaded_features) << loaded_features
|
187
|
-
else
|
188
|
-
instance_variable_set(:@loaded_features, [loaded_features])
|
189
|
-
end
|
182
|
+
@loaded_features.merge(Array(loaded_features))
|
190
183
|
end
|
191
184
|
|
192
185
|
private
|
data/lib/rubocop/config_store.rb
CHANGED
@@ -4,6 +4,9 @@ module RuboCop
|
|
4
4
|
# Handles caching of configurations and association of inspected
|
5
5
|
# ruby files to configurations.
|
6
6
|
class ConfigStore
|
7
|
+
attr_reader :validated
|
8
|
+
alias validated? validated
|
9
|
+
|
7
10
|
def initialize
|
8
11
|
# @options_config stores a config that is specified in the command line.
|
9
12
|
# This takes precedence over configs located in any directories
|
@@ -17,6 +20,9 @@ module RuboCop
|
|
17
20
|
# @object_cache maps configuration file paths to
|
18
21
|
# configuration objects so we only need to load them once.
|
19
22
|
@object_cache = {}
|
23
|
+
|
24
|
+
# By default the config is validated before it can be used.
|
25
|
+
@validated = true
|
20
26
|
end
|
21
27
|
|
22
28
|
def options_config=(options_config)
|
@@ -29,6 +35,11 @@ module RuboCop
|
|
29
35
|
@options_config = ConfigLoader.default_configuration
|
30
36
|
end
|
31
37
|
|
38
|
+
def unvalidated
|
39
|
+
@validated = false
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
32
43
|
def for_file(file)
|
33
44
|
for_dir(File.dirname(file))
|
34
45
|
end
|
@@ -55,7 +66,7 @@ module RuboCop
|
|
55
66
|
path = @path_cache[dir]
|
56
67
|
@object_cache[path] ||= begin
|
57
68
|
print "For #{dir}: " if ConfigLoader.debug?
|
58
|
-
ConfigLoader.configuration_from_file(path)
|
69
|
+
ConfigLoader.configuration_from_file(path, check: validated?)
|
59
70
|
end
|
60
71
|
end
|
61
72
|
end
|
data/lib/rubocop/cop/base.rb
CHANGED
@@ -34,6 +34,7 @@ module RuboCop
|
|
34
34
|
class Base # rubocop:disable Metrics/ClassLength
|
35
35
|
extend RuboCop::AST::Sexp
|
36
36
|
extend NodePattern::Macros
|
37
|
+
extend ExcludeLimit
|
37
38
|
include RuboCop::AST::Sexp
|
38
39
|
include Util
|
39
40
|
include IgnoredNode
|
@@ -351,7 +352,7 @@ module RuboCop
|
|
351
352
|
def use_corrector(range, corrector)
|
352
353
|
if autocorrect?
|
353
354
|
attempt_correction(range, corrector)
|
354
|
-
elsif corrector
|
355
|
+
elsif corrector && cop_config.fetch('AutoCorrect', true)
|
355
356
|
:uncorrected
|
356
357
|
else
|
357
358
|
:unsupported
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
# Allows specified configuration options to have an exclude limit
|
5
|
+
# ie. a maximum value tracked that it can be used by `--auto-gen-config`.
|
6
|
+
module ExcludeLimit
|
7
|
+
# Sets up a configuration option to have an exclude limit tracked.
|
8
|
+
# The parameter name given is transformed into a method name (eg. `Max`
|
9
|
+
# becomes `self.max=` and `MinDigits` becomes `self.min_digits=`).
|
10
|
+
def exclude_limit(parameter_name, method_name: transform(parameter_name))
|
11
|
+
define_method("#{method_name}=") do |value|
|
12
|
+
cfg = config_to_allow_offenses
|
13
|
+
cfg[:exclude_limit] ||= {}
|
14
|
+
current_max = cfg[:exclude_limit][parameter_name]
|
15
|
+
value = [current_max, value].max if current_max
|
16
|
+
cfg[:exclude_limit][parameter_name] = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def transform(parameter_name)
|
23
|
+
parameter_name.gsub(/(?<!\A)(?=[A-Z])/, '_').downcase
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Gemspec
|
6
|
+
# This cop checks that `date =` is not used in gemspec file.
|
7
|
+
# It is set automatically when the gem is packaged.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# Gem::Specification.new do |spec|
|
13
|
+
# s.name = 'your_cool_gem_name'
|
14
|
+
# spec.date = Time.now.strftime('%Y-%m-%d')
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# Gem::Specification.new do |spec|
|
19
|
+
# s.name = 'your_cool_gem_name'
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
class DateAssignment < Base
|
23
|
+
include RangeHelp
|
24
|
+
extend AutoCorrector
|
25
|
+
|
26
|
+
MSG = 'Do not use `date =` in gemspec, it is set automatically when the gem is packaged.'
|
27
|
+
|
28
|
+
def_node_matcher :gem_specification, <<~PATTERN
|
29
|
+
(block
|
30
|
+
(send
|
31
|
+
(const
|
32
|
+
(const {cbase nil?} :Gem) :Specification) :new)
|
33
|
+
...)
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def on_block(block_node)
|
37
|
+
return unless gem_specification(block_node)
|
38
|
+
|
39
|
+
block_parameter = block_node.arguments.first.source
|
40
|
+
|
41
|
+
date_assignment = block_node.descendants.detect do |node|
|
42
|
+
node.send_type? && node.receiver&.source == block_parameter && node.method?(:date=)
|
43
|
+
end
|
44
|
+
|
45
|
+
return unless date_assignment
|
46
|
+
|
47
|
+
add_offense(date_assignment) do |corrector|
|
48
|
+
range = range_by_whole_lines(date_assignment.source_range, include_final_newline: true)
|
49
|
+
|
50
|
+
corrector.remove(range)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -81,9 +81,7 @@ module RuboCop
|
|
81
81
|
SPEC_TEMPLATE = <<~SPEC
|
82
82
|
# frozen_string_literal: true
|
83
83
|
|
84
|
-
RSpec.describe RuboCop::Cop::%<department>s::%<cop_name>s do
|
85
|
-
subject(:cop) { described_class.new(config) }
|
86
|
-
|
84
|
+
RSpec.describe RuboCop::Cop::%<department>s::%<cop_name>s, :config do
|
87
85
|
let(:config) { RuboCop::Config.new }
|
88
86
|
|
89
87
|
# TODO: Write test code
|
@@ -1,10 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'internal_affairs/empty_line_between_expect_offense_and_correction'
|
4
|
+
require_relative 'internal_affairs/example_description'
|
3
5
|
require_relative 'internal_affairs/method_name_equal'
|
4
6
|
require_relative 'internal_affairs/node_destructuring'
|
5
7
|
require_relative 'internal_affairs/node_type_predicate'
|
6
8
|
require_relative 'internal_affairs/offense_location_keyword'
|
7
|
-
require_relative 'internal_affairs/
|
9
|
+
require_relative 'internal_affairs/redundant_described_class_as_subject'
|
10
|
+
require_relative 'internal_affairs/redundant_let_rubocop_config_new'
|
8
11
|
require_relative 'internal_affairs/redundant_location_argument'
|
12
|
+
require_relative 'internal_affairs/redundant_message_argument'
|
9
13
|
require_relative 'internal_affairs/style_detected_api_use'
|
10
14
|
require_relative 'internal_affairs/useless_message_assertion'
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module InternalAffairs
|
6
|
+
# This cop checks whether `expect_offense` and correction expectation methods
|
7
|
+
# (i.e. `expect_correction` and `expect_no_corrections`) are separated by empty line.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# it 'registers and corrects an offense' do
|
12
|
+
# expect_offense(<<~RUBY)
|
13
|
+
# bad_method
|
14
|
+
# ^^^^^^^^^^ Use `good_method`.
|
15
|
+
# RUBY
|
16
|
+
# expect_correction(<<~RUBY)
|
17
|
+
# good_method
|
18
|
+
# RUBY
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# it 'registers and corrects an offense' do
|
23
|
+
# expect_offense(<<~RUBY)
|
24
|
+
# bad_method
|
25
|
+
# ^^^^^^^^^^ Use `good_method`.
|
26
|
+
# RUBY
|
27
|
+
#
|
28
|
+
# expect_correction(<<~RUBY)
|
29
|
+
# good_method
|
30
|
+
# RUBY
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
class EmptyLineBetweenExpectOffenseAndCorrection < Base
|
34
|
+
extend AutoCorrector
|
35
|
+
|
36
|
+
MSG = 'Add empty line between `expect_offense` and `%<expect_correction>s`.'
|
37
|
+
RESTRICT_ON_SEND = %i[expect_offense].freeze
|
38
|
+
CORRECTION_EXPECTATION_METHODS = %i[expect_correction expect_no_corrections].freeze
|
39
|
+
|
40
|
+
def on_send(node)
|
41
|
+
return unless (next_sibling = node.right_sibling) && next_sibling.send_type?
|
42
|
+
|
43
|
+
method_name = next_sibling.method_name
|
44
|
+
return unless CORRECTION_EXPECTATION_METHODS.include?(method_name)
|
45
|
+
|
46
|
+
range = offense_range(node)
|
47
|
+
return unless range.last_line + 1 == next_sibling.loc.line
|
48
|
+
|
49
|
+
add_offense(range, message: format(MSG, expect_correction: method_name)) do |corrector|
|
50
|
+
corrector.insert_after(range, "\n")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def offense_range(node)
|
57
|
+
first_argument = node.first_argument
|
58
|
+
|
59
|
+
if first_argument.respond_to?(:heredoc?) && first_argument.heredoc?
|
60
|
+
first_argument.loc.heredoc_end
|
61
|
+
else
|
62
|
+
node
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module InternalAffairs
|
6
|
+
# Checks that RSpec examples that use `expects_offense`
|
7
|
+
# or `expects_no_offenses` do not have conflicting
|
8
|
+
# descriptions.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# it 'does not register an offense' do
|
13
|
+
# expect_offense('...')
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# it 'registers an offense' do
|
17
|
+
# expect_no_offenses('...')
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# it 'registers an offense' do
|
22
|
+
# expect_offense('...')
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# it 'does not register an offense' do
|
26
|
+
# expect_no_offenses('...')
|
27
|
+
# end
|
28
|
+
class ExampleDescription < Base
|
29
|
+
class << self
|
30
|
+
attr_accessor :descriptions
|
31
|
+
end
|
32
|
+
|
33
|
+
MSG = 'Description does not match use of `%<method_name>s`.'
|
34
|
+
|
35
|
+
RESTRICT_ON_SEND = %i[
|
36
|
+
expect_offense
|
37
|
+
expect_no_offenses
|
38
|
+
expect_correction
|
39
|
+
expect_no_corrections
|
40
|
+
].to_set.freeze
|
41
|
+
|
42
|
+
EXPECT_NO_OFFENSES_INCORRECT_DESCRIPTIONS = [
|
43
|
+
/^(adds|registers|reports|finds) (an? )?offense/,
|
44
|
+
/^flags\b/
|
45
|
+
].freeze
|
46
|
+
|
47
|
+
EXPECT_OFFENSE_INCORRECT_DESCRIPTIONS = [
|
48
|
+
/^(does not|doesn't) (register|find|flag|report)/,
|
49
|
+
/^(does not|doesn't) add (a|an|any )?offense/
|
50
|
+
].freeze
|
51
|
+
|
52
|
+
EXPECT_NO_CORRECTIONS_INCORRECT_DESCRIPTIONS = [
|
53
|
+
/^(auto[- ]?)?correct/
|
54
|
+
].freeze
|
55
|
+
|
56
|
+
EXPECT_CORRECTION_INCORRECT_DESCRIPTIONS = [
|
57
|
+
/\b(does not|doesn't) (auto[- ]?)?correct/
|
58
|
+
].freeze
|
59
|
+
|
60
|
+
def_node_matcher :offense_example?, <<~PATTERN
|
61
|
+
(block
|
62
|
+
(send _ {:it :specify} $_description)
|
63
|
+
_args
|
64
|
+
`(send nil? %RESTRICT_ON_SEND ...)
|
65
|
+
)
|
66
|
+
PATTERN
|
67
|
+
|
68
|
+
def on_send(node)
|
69
|
+
parent = node.each_ancestor(:block).first
|
70
|
+
return unless parent && (description = offense_example?(parent))
|
71
|
+
|
72
|
+
method_name = node.method_name
|
73
|
+
message = format(MSG, method_name: method_name)
|
74
|
+
|
75
|
+
regexp_group = self.class.const_get("#{method_name}_incorrect_descriptions".upcase)
|
76
|
+
check_description(description, regexp_group, message)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def check_description(description, regexps, message)
|
82
|
+
return unless regexps.any? { |regexp| regexp.match?(description.value) }
|
83
|
+
|
84
|
+
add_offense(description, message: message)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|