rubocop 1.8.1 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/config/default.yml +37 -4
  4. data/lib/rubocop.rb +6 -0
  5. data/lib/rubocop/cli/command/auto_genenerate_config.rb +5 -4
  6. data/lib/rubocop/config.rb +5 -2
  7. data/lib/rubocop/config_loader.rb +7 -14
  8. data/lib/rubocop/config_store.rb +12 -1
  9. data/lib/rubocop/cop/base.rb +2 -1
  10. data/lib/rubocop/cop/exclude_limit.rb +26 -0
  11. data/lib/rubocop/cop/generator.rb +1 -3
  12. data/lib/rubocop/cop/internal_affairs.rb +5 -1
  13. data/lib/rubocop/cop/internal_affairs/empty_line_between_expect_offense_and_correction.rb +68 -0
  14. data/lib/rubocop/cop/internal_affairs/example_description.rb +89 -0
  15. data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +61 -0
  16. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +64 -0
  17. data/lib/rubocop/cop/layout/class_structure.rb +7 -2
  18. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +37 -17
  19. data/lib/rubocop/cop/layout/first_argument_indentation.rb +16 -2
  20. data/lib/rubocop/cop/layout/line_length.rb +2 -1
  21. data/lib/rubocop/cop/layout/space_before_brackets.rb +9 -4
  22. data/lib/rubocop/cop/lint/deprecated_constants.rb +5 -0
  23. data/lib/rubocop/cop/lint/number_conversion.rb +41 -6
  24. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +47 -0
  25. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +39 -0
  26. data/lib/rubocop/cop/lint/symbol_conversion.rb +103 -0
  27. data/lib/rubocop/cop/lint/triple_quotes.rb +71 -0
  28. data/lib/rubocop/cop/message_annotator.rb +4 -1
  29. data/lib/rubocop/cop/metrics/block_nesting.rb +2 -2
  30. data/lib/rubocop/cop/metrics/parameter_lists.rb +5 -2
  31. data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -0
  32. data/lib/rubocop/cop/mixin/code_length.rb +3 -1
  33. data/lib/rubocop/cop/mixin/comments_help.rb +0 -1
  34. data/lib/rubocop/cop/mixin/configurable_max.rb +1 -0
  35. data/lib/rubocop/cop/mixin/method_complexity.rb +3 -1
  36. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +38 -5
  37. data/lib/rubocop/cop/naming/variable_number.rb +1 -1
  38. data/lib/rubocop/cop/severity.rb +3 -3
  39. data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
  40. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +49 -9
  41. data/lib/rubocop/cop/style/eval_with_location.rb +63 -34
  42. data/lib/rubocop/cop/style/float_division.rb +3 -0
  43. data/lib/rubocop/cop/style/format_string_token.rb +18 -2
  44. data/lib/rubocop/cop/style/if_inside_else.rb +14 -7
  45. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +120 -0
  46. data/lib/rubocop/cop/style/nil_comparison.rb +3 -0
  47. data/lib/rubocop/cop/style/non_nil_check.rb +23 -13
  48. data/lib/rubocop/cop/style/numeric_literals.rb +6 -9
  49. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  50. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  51. data/lib/rubocop/cop/style/sole_nested_conditional.rb +26 -2
  52. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  53. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -0
  54. data/lib/rubocop/formatter/simple_text_formatter.rb +2 -1
  55. data/lib/rubocop/magic_comment.rb +30 -1
  56. data/lib/rubocop/options.rb +1 -1
  57. data/lib/rubocop/rspec/expect_offense.rb +5 -2
  58. data/lib/rubocop/runner.rb +1 -0
  59. data/lib/rubocop/version.rb +2 -2
  60. metadata +13 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe7ec80b8cde2a075fee5a5658abf6dc74889091dae29e88482107983d2de270
4
- data.tar.gz: 7852fe6a6d0ba9cae98439cba0dfbfda659a5a434f62eca318bcd5bc45dfd3c4
3
+ metadata.gz: 86b427813730f667d5978bd4b4ca7e8f6766adf4ddf713e413cf07a0345b84b2
4
+ data.tar.gz: ede1028f4ebe7b6b1cd59de4d604e655c7115d928d38393ebd7febdbbff5b3e7
5
5
  SHA512:
