rubocop 0.82.0 → 0.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/config/default.yml +29 -4
  4. data/lib/rubocop.rb +2 -1
  5. data/lib/rubocop/ast/node/send_node.rb +4 -0
  6. data/lib/rubocop/cli.rb +1 -1
  7. data/lib/rubocop/config.rb +5 -1
  8. data/lib/rubocop/config_loader.rb +15 -14
  9. data/lib/rubocop/config_loader_resolver.rb +27 -0
  10. data/lib/rubocop/config_validator.rb +2 -1
  11. data/lib/rubocop/cop/generator.rb +3 -2
  12. data/lib/rubocop/cop/layout/condition_position.rb +12 -2
  13. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +68 -0
  14. data/lib/rubocop/cop/layout/line_length.rb +4 -1
  15. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +13 -4
  16. data/lib/rubocop/cop/layout/space_around_operators.rb +18 -1
  17. data/lib/rubocop/cop/layout/trailing_whitespace.rb +2 -2
  18. data/lib/rubocop/cop/lint/ambiguous_operator.rb +38 -0
  19. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +14 -0
  20. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -5
  21. data/lib/rubocop/cop/lint/empty_when.rb +29 -6
  22. data/lib/rubocop/cop/lint/ensure_return.rb +18 -1
  23. data/lib/rubocop/cop/lint/literal_as_condition.rb +10 -13
  24. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +21 -9
  25. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +1 -6
  26. data/lib/rubocop/cop/lint/suppressed_exception.rb +0 -6
  27. data/lib/rubocop/cop/lint/useless_access_modifier.rb +12 -0
  28. data/lib/rubocop/cop/lint/useless_assignment.rb +3 -2
  29. data/lib/rubocop/cop/lint/useless_else_without_rescue.rb +5 -0
  30. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +10 -1
  31. data/lib/rubocop/cop/mixin/hash_transform_method.rb +8 -1
  32. data/lib/rubocop/cop/mixin/line_length_help.rb +2 -1
  33. data/lib/rubocop/cop/mixin/parser_diagnostic.rb +1 -1
  34. data/lib/rubocop/cop/mixin/statement_modifier.rb +7 -23
  35. data/lib/rubocop/cop/mixin/target_ruby_version.rb +5 -1
  36. data/lib/rubocop/cop/naming/method_name.rb +1 -5
  37. data/lib/rubocop/cop/style/case_equality.rb +1 -1
  38. data/lib/rubocop/cop/style/empty_method.rb +0 -4
  39. data/lib/rubocop/cop/style/guard_clause.rb +25 -2
  40. data/lib/rubocop/cop/style/if_with_semicolon.rb +16 -0
  41. data/lib/rubocop/cop/style/lambda_call.rb +0 -20
  42. data/lib/rubocop/cop/style/multiline_when_then.rb +16 -1
  43. data/lib/rubocop/cop/style/optional_arguments.rb +1 -1
  44. data/lib/rubocop/cop/style/slicing_with_range.rb +39 -0
  45. data/lib/rubocop/cop/util.rb +24 -0
  46. data/lib/rubocop/cop/variable_force/assignment.rb +1 -0
  47. data/lib/rubocop/cop/variable_force/scope.rb +1 -0
  48. data/lib/rubocop/cop/variable_force/variable.rb +1 -0
  49. data/lib/rubocop/name_similarity.rb +12 -9
  50. data/lib/rubocop/options.rb +11 -4
  51. data/lib/rubocop/runner.rb +6 -1
  52. data/lib/rubocop/target_finder.rb +6 -4
  53. data/lib/rubocop/version.rb +1 -1
  54. metadata +4 -17
  55. data/lib/rubocop/string_util.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e04c6e897293df4ef1c7bf05a0c0077512e2cba797a7caee484b1c08a7300ad
4
- data.tar.gz: 92fbc98b5068e5c30bf83d058e3cfe774a1a65cc90608027ff8d6624c8c2910c
3
+ metadata.gz: 02ede8c9af1fb47b66ac79a0d93d577a5384f82f5076e1e64631ca761c4d3f6b
4
+ data.tar.gz: 2f22e7716a7068013b131636217a77189f2ca506e53c8613c407c18f8028600e
5
5
  SHA512:
6
- metadata.gz: cacf128617d233de65c2061d0300d345357840b151f65c78a2463fa23830115dcaa667cc2110ef39cacd14d957e626fc3fb4857443e40a1ab0fb485ce9cbfeed
7
- data.tar.gz: f7a095da673a5b32de33293c6056c580995d1b2ad913e2891c1740ab1ffb42896f1b0c69491eb75656907b60e4de6f6a58a119c8936cff26d1ed678bc1159e0c
6
+ metadata.gz: 936fdee12ea43542b66ab1cccc0514f1a7076dbd671386cccf376e61633b12e51c88feba3e652e77c925a2f5df4e0646bf568af453032bd2cb6eafdd9ea66d48
7
+ data.tar.gz: 23ec3628be52fd219206cd866c7e46757cdae9c8bd5c29334a7471c856f19becb03af7da2dd6f12a12c1f876f4eb900d10f9c8fdd7ad669c1bf2dea4cb333145
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/rubocop.svg)](https://badge.fury.io/rb/rubocop)
2
2
  [![CircleCI Status](https://circleci.com/gh/rubocop-hq/rubocop/tree/master.svg?style=svg)](https://circleci.com/gh/rubocop-hq/rubocop/tree/master)
3
- [![AppVeyor Status](https://ci.appveyor.com/api/projects/status/sj3ye7n5690d0nvg?svg=true)](https://ci.appveyor.com/project/bbatsov/rubocop)
3
+ [![Actions Status](https://github.com/rubocop-hq/rubocop/workflows/CI/badge.svg?branch=master)](https://github.com/rubocop-hq/rubocop/actions?query=workflow%3ACI)
4
4
  [![Coverage Status](https://api.codeclimate.com/v1/badges/ad6e76460499c8c99697/test_coverage)](https://codeclimate.com/github/bbatsov/rubocop)
5
5
  [![Code Climate](https://codeclimate.com/github/bbatsov/rubocop/badges/gpa.svg)](https://codeclimate.com/github/bbatsov/rubocop)
6
6
  [![Inline docs](https://inch-ci.org/github/bbatsov/rubocop.svg)](https://inch-ci.org/github/bbatsov/rubocop)
@@ -53,7 +53,7 @@ haven't reached version 1.0 yet). To prevent an unwanted RuboCop update you
53
53
  might want to use a conservative version lock in your `Gemfile`:
54
54
 
55
55
  ```rb
56
- gem 'rubocop', '~> 0.82.0', require: false
56
+ gem 'rubocop', '~> 0.83.0', require: false
57
57
  ```
58
58
 
59
59
  ## Quickstart
@@ -35,6 +35,7 @@ AllCops:
35
35
  - '**/*.watchr'
36
36
  - '**/.irbrc'
37
37
  - '**/.pryrc'
38
+ - '**/.simplecov'
38
39
  - '**/buildfile'
39
40
  - '**/Appraisals'
40
41
  - '**/Berksfile'
@@ -53,6 +54,7 @@ AllCops:
53
54
  - '**/Podfile'
54
55
  - '**/Puppetfile'
55
56
  - '**/Rakefile'
57
+ - '**/rakefile'
56
58
  - '**/Snapfile'
57
59
  - '**/Steepfile'
58
60
  - '**/Thorfile'
@@ -375,6 +377,7 @@ Layout/ConditionPosition:
375
377
  StyleGuide: '#same-line-condition'
376
378
  Enabled: true
377
379
  VersionAdded: '0.53'
380
+ VersionChanged: '0.83'
378
381
 
379
382
  Layout/DefEndAlignment:
380
383
  Description: 'Align ends corresponding to defs correctly.'
@@ -460,6 +463,12 @@ Layout/EmptyLinesAroundArguments:
460
463
  Enabled: true
461
464
  VersionAdded: '0.52'
462
465
 
466
+ Layout/EmptyLinesAroundAttributeAccessor:
467
+ Description: "Keep blank lines around attribute accessors."
468
+ StyleGuide: '#empty-lines-around-attribute-accessor'
469
+ Enabled: pending
470
+ VersionAdded: '0.83'
471
+
463
472
  Layout/EmptyLinesAroundBeginBody:
464
473
  Description: "Keeps track of empty lines around begin-end bodies."
465
474
  StyleGuide: '#empty-lines-around-bodies'
@@ -1295,8 +1304,8 @@ Layout/TrailingWhitespace:
1295
1304
  StyleGuide: '#no-trailing-whitespace'
1296
1305
  Enabled: true
1297
1306
  VersionAdded: '0.49'
1298
- VersionChanged: '0.55'
1299
- AllowInHeredoc: false
1307
+ VersionChanged: '0.83'
1308
+ AllowInHeredoc: true
1300
1309
 
1301
1310
  #################### Lint ##################################
1302
1311
  ### Warnings
@@ -1316,6 +1325,7 @@ Lint/AmbiguousOperator:
1316
1325
  StyleGuide: '#method-invocation-parens'
1317
1326
  Enabled: true
1318
1327
  VersionAdded: '0.17'
1328
+ VersionChanged: '0.83'
1319
1329
 
1320
1330
  Lint/AmbiguousRegexpLiteral:
1321
1331
  Description: >-
@@ -1323,6 +1333,7 @@ Lint/AmbiguousRegexpLiteral:
1323
1333
  a method invocation without parentheses.
1324
1334
  Enabled: true
1325
1335
  VersionAdded: '0.17'
1336
+ VersionChanged: '0.83'
1326
1337
 
1327
1338
  Lint/AssignmentInCondition:
1328
1339
  Description: "Don't use assignment in conditions."
@@ -1339,8 +1350,9 @@ Lint/BigDecimalNew:
1339
1350
  Lint/BooleanSymbol:
1340
1351
  Description: 'Check for `:true` and `:false` symbols.'
1341
1352
  Enabled: true
1353
+ Safe: false
1342
1354
  VersionAdded: '0.50'
1343
- VersionChanged: '0.81'
1355
+ VersionChanged: '0.83'
1344
1356
 
1345
1357
  Lint/CircularArgumentReference:
1346
1358
  Description: "Default values in optional keyword arguments and optional ordinal arguments should not refer back to the name of the argument."
@@ -1411,13 +1423,16 @@ Lint/EmptyInterpolation:
1411
1423
  Lint/EmptyWhen:
1412
1424
  Description: 'Checks for `when` branches with empty bodies.'
1413
1425
  Enabled: true
1426
+ AllowComments: true
1414
1427
  VersionAdded: '0.45'
1428
+ VersionChanged: '0.83'
1415
1429
 
1416
1430
  Lint/EnsureReturn:
1417
1431
  Description: 'Do not use return in an ensure block.'
1418
1432
  StyleGuide: '#no-return-ensure'
1419
1433
  Enabled: true
1420
1434
  VersionAdded: '0.9'
1435
+ VersionChanged: '0.83'
1421
1436
 
1422
1437
  Lint/ErbNewArguments:
1423
1438
  Description: 'Use `:trim_mode` and `:eoutvar` keyword arguments to `ERB.new`.'
@@ -1565,6 +1580,7 @@ Lint/ParenthesesAsGroupedExpression:
1565
1580
  StyleGuide: '#parens-no-spaces'
1566
1581
  Enabled: true
1567
1582
  VersionAdded: '0.12'
1583
+ VersionChanged: '0.83'
1568
1584
 
1569
1585
  Lint/PercentStringArray:
1570
1586
  Description: >-
@@ -1808,7 +1824,7 @@ Lint/UselessAccessModifier:
1808
1824
  Description: 'Checks for useless access modifiers.'
1809
1825
  Enabled: true
1810
1826
  VersionAdded: '0.20'
1811
- VersionChanged: '0.47'
1827
+ VersionChanged: '0.83'
1812
1828
  ContextCreatingMethods: []
1813
1829
  MethodCreatingMethods: []
1814
1830
 
@@ -2948,6 +2964,7 @@ Style/IfWithSemicolon:
2948
2964
  StyleGuide: '#no-semicolon-ifs'
2949
2965
  Enabled: true
2950
2966
  VersionAdded: '0.9'
2967
+ VersionChanged: '0.83'
2951
2968
 
2952
2969
  Style/ImplicitRuntimeError:
2953
2970
  Description: >-
@@ -3425,7 +3442,9 @@ Style/OptionalArguments:
3425
3442
  of the argument list.
3426
3443
  StyleGuide: '#optional-arguments'
3427
3444
  Enabled: true
3445
+ Safe: false
3428
3446
  VersionAdded: '0.33'
3447
+ VersionChanged: '0.83'
3429
3448
 
3430
3449
  Style/OrAssignment:
3431
3450
  Description: 'Recommend usage of double pipe equals (||=) where applicable.'
@@ -3730,6 +3749,12 @@ Style/SingleLineMethods:
3730
3749
  VersionChanged: '0.19'
3731
3750
  AllowIfMethodIsEmpty: true
3732
3751
 
3752
+ Style/SlicingWithRange:
3753
+ Description: 'Checks array slicing is done with endless ranges when suitable.'
3754
+ Enabled: pending
3755
+ VersionAdded: '0.83'
3756
+ Safe: false
3757
+
3733
3758
  Style/SpecialGlobalVars:
3734
3759
  Description: 'Avoid Perl-style global variables.'
3735
3760
  StyleGuide: '#no-cryptic-perlisms'
@@ -14,7 +14,6 @@ require_relative 'rubocop/core_ext/string'
14
14
  require_relative 'rubocop/path_util'
15
15
  require_relative 'rubocop/file_finder'
16
16
  require_relative 'rubocop/platform'
17
- require_relative 'rubocop/string_util'
18
17
  require_relative 'rubocop/name_similarity'
19
18
  require_relative 'rubocop/node_pattern'
20
19
  require_relative 'rubocop/string_interpreter'
@@ -213,6 +212,7 @@ require_relative 'rubocop/cop/layout/empty_line_after_magic_comment'
213
212
  require_relative 'rubocop/cop/layout/empty_line_between_defs'
214
213
  require_relative 'rubocop/cop/layout/empty_lines_around_access_modifier'
215
214
  require_relative 'rubocop/cop/layout/empty_lines_around_arguments'
215
+ require_relative 'rubocop/cop/layout/empty_lines_around_attribute_accessor'
216
216
  require_relative 'rubocop/cop/layout/empty_lines_around_begin_body'
217
217
  require_relative 'rubocop/cop/layout/empty_lines_around_block_body'
218
218
  require_relative 'rubocop/cop/layout/empty_lines_around_class_body'
@@ -542,6 +542,7 @@ require_relative 'rubocop/cop/style/send'
542
542
  require_relative 'rubocop/cop/style/signal_exception'
543
543
  require_relative 'rubocop/cop/style/single_line_block_params'
544
544
  require_relative 'rubocop/cop/style/single_line_methods'
545
+ require_relative 'rubocop/cop/style/slicing_with_range'
545
546
  require_relative 'rubocop/cop/style/special_global_vars'
546
547
  require_relative 'rubocop/cop/style/stabby_lambda_parentheses'
547
548
  require_relative 'rubocop/cop/style/stderr_puts'
@@ -8,6 +8,10 @@ module RuboCop
8
8
  class SendNode < Node
9
9
  include ParameterizedNode
10
10
  include MethodDispatchNode
11
+
12
+ def_node_matcher :attribute_accessor?, <<~PATTERN
13
+ (send nil? ${:attr_reader :attr_writer :attr_accessor :attr} $...)
14
+ PATTERN
11
15
  end
12
16
  end
13
17
  end
@@ -35,8 +35,8 @@ module RuboCop
35
35
  if @options[:init]
36
36
  run_command(:init)
37
37
  else
38
- validate_options_vs_config
39
38
  act_on_options
39
+ validate_options_vs_config
40
40
  apply_default_formatter
41
41
  execute_runners
42
42
  end
@@ -261,9 +261,13 @@ module RuboCop
261
261
 
262
262
  def enable_cop?(qualified_cop_name, cop_options)
263
263
  department = department_of(qualified_cop_name)
264
+ cop_enabled = cop_options.fetch('Enabled') do
265
+ !for_all_cops['DisabledByDefault']
266
+ end
267
+ return true if cop_enabled == 'override_department'
264
268
  return false if department && department['Enabled'] == false
265
269
 
266
- cop_options.fetch('Enabled') { !for_all_cops['DisabledByDefault'] }
270
+ cop_enabled
267
271
  end
268
272
 
269
273
  def department_of(qualified_cop_name)
@@ -36,7 +36,7 @@ module RuboCop
36
36
  FileFinder.root_level = nil
37
37
  end
38
38
 
39
- def load_file(file)
39
+ def load_file(file) # rubocop:disable Metrics/AbcSize
40
40
  path = File.absolute_path(file.is_a?(RemoteConfig) ? file.file : file)
41
41
 
42
42
  hash = load_yaml_configuration(path)
@@ -46,6 +46,7 @@ module RuboCop
46
46
 
47
47
  add_missing_namespaces(path, hash)
48
48
 
49
+ resolver.override_department_setting_for_cops({}, hash)
49
50
  resolver.resolve_inheritance_from_gems(hash)
50
51
  resolver.resolve_inheritance(path, hash, file, debug?)
51
52
 
@@ -132,9 +133,9 @@ module RuboCop
132
133
  '`false` in your `.rubocop.yml` file:').yellow
133
134
 
134
135
  pending_cops.each do |cop|
135
- warn Rainbow(
136
- " - #{cop.name} (#{cop.metadata['VersionAdded']})"
137
- ).yellow
136
+ version = cop.metadata['VersionAdded'] || 'N/A'
137
+
138
+ warn Rainbow(" - #{cop.name} (#{version})").yellow
138
139
  end
139
140
 
140
141
  warn Rainbow('For more information: https://docs.rubocop.org/en/latest/versioning/').yellow
@@ -217,7 +218,10 @@ module RuboCop
217
218
  end
218
219
 
219
220
  def load_yaml_configuration(absolute_path)
220
- yaml_code = read_file(absolute_path)
221
+ file_contents = read_file(absolute_path)
222
+ yaml_code = Dir.chdir(File.dirname(absolute_path)) do
223
+ ERB.new(file_contents).result
224
+ end
221
225
  check_duplication(yaml_code, absolute_path)
222
226
  hash = yaml_safe_load(yaml_code, absolute_path) || {}
223
227
 
@@ -241,8 +245,7 @@ module RuboCop
241
245
  "#{smart_path}:#{line1}: " \
242
246
  "`#{value}` is concealed by line #{line2}"
243
247
  else
244
- "#{smart_path}: " \
245
- "`#{value}` is concealed by duplicate"
248
+ "#{smart_path}: `#{value}` is concealed by duplicate"
246
249
  end
247
250
  warn Rainbow(message).yellow
248
251
  end
@@ -263,13 +266,11 @@ module RuboCop
263
266
  SafeYAML.load(yaml_code, filename, whitelisted_tags: %w[!ruby/regexp])
264
267
  # Ruby 2.6+
265
268
  elsif Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0')
266
- YAML.safe_load(
267
- yaml_code,
268
- permitted_classes: [Regexp, Symbol],
269
- permitted_symbols: [],
270
- aliases: true,
271
- filename: filename
272
- )
269
+ YAML.safe_load(yaml_code,
270
+ permitted_classes: [Regexp, Symbol],
271
+ permitted_symbols: [],
272
+ aliases: true,
273
+ filename: filename)
273
274
  else
274
275
  YAML.safe_load(yaml_code, [Regexp, Symbol], [], true, filename)
275
276
  end
@@ -17,10 +17,12 @@ module RuboCop
17
17
  end
18
18
  end
19
19
 
20
+ # rubocop:disable Metrics/MethodLength
20
21
  def resolve_inheritance(path, hash, file, debug)
21
22
  inherited_files = Array(hash['inherit_from'])
22
23
  base_configs(path, inherited_files, file)
23
24
  .reverse.each_with_index do |base_config, index|
25
+ override_department_setting_for_cops(base_config, hash)
24
26
  base_config.each do |k, v|
25
27
  next unless v.is_a?(Hash)
26
28
 
@@ -34,6 +36,7 @@ module RuboCop
34
36
  end
35
37
  end
36
38
  end
39
+ # rubocop:enable Metrics/MethodLength
37
40
 
38
41
  def resolve_inheritance_from_gems(hash)
39
42
  gems = hash.delete('inherit_gem')
@@ -100,8 +103,32 @@ module RuboCop
100
103
  end
101
104
  # rubocop:enable Metrics/AbcSize
102
105
 
106
+ # An `Enabled: true` setting in user configuration for a cop overrides an
107
+ # `Enabled: false` setting for its department.
108
+ def override_department_setting_for_cops(base_hash, derived_hash)
109
+ derived_hash.each_key do |key|
110
+ next unless key =~ %r{(.*)/.*}
111
+
112
+ department = Regexp.last_match(1)
113
+ next unless disabled?(derived_hash, department) ||
114
+ disabled?(base_hash, department)
115
+
116
+ # The `override_department` setting for the `Enabled` parameter is an
117
+ # internal setting that's not documented in the manual. It will cause a
118
+ # cop to be enabled later, when logic surrounding enabled/disabled it
119
+ # run, even though its department is disabled.
120
+ if derived_hash[key]['Enabled']
121
+ derived_hash[key]['Enabled'] = 'override_department'
122
+ end
123
+ end
124
+ end
125
+
103
126
  private
104
127
 
128
+ def disabled?(hash, department)
129
+ hash[department] && hash[department]['Enabled'] == false
130
+ end
131
+
105
132
  def duplicate_setting?(base_hash, derived_hash, key, inherited_file)
106
133
  return false if inherited_file.nil? # Not inheritance resolving merge
107
134
  return false if inherited_file.start_with?('..') # Legitimate override
@@ -206,7 +206,8 @@ module RuboCop
206
206
  SafeAutoCorrect
207
207
  AutoCorrect].include?(key) && value.is_a?(String)
208
208
 
209
- next if key == 'Enabled' && value == 'pending'
209
+ next if key == 'Enabled' &&
210
+ %w[pending override_department].include?(value)
210
211
 
211
212
  raise ValidationError, msg_not_boolean(parent, key, value)
212
213
  end
@@ -132,11 +132,12 @@ module RuboCop
132
132
  ).inject
133
133
  end
134
134
 
135
- def inject_config(config_file_path: 'config/default.yml')
135
+ def inject_config(config_file_path: 'config/default.yml',
136
+ version_added: bump_minor_version)
136
137
  injector =
137
138
  ConfigurationInjector.new(configuration_file_path: config_file_path,
138
139
  badge: badge,
139
- version_added: bump_minor_version)
140
+ version_added: version_added)
140
141
 
141
142
  injector.inject do
142
143
  output.puts(format(CONFIGURATION_ADDED_MESSAGE,
@@ -23,6 +23,8 @@ module RuboCop
23
23
  # do_something
24
24
  # end
25
25
  class ConditionPosition < Cop
26
+ include RangeHelp
27
+
26
28
  MSG = 'Place the condition on the same line as `%<keyword>s`.'
27
29
 
28
30
  def on_if(node)
@@ -34,9 +36,17 @@ module RuboCop
34
36
  def on_while(node)
35
37
  check(node)
36
38
  end
39
+ alias on_until on_while
37
40
 
38
- def on_until(node)
39
- check(node)
41
+ def autocorrect(node)
42
+ lambda do |corrector|
43
+ range = range_by_whole_lines(
44
+ node.source_range, include_final_newline: true
45
+ )
46
+
47
+ corrector.insert_after(node.parent.loc.keyword, " #{node.source}")
48
+ corrector.remove(range)
49
+ end
40
50
  end
41
51
 
42
52
  private
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Layout
6
+ # Checks for a newline after attribute accessor.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # attr_accessor :foo
11
+ # def do_something
12
+ # end
13
+ #
14
+ # # good
15
+ # attr_accessor :foo
16
+ #
17
+ # def do_something
18
+ # end
19
+ #
20
+ # # good
21
+ # attr_accessor :foo
22
+ # attr_reader :bar
23
+ # attr_writer :baz
24
+ # attr :qux
25
+ #
26
+ # def do_something
27
+ # end
28
+ #
29
+ class EmptyLinesAroundAttributeAccessor < Cop
30
+ include RangeHelp
31
+
32
+ MSG = 'Add an empty line after attribute accessor.'
33
+
34
+ def on_send(node)
35
+ return unless node.attribute_accessor?
36
+ return if next_line_empty?(node.last_line)
37
+
38
+ next_line_node = next_line_node(node)
39
+ return if next_line_node.nil? || attribute_accessor?(next_line_node)
40
+
41
+ add_offense(node)
42
+ end
43
+
44
+ def autocorrect(node)
45
+ lambda do |corrector|
46
+ range = range_by_whole_lines(node.source_range)
47
+
48
+ corrector.insert_after(range, "\n")
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def next_line_empty?(line)
55
+ processed_source[line].blank?
56
+ end
57
+
58
+ def next_line_node(node)
59
+ node.parent.children[node.sibling_index + 1]
60
+ end
61
+
62
+ def attribute_accessor?(node)
63
+ node.send_type? && node.attribute_accessor?
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end