6
- metadata.gz: 4239a27259a39584a544ed4bfc7c6025ece2198d463f8a2918a98e5560cec9a5a2ea5b978f0d253e61ea700446bb49f705627d8b8e41d4ead0fef35b23b14d98
7
- data.tar.gz: 59746b47f6bbe92dec5f6d79395ad6cd1a175d3a186005412953f0a849192673084b426bd14f6e9ac83e11dfaa0b27fc15718857b29ca6d359e2c9d387bca9c2
6
+ metadata.gz: b5e3ca723e7530916204e9bfce59bec1452232765a0903093ba2b4783fc35f109e655251c2fce8e3b08dbebcf4a4e67a4301b00c8113d45f5f0bb2a63cc4be7f
7
+ data.tar.gz: d91068c77da3ea787a5c36c287f1913bc94c23d98fc315604883cb21742f4c1031014e10c9470c67dca2fa3dfdc41eb848134f0a9565ff5a82e65aed5d6d2cf3
data/README.md CHANGED
@@ -45,13 +45,13 @@ gem 'rubocop', require: false
45
45
  ```
46
46
 
47
47
  RuboCop is stable between major versions, both in terms of API and cop configuration.
48
- We aim the ease the maintenance of RuboCop extensions and the upgrades between RuboCop
48
+ We aim to ease the maintenance of RuboCop extensions and the upgrades between RuboCop
49
49
  releases. All big changes are reserved for major releases.
50
50
  To prevent an unwanted RuboCop update you might want to use a conservative version lock
51
51
  in your `Gemfile`:
52
52
 
53
53
  ```rb
54
- gem 'rubocop', '~> 1.8', require: false
54
+ gem 'rubocop', '~> 1.9', require: false
55
55
  ```
56
56
 
57
57
  See [versioning](https://docs.rubocop.org/rubocop/1.0/versioning.html) for further details.
@@ -1806,6 +1806,17 @@ Lint/NumberConversion:
1806
1806
  - Time
1807
1807
  - DateTime
1808
1808
 
1809
+ Lint/NumberedParameterAssignment:
1810
+ Description: 'Checks for uses of numbered parameter assignment.'
1811
+ Enabled: pending
1812
+ VersionAdded: '1.9'
1813
+
1814
+ Lint/OrAssignmentToConstant:
1815
+ Description: 'Checks unintended or-assignment to constant.'
1816
+ Enabled: pending
1817
+ Safe: false
1818
+ VersionAdded: '1.9'
1819
+
1809
1820
  Lint/OrderedMagicComments:
1810
1821
  Description: 'Checks the proper ordering of magic comments and whether a magic comment is not placed before a shebang.'
1811
1822
  Enabled: true
@@ -2032,12 +2043,16 @@ Lint/SuppressedException:
2032
2043
  VersionAdded: '0.9'
2033
2044
  VersionChanged: '0.81'
2034
2045
 
2046
+ Lint/SymbolConversion:
2047
+ Description: 'Checks for unnecessary symbol conversions.'
2048
+ Enabled: pending
2049
+ VersionAdded: '1.9'
2050
+
2035
2051
  Lint/Syntax:
2036
- Description: 'Checks syntax error.'
2052
+ Description: 'Checks for syntax errors.'
2037
2053
  Enabled: true
2038
2054
  VersionAdded: '0.9'
2039
2055
 
2040
-
2041
2056
  Lint/ToEnumArguments:
2042
2057
  Description: 'This cop ensures that `to_enum`/`enum_for`, called for the current method, has correct arguments.'
2043
2058
  Enabled: pending
@@ -2058,6 +2073,11 @@ Lint/TrailingCommaInAttributeDeclaration:
2058
2073
  Enabled: true
2059
2074
  VersionAdded: '0.90'
2060
2075
 
2076
+ Lint/TripleQuotes:
2077
+ Description: 'Checks for useless triple quote constructs.'
2078
+ Enabled: pending
2079
+ VersionAdded: '1.9'
2080
+
2061
2081
  Lint/UnderscorePrefixedVariableName:
2062
2082
  Description: 'Do not use prefix `_` for a variable that is used.'
2063
2083
  Enabled: true
@@ -2689,7 +2709,8 @@ Style/AsciiComments:
2689
2709
  Enabled: true
2690
2710
  VersionAdded: '0.9'
2691
2711
  VersionChanged: '0.52'
2692
- AllowedChars: []
2712
+ AllowedChars:
2713
+ - ©
2693
2714
 
2694
2715
  Style/Attr:
2695
2716
  Description: 'Checks for uses of Module#attr.'
@@ -3120,6 +3141,8 @@ Style/DisableCopsWithinSourceCodeDirective:
3120
3141
  Forbids disabling/enabling cops within source code.
3121
3142
  Enabled: false
3122
3143
  VersionAdded: '0.82'
3144
+ VersionChanged: '1.9'
3145
+ AllowedCops: []
3123
3146
 
3124
3147
  Style/DocumentDynamicEvalDefinition:
3125
3148
  Description: >-
@@ -3291,7 +3314,8 @@ Style/FloatDivision:
3291
3314
  Reference: 'https://blog.rubystyle.guide/ruby/2019/06/21/float-division.html'
3292
3315
  Enabled: true
3293
3316
  VersionAdded: '0.72'
3294
- VersionChanged: '1.6'
3317
+ VersionChanged: '1.9'
3318
+ Safe: false
3295
3319
  EnforcedStyle: single_coerce
3296
3320
  SupportedStyles:
3297
3321
  - left_coerce
@@ -3339,6 +3363,7 @@ Style/FormatStringToken:
3339
3363
  MaxUnannotatedPlaceholdersAllowed: 1
3340
3364
  VersionAdded: '0.49'
3341
3365
  VersionChanged: '1.0'
3366
+ IgnoredMethods: []
3342
3367
 
3343
3368
  Style/FrozenStringLiteralComment:
3344
3369
  Description: >-
@@ -3492,6 +3517,14 @@ Style/IfUnlessModifierOfIfUnless:
3492
3517
  VersionAdded: '0.39'
3493
3518
  VersionChanged: '0.87'
3494
3519
 
3520
+ Style/IfWithBooleanLiteralBranches:
3521
+ Description: 'Checks for redundant `if` with boolean literal branches.'
3522
+ Enabled: pending
3523
+ VersionAdded: '1.9'
3524
+ SafeAutoCorrect: false
3525
+ AllowedMethods:
3526
+ - nonzero?
3527
+
3495
3528
  Style/IfWithSemicolon:
3496
3529
  Description: 'Do not use if x; .... Use the ternary operator instead.'
3497
3530
  StyleGuide: '#no-semicolon-ifs'
@@ -35,6 +35,7 @@ require_relative 'rubocop/cop/offense'
35
35
  require_relative 'rubocop/cop/message_annotator'
36
36
  require_relative 'rubocop/cop/ignored_node'
37
37
  require_relative 'rubocop/cop/autocorrect_logic'
38
+ require_relative 'rubocop/cop/exclude_limit'
38
39
  require_relative 'rubocop/cop/badge'
39
40
  require_relative 'rubocop/cop/registry'
40
41
  require_relative 'rubocop/cop/base'
@@ -312,6 +313,8 @@ require_relative 'rubocop/cop/lint/no_return_in_begin_end_blocks'
312
313
  require_relative 'rubocop/cop/lint/non_deterministic_require_order'
313
314
  require_relative 'rubocop/cop/lint/non_local_exit_from_iterator'
314
315
  require_relative 'rubocop/cop/lint/number_conversion'
316
+ require_relative 'rubocop/cop/lint/numbered_parameter_assignment'
317
+ require_relative 'rubocop/cop/lint/or_assignment_to_constant'
315
318
  require_relative 'rubocop/cop/lint/ordered_magic_comments'
316
319
  require_relative 'rubocop/cop/lint/out_of_range_regexp_ref'
317
320
  require_relative 'rubocop/cop/lint/parentheses_as_grouped_expression'
@@ -344,11 +347,13 @@ require_relative 'rubocop/cop/lint/shadowed_exception'
344
347
  require_relative 'rubocop/cop/lint/shadowing_outer_local_variable'
345
348
  require_relative 'rubocop/cop/lint/struct_new_override'
346
349
  require_relative 'rubocop/cop/lint/suppressed_exception'
350
+ require_relative 'rubocop/cop/lint/symbol_conversion'
347
351
  require_relative 'rubocop/cop/lint/syntax'
348
352
  require_relative 'rubocop/cop/lint/to_enum_arguments'
349
353
  require_relative 'rubocop/cop/lint/to_json'
350
354
  require_relative 'rubocop/cop/lint/top_level_return_with_argument'
351
355
  require_relative 'rubocop/cop/lint/trailing_comma_in_attribute_declaration'
356
+ require_relative 'rubocop/cop/lint/triple_quotes'
352
357
  require_relative 'rubocop/cop/lint/underscore_prefixed_variable_name'
353
358
  require_relative 'rubocop/cop/lint/unexpected_block_arity'
354
359
  require_relative 'rubocop/cop/lint/unified_integer'
@@ -477,6 +482,7 @@ require_relative 'rubocop/cop/style/identical_conditional_branches'
477
482
  require_relative 'rubocop/cop/style/if_inside_else'
478
483
  require_relative 'rubocop/cop/style/if_unless_modifier'
479
484
  require_relative 'rubocop/cop/style/if_unless_modifier_of_if_unless'
485
+ require_relative 'rubocop/cop/style/if_with_boolean_literal_branches'
480
486
  require_relative 'rubocop/cop/style/if_with_semicolon'
481
487
  require_relative 'rubocop/cop/style/implicit_runtime_error'
482
488
  require_relative 'rubocop/cop/style/infinite_loop'
@@ -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
- File.open(file_name, 'w') do |f|
134
- f.write "inherit_from:#{file_string}\n"
135
- f.write "\n#{rubocop_yml_contents}" if /\S/.match?(rubocop_yml_contents)
136
- end
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
@@ -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).check
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.uniq
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
- if instance_variable_defined?(:@loaded_features)
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
@@ -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
@@ -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
@@ -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/redundant_message_argument'
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