rubocop 1.40.0 → 1.41.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +18 -0
  4. data/lib/rubocop/config.rb +28 -5
  5. data/lib/rubocop/config_loader.rb +9 -0
  6. data/lib/rubocop/config_validator.rb +1 -1
  7. data/lib/rubocop/cop/badge.rb +9 -4
  8. data/lib/rubocop/cop/base.rb +25 -9
  9. data/lib/rubocop/cop/commissioner.rb +8 -3
  10. data/lib/rubocop/cop/cop.rb +1 -1
  11. data/lib/rubocop/cop/internal_affairs/cop_description.rb +3 -1
  12. data/lib/rubocop/cop/layout/empty_lines.rb +2 -0
  13. data/lib/rubocop/cop/layout/extra_spacing.rb +10 -6
  14. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +38 -2
  15. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +49 -2
  16. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +61 -2
  17. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +52 -2
  18. data/lib/rubocop/cop/layout/indentation_style.rb +3 -1
  19. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +5 -0
  20. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +7 -1
  21. data/lib/rubocop/cop/layout/line_length.rb +2 -0
  22. data/lib/rubocop/cop/layout/multiline_array_line_breaks.rb +51 -2
  23. data/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb +49 -2
  24. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +53 -2
  25. data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +58 -2
  26. data/lib/rubocop/cop/layout/redundant_line_break.rb +2 -2
  27. data/lib/rubocop/cop/layout/trailing_empty_lines.rb +1 -1
  28. data/lib/rubocop/cop/layout/trailing_whitespace.rb +6 -2
  29. data/lib/rubocop/cop/lint/constant_resolution.rb +4 -0
  30. data/lib/rubocop/cop/lint/debugger.rb +3 -1
  31. data/lib/rubocop/cop/lint/duplicate_branch.rb +0 -2
  32. data/lib/rubocop/cop/lint/duplicate_methods.rb +19 -8
  33. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +10 -5
  34. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +1 -1
  35. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +8 -19
  36. data/lib/rubocop/cop/metrics/class_length.rb +1 -1
  37. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  38. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +2 -2
  39. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  40. data/lib/rubocop/cop/mixin/allowed_identifiers.rb +2 -2
  41. data/lib/rubocop/cop/mixin/annotation_comment.rb +13 -6
  42. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +21 -9
  43. data/lib/rubocop/cop/mixin/first_element_line_break.rb +11 -7
  44. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +5 -1
  45. data/lib/rubocop/cop/mixin/line_length_help.rb +8 -1
  46. data/lib/rubocop/cop/mixin/method_complexity.rb +5 -3
  47. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +5 -3
  48. data/lib/rubocop/cop/mixin/percent_array.rb +3 -5
  49. data/lib/rubocop/cop/mixin/require_library.rb +2 -0
  50. data/lib/rubocop/cop/mixin/rescue_node.rb +3 -3
  51. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  52. data/lib/rubocop/cop/naming/class_and_module_camel_case.rb +2 -0
  53. data/lib/rubocop/cop/naming/inclusive_language.rb +4 -1
  54. data/lib/rubocop/cop/registry.rb +6 -3
  55. data/lib/rubocop/cop/style/concat_array_literals.rb +66 -0
  56. data/lib/rubocop/cop/style/documentation.rb +1 -1
  57. data/lib/rubocop/cop/style/guard_clause.rb +5 -1
  58. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -3
  59. data/lib/rubocop/cop/style/inverse_methods.rb +2 -0
  60. data/lib/rubocop/cop/style/line_end_concatenation.rb +4 -1
  61. data/lib/rubocop/cop/style/redundant_constant_base.rb +13 -0
  62. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +39 -0
  63. data/lib/rubocop/cop/style/require_order.rb +61 -9
  64. data/lib/rubocop/cop/style/semicolon.rb +2 -1
  65. data/lib/rubocop/cop/util.rb +31 -4
  66. data/lib/rubocop/cops_documentation_generator.rb +22 -3
  67. data/lib/rubocop/directive_comment.rb +1 -1
  68. data/lib/rubocop/file_patterns.rb +43 -0
  69. data/lib/rubocop/options.rb +1 -1
  70. data/lib/rubocop/path_util.rb +20 -14
  71. data/lib/rubocop/target_finder.rb +1 -1
  72. data/lib/rubocop/version.rb +1 -1
  73. data/lib/rubocop.rb +3 -1
  74. metadata +6 -4
  75. data/lib/rubocop/optimized_patterns.rb +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb2b7b02733dca8e7577cd5ccd776044179cd762685152d9c7d5a739dd7cf9e1
4
- data.tar.gz: 47b6ef2704e65f30af91def68b22331aca13d629d954cc860288738dccd4f14d
3
+ metadata.gz: 77b5fe782c4a109e75c380273e08f38b9c61eb519f11f19a606a380080f1f0a2
4
+ data.tar.gz: fa67b5a3f120d6f1538963f9ddbc8bd0508b7f54763bd7c76da725054c9e0459
5
5
  SHA512:
6
- metadata.gz: c65cd2840052b36832dec3218f2994b0dbaae5e482328c767b0f82ac3d882ce1a29a02ddd53e960c199514ff241d17d0c5f33b139b50634fce4c7fa246018a1c
7
- data.tar.gz: 51442c821afd72669bb0845386c1c05495618736b46978e1d2fae12f3f69f30ff914025209f6efc2c673692e585e4e2968fe76467f2433ba98f2209d435b46bd
6
+ metadata.gz: 1475ad1af84e34c10ccf4905247b6e1090e0bd2969d6eb5ef2c4f4dec116841d438b42c5f4c84678870ffac0703f663e1f1ea79ce588b74caca27162f5ad6c07
7
+ data.tar.gz: 267189e76baac7ebc2632d3249957987ba9a3db3df26fa1bf7abe4510a7f9baf8cf5bec399b024d1e900accd1b55f2a5c72e8ab3686d1edc656272ef9723bdaf
data/README.md CHANGED
@@ -53,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
53
53
  in your `Gemfile`:
54
54
 
55
55
  ```rb
56
- gem 'rubocop', '~> 1.40', require: false
56
+ gem 'rubocop', '~> 1.41', require: false
57
57
  ```
58
58
 
59
59
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -762,6 +762,7 @@ Layout/FirstArrayElementLineBreak:
762
762
  multi-line array.
763
763
  Enabled: false
764
764
  VersionAdded: '0.49'
765
+ AllowMultilineFinalElement: false
765
766
 
766
767
  Layout/FirstHashElementIndentation:
767
768
  Description: 'Checks the indentation of the first key in a hash literal.'
@@ -794,6 +795,7 @@ Layout/FirstHashElementLineBreak:
794
795
  multi-line hash.
795
796
  Enabled: false
796
797
  VersionAdded: '0.49'
798
+ AllowMultilineFinalElement: false
797
799
 
798
800
  Layout/FirstMethodArgumentLineBreak:
799
801
  Description: >-
@@ -801,6 +803,7 @@ Layout/FirstMethodArgumentLineBreak:
801
803
  multi-line method call.
802
804
  Enabled: false
803
805
  VersionAdded: '0.49'
806
+ AllowMultilineFinalElement: false
804
807
 
805
808
  Layout/FirstMethodParameterLineBreak:
806
809
  Description: >-
@@ -808,6 +811,7 @@ Layout/FirstMethodParameterLineBreak:
808
811
  multi-line method parameter definition.
809
812
  Enabled: false
810
813
  VersionAdded: '0.49'
814
+ AllowMultilineFinalElement: false
811
815
 
812
816
  Layout/FirstParameterIndentation:
813
817
  Description: >-
@@ -1067,6 +1071,7 @@ Layout/MultilineArrayLineBreaks:
1067
1071
  starts on a separate line.
1068
1072
  Enabled: false
1069
1073
  VersionAdded: '0.67'
1074
+ AllowMultilineFinalElement: false
1070
1075
 
1071
1076
  Layout/MultilineAssignmentLayout:
1072
1077
  Description: 'Check for a newline after the assignment operator in multi-line assignments.'
@@ -1117,6 +1122,7 @@ Layout/MultilineHashKeyLineBreaks:
1117
1122
  starts on a separate line.
1118
1123
  Enabled: false
1119
1124
  VersionAdded: '0.67'
1125
+ AllowMultilineFinalElement: false
1120
1126
 
1121
1127
  Layout/MultilineMethodArgumentLineBreaks:
1122
1128
  Description: >-
@@ -1124,6 +1130,7 @@ Layout/MultilineMethodArgumentLineBreaks:
1124
1130
  starts on a separate line.
1125
1131
  Enabled: false
1126
1132
  VersionAdded: '0.67'
1133
+ AllowMultilineFinalElement: false
1127
1134
 
1128
1135
  Layout/MultilineMethodCallBraceLayout:
1129
1136
  Description: >-
@@ -1178,6 +1185,7 @@ Layout/MultilineMethodParameterLineBreaks:
1178
1185
  starts on a separate line.
1179
1186
  Enabled: false
1180
1187
  VersionAdded: '1.32'
1188
+ AllowMultilineFinalElement: false
1181
1189
 
1182
1190
  Layout/MultilineOperationIndentation:
1183
1191
  Description: >-
@@ -3418,6 +3426,11 @@ Style/CommentedKeyword:
3418
3426
  VersionAdded: '0.51'
3419
3427
  VersionChanged: '1.19'
3420
3428
 
3429
+ Style/ConcatArrayLiterals:
3430
+ Description: 'Enforces the use of `Array#push(item)` instead of `Array#concat([item])` to avoid redundant array literals.'
3431
+ Enabled: pending
3432
+ VersionAdded: '1.41'
3433
+
3421
3434
  Style/ConditionalAssignment:
3422
3435
  Description: >-
3423
3436
  Use the return value of `if` and `case` statements for
@@ -4731,6 +4744,11 @@ Style/RedundantConstantBase:
4731
4744
  Enabled: pending
4732
4745
  VersionAdded: '1.40'
4733
4746
 
4747
+ Style/RedundantDoubleSplatHashBraces:
4748
+ Description: 'Checks for redundant uses of double splat hash braces.'
4749
+ Enabled: pending
4750
+ VersionAdded: '1.41'
4751
+
4734
4752
  Style/RedundantEach:
4735
4753
  Description: 'Checks for redundant `each`.'
4736
4754
  Enabled: pending
@@ -21,6 +21,7 @@ module RuboCop
21
21
  DEFAULT_RAILS_VERSION = 5.0
22
22
  attr_reader :loaded_path
23
23
 
24
+ # rubocop:disable Metrics/AbcSize
24
25
  def initialize(hash = {}, loaded_path = nil)
25
26
  @loaded_path = loaded_path
26
27
  @for_cop = Hash.new do |h, cop|
@@ -32,7 +33,11 @@ module RuboCop
32
33
  end
33
34
  @hash = hash
34
35
  @validator = ConfigValidator.new(self)
36
+
37
+ @badge_config_cache = {}.compare_by_identity
38
+ @clusivity_config_exists_cache = {}
35
39
  end
40
+ # rubocop:enable Metrics/AbcSize
36
41
 
37
42
  def self.create(hash, path, check: true)
38
43
  config = new(hash, path)
@@ -123,8 +128,25 @@ module RuboCop
123
128
  # @return [Config] for the given cop merged with that of its department (if any)
124
129
  # Note: the 'Enabled' attribute is same as that returned by `for_cop`
125
130
  def for_badge(badge)
126
- cop_config = for_cop(badge.to_s)
127
- fetch(badge.department.to_s) { return cop_config }.merge(cop_config)
131
+ @badge_config_cache[badge] ||= begin
132
+ department_config = self[badge.department_name]
133
+ cop_config = for_cop(badge.to_s)
134
+ if department_config
135
+ department_config.merge(cop_config)
136
+ else
137
+ cop_config
138
+ end
139
+ end
140
+ end
141
+
142
+ # @return [Boolean] whether config for this badge has 'Include' or 'Exclude' keys
143
+ # @api private
144
+ def clusivity_config_for_badge?(badge)
145
+ exists = @clusivity_config_exists_cache[badge.to_s]
146
+ return exists unless exists.nil?
147
+
148
+ cop_config = for_badge(badge)
149
+ @clusivity_config_exists_cache[badge.to_s] = cop_config['Include'] || cop_config['Exclude']
128
150
  end
129
151
 
130
152
  # @return [Config] for the given department name.
@@ -273,9 +295,10 @@ module RuboCop
273
295
  return nil unless lock_file_path
274
296
 
275
297
  File.foreach(lock_file_path) do |line|
276
- # If rails is in Gemfile.lock or gems.lock, there should be a line like:
277
- # rails (X.X.X)
278
- result = line.match(/^\s+rails\s+\((\d+\.\d+)/)
298
+ # If Rails (or one of its frameworks) is in Gemfile.lock or gems.lock, there should be
299
+ # a line like:
300
+ # railties (X.X.X)
301
+ result = line.match(/^\s+railties\s+\((\d+\.\d+)/)
279
302
  return result.captures.first.to_f if result
280
303
  end
281
304
  end
@@ -137,6 +137,15 @@ module RuboCop
137
137
  end
138
138
  end
139
139
 
140
+ # @api private
141
+ def inject_defaults!(project_root)
142
+ path = File.join(project_root, 'config', 'default.yml')
143
+ config = load_file(path)
144
+ new_config = ConfigLoader.merge_with_default(config, path)
145
+ puts "configuration from #{path}" if debug?
146
+ @default_configuration = new_config
147
+ end
148
+
140
149
  # Returns the path RuboCop inferred as the root of the project. No file
141
150
  # searches will go past this directory.
142
151
  # @deprecated Use `RuboCop::ConfigFinder.project_root` instead.
@@ -162,7 +162,7 @@ module RuboCop
162
162
  return unless syntax_config && default_config.merge(syntax_config) != default_config
163
163
 
164
164
  raise ValidationError,
165
- "configuration for Syntax cop found in #{smart_loaded_path}\n" \
165
+ "configuration for Lint/Syntax cop found in #{smart_loaded_path}\n" \
166
166
  'It\'s not possible to disable this cop.'
167
167
  end
168
168
 
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # allow for badge references in source files that omit the department for
11
11
  # RuboCop to infer.
12
12
  class Badge
13
- attr_reader :department, :cop_name
13
+ attr_reader :department, :department_name, :cop_name
14
14
 
15
15
  def self.for(class_name)
16
16
  parts = class_name.split('::')
@@ -18,19 +18,23 @@ module RuboCop
18
18
  new(name_deep_enough ? parts[2..] : parts.last(2))
19
19
  end
20
20
 
21
+ @parse_cache = {}
22
+
21
23
  def self.parse(identifier)
22
- new(identifier.split('/').map { |i| camel_case(i) })
24
+ @parse_cache[identifier] ||= new(identifier.split('/').map! { |i| camel_case(i) })
23
25
  end
24
26
 
25
27
  def self.camel_case(name_part)
26
28
  return 'RSpec' if name_part == 'rspec'
29
+ return name_part unless name_part.match?(/^[a-z]|_[a-z]/)
27
30
 
28
- name_part.gsub(/^\w|_\w/) { |match| match[-1, 1].upcase }
31
+ name_part.gsub(/^[a-z]|_[a-z]/) { |match| match[-1, 1].upcase }
29
32
  end
30
33
 
31
34
  def initialize(class_name_parts)
32
35
  department_parts = class_name_parts[0...-1]
33
36
  @department = (department_parts.join('/').to_sym unless department_parts.empty?)
37
+ @department_name = @department&.to_s
34
38
  @cop_name = class_name_parts.last
35
39
  end
36
40
 
@@ -40,7 +44,8 @@ module RuboCop
40
44
  alias eql? ==
41
45
 
42
46
  def hash
43
- [department, cop_name].hash
47
+ # Do hashing manually to reduce Array allocations.
48
+ department.hash ^ cop_name.hash # rubocop:disable Security/CompoundHash
44
49
  end
45
50
 
46
51
  def match?(other)
@@ -107,8 +107,7 @@ module RuboCop
107
107
  def add_global_offense(message = nil, severity: nil)
108
108
  severity = find_severity(nil, severity)
109
109
  message = find_message(nil, message)
110
- @current_offenses <<
111
- Offense.new(severity, Offense::NO_LOCATION, message, name, :unsupported)
110
+ current_offenses << Offense.new(severity, Offense::NO_LOCATION, message, name, :unsupported)
112
111
  end
113
112
 
114
113
  # Adds an offense on the specified range (or node with an expression)
@@ -126,7 +125,7 @@ module RuboCop
126
125
 
127
126
  status, corrector = enabled_line?(range.line) ? correct(range, &block) : :disabled
128
127
 
129
- @current_offenses << Offense.new(severity, range, message, name, status, corrector)
128
+ current_offenses << Offense.new(severity, range, message, name, status, corrector)
130
129
  end
131
130
 
132
131
  # This method should be overridden when a cop's behavior depends
@@ -187,7 +186,7 @@ module RuboCop
187
186
  def self.match?(given_names)
188
187
  return false unless given_names
189
188
 
190
- given_names.include?(cop_name) || given_names.include?(department.to_s)
189
+ given_names.include?(cop_name) || given_names.include?(badge.department_name)
191
190
  end
192
191
 
193
192
  def cop_name
@@ -225,6 +224,8 @@ module RuboCop
225
224
  end
226
225
 
227
226
  def relevant_file?(file)
227
+ return true unless @config.clusivity_config_for_badge?(self.class.badge)
228
+
228
229
  file == RuboCop::AST::ProcessedSource::STRING_SOURCE_NAME ||
229
230
  (file_name_matches_any?(file, 'Include', true) &&
230
231
  !file_name_matches_any?(file, 'Exclude', false))
@@ -292,7 +293,7 @@ module RuboCop
292
293
  end
293
294
 
294
295
  def apply_correction(corrector)
295
- @current_corrector&.merge!(corrector) if corrector
296
+ current_corrector&.merge!(corrector) if corrector
296
297
  end
297
298
 
298
299
  ### Reserved for Commissioner:
@@ -305,22 +306,37 @@ module RuboCop
305
306
  @currently_disabled_lines ||= Set.new
306
307
  end
307
308
 
309
+ def current_corrector
310
+ @current_corrector ||= Corrector.new(@processed_source) if @processed_source.valid_syntax?
311
+ end
312
+
313
+ def current_offenses
314
+ @current_offenses ||= []
315
+ end
316
+
308
317
  private_class_method def self.restrict_on_send
309
318
  @restrict_on_send ||= self::RESTRICT_ON_SEND.to_a.freeze
310
319
  end
311
320
 
312
321
  # Called before any investigation
313
322
  def begin_investigation(processed_source)
314
- @current_offenses = []
323
+ @current_offenses = nil
315
324
  @current_offense_locations = nil
316
325
  @currently_disabled_lines = nil
317
326
  @processed_source = processed_source
318
- @current_corrector = Corrector.new(@processed_source) if @processed_source.valid_syntax?
327
+ @current_corrector = nil
319
328
  end
320
329
 
330
+ # rubocop:disable Layout/ClassStructure
331
+ EMPTY_OFFENSES = [].freeze
332
+ private_constant :EMPTY_OFFENSES
333
+ # rubocop:enable Layout/ClassStructure
334
+
321
335
  # Called to complete an investigation
322
336
  def complete_investigation
323
- InvestigationReport.new(self, processed_source, @current_offenses, @current_corrector)
337
+ InvestigationReport.new(
338
+ self, processed_source, @current_offenses || EMPTY_OFFENSES, @current_corrector
339
+ )
324
340
  ensure
325
341
  reset_investigation
326
342
  end
@@ -412,7 +428,7 @@ module RuboCop
412
428
  patterns = cop_config[parameter]
413
429
  return default_result unless patterns
414
430
 
415
- patterns = OptimizedPatterns.from(patterns)
431
+ patterns = FilePatterns.from(patterns)
416
432
  patterns.match?(config.path_relative_to_config(file)) || patterns.match?(file)
417
433
  end
418
434
 
@@ -82,7 +82,8 @@ module RuboCop
82
82
  @cops.each { |cop| cop.send :begin_investigation, processed_source }
83
83
  if processed_source.valid_syntax?
84
84
  invoke(:on_new_investigation, @cops)
85
- invoke(:investigate, @forces, processed_source)
85
+ invoke_with_argument(:investigate, @forces, processed_source)
86
+
86
87
  walk(processed_source.ast) unless @cops.empty?
87
88
  invoke(:on_investigation_end, @cops)
88
89
  else
@@ -149,8 +150,12 @@ module RuboCop
149
150
  map
150
151
  end
151
152
 
152
- def invoke(callback, cops, *args)
153
- cops.each { |cop| with_cop_error_handling(cop) { cop.send(callback, *args) } }
153
+ def invoke(callback, cops)
154
+ cops.each { |cop| with_cop_error_handling(cop) { cop.send(callback) } }
155
+ end
156
+
157
+ def invoke_with_argument(callback, cops, arg)
158
+ cops.each { |cop| with_cop_error_handling(cop) { cop.send(callback, arg) } }
154
159
  end
155
160
 
156
161
  # Allow blind rescues here, since we're absorbing and packaging or
@@ -97,7 +97,7 @@ module RuboCop
97
97
 
98
98
  def begin_investigation(processed_source)
99
99
  super
100
- @offenses = @current_offenses
100
+ @offenses = current_offenses
101
101
  @last_corrector = @current_corrector
102
102
  end
103
103
 
@@ -28,8 +28,9 @@ module RuboCop
28
28
  /^\s+# This cop (?<special>#{SPECIAL_WORDS.join('|')})?\s*(?<word>.+?) .*/.freeze
29
29
  REPLACEMENT_REGEX = /^\s+# This cop (#{SPECIAL_WORDS.join('|')})?\s*(.+?) /.freeze
30
30
 
31
+ # rubocop:disable Metrics/CyclomaticComplexity
31
32
  def on_class(node)
32
- return unless (module_node = node.parent)
33
+ return unless (module_node = node.parent) && node.parent_class
33
34
 
34
35
  description_beginning = first_comment_line(module_node)
35
36
  return unless description_beginning
@@ -48,6 +49,7 @@ module RuboCop
48
49
  end
49
50
  end
50
51
  end
52
+ # rubocop:enable Metrics/CyclomaticComplexity
51
53
 
52
54
  private
53
55
 
@@ -27,6 +27,8 @@ module RuboCop
27
27
 
28
28
  def on_new_investigation
29
29
  return if processed_source.tokens.empty?
30
+ # Quick check if we possibly have consecutive blank lines.
31
+ return unless processed_source.raw_source.include?("\n\n\n")
30
32
 
31
33
  lines = Set.new
32
34
  processed_source.each_token { |token| lines << token.line }
@@ -119,12 +119,16 @@ module RuboCop
119
119
  def ignored_ranges(ast)
120
120
  return [] unless ast
121
121
 
122
- @ignored_ranges ||= on_node(:pair, ast).map do |pair|
123
- next if pair.parent.single_line?
124
-
125
- key, value = *pair
126
- key.source_range.end_pos...value.source_range.begin_pos
127
- end.compact
122
+ @ignored_ranges ||= begin
123
+ ranges = []
124
+ on_node(:pair, ast) do |pair|
125
+ next if pair.parent.single_line?
126
+
127
+ key, value = *pair
128
+ ranges << (key.source_range.end_pos...value.source_range.begin_pos)
129
+ end
130
+ ranges
131
+ end
128
132
  end
129
133
 
130
134
  def force_equal_sign_alignment?
@@ -6,17 +6,49 @@ module RuboCop
6
6
  # Checks for a line break before the first element in a
7
7
  # multi-line array.
8
8
  #
9
- # @example
9
+ # @example AllowMultilineFinalElement: false (default)
10
+ #
11
+ # # bad
12
+ # [ :a,
13
+ # :b]
14
+ #
15
+ # # bad
16
+ # [ :a, {
17
+ # :b => :c
18
+ # }]
19
+ #
20
+ # # good
21
+ # [:a, :b]
22
+ #
23
+ # # good
24
+ # [
25
+ # :a,
26
+ # :b]
27
+ #
28
+ # # good
29
+ # [
30
+ # :a, {
31
+ # :b => :c
32
+ # }]
33
+ #
34
+ # @example AllowMultilineFinalElement: true
10
35
  #
11
36
  # # bad
12
37
  # [ :a,
13
38
  # :b]
14
39
  #
15
40
  # # good
41
+ # [ :a, {
42
+ # :b => :c
43
+ # }]
44
+ #
45
+ # # good
16
46
  # [
17
47
  # :a,
18
48
  # :b]
19
49
  #
50
+ # # good
51
+ # [:a, :b]
20
52
  class FirstArrayElementLineBreak < Base
21
53
  include FirstElementLineBreak
22
54
  extend AutoCorrector
@@ -26,7 +58,7 @@ module RuboCop
26
58
  def on_array(node)
27
59
  return if !node.loc.begin && !assignment_on_same_line?(node)
28
60
 
29
- check_children_line_break(node, node.children)
61
+ check_children_line_break(node, node.children, ignore_last: ignore_last_element?)
30
62
  end
31
63
 
32
64
  private
@@ -35,6 +67,10 @@ module RuboCop
35
67
  source = node.source_range.source_line[0...node.loc.column]
36
68
  /\s*=\s*$/.match?(source)
37
69
  end
70
+
71
+ def ignore_last_element?
72
+ !!cop_config['AllowMultilineFinalElement']
73
+ end
38
74
  end
39
75
  end
40
76
  end
@@ -6,16 +6,55 @@ module RuboCop
6
6
  # Checks for a line break before the first element in a
7
7
  # multi-line hash.
8
8
  #
9
- # @example
9
+ # @example AllowMultilineFinalElement: false (default)
10
10
  #
11
11
  # # bad
12
12
  # { a: 1,
13
13
  # b: 2}
14
14
  #
15
+ # # bad
16
+ # { a: 1, b: {
17
+ # c: 3
18
+ # }}
19
+ #
15
20
  # # good
16
21
  # {
17
22
  # a: 1,
18
23
  # b: 2 }
24
+ #
25
+ # # good
26
+ # {
27
+ # a: 1, b: {
28
+ # c: 3
29
+ # }}
30
+ #
31
+ # @example AllowMultilineFinalElement: true
32
+ #
33
+ # # bad
34
+ # { a: 1,
35
+ # b: 2}
36
+ #
37
+ # # bad
38
+ # { a: 1,
39
+ # b: {
40
+ # c: 3
41
+ # }}
42
+ #
43
+ # # good
44
+ # { a: 1, b: {
45
+ # c: 3
46
+ # }}
47
+ #
48
+ # # good
49
+ # {
50
+ # a: 1,
51
+ # b: 2 }
52
+ #
53
+ # # good
54
+ # {
55
+ # a: 1, b: {
56
+ # c: 3
57
+ # }}
19
58
  class FirstHashElementLineBreak < Base
20
59
  include FirstElementLineBreak
21
60
  extend AutoCorrector
@@ -25,7 +64,15 @@ module RuboCop
25
64
  def on_hash(node)
26
65
  # node.loc.begin tells us whether the hash opens with a {
27
66
  # If it doesn't, Style/FirstMethodArgumentLineBreak will handle it
28
- check_children_line_break(node, node.children) if node.loc.begin
67
+ return unless node.loc.begin
68
+
69
+ check_children_line_break(node, node.children, ignore_last: ignore_last_element?)
70
+ end
71
+
72
+ private
73
+
74
+ def ignore_last_element?
75
+ !!cop_config['AllowMultilineFinalElement']
29
76
  end
30
77
  end
31
78
  end
@@ -6,17 +6,70 @@ module RuboCop
6
6
  # Checks for a line break before the first argument in a
7
7
  # multi-line method call.
8
8
  #
9
- # @example
9
+ # @example AllowMultilineFinalElement: false (default)
10
10
  #
11
11
  # # bad
12
12
  # method(foo, bar,
13
13
  # baz)
14
14
  #
15
+ # # bad
16
+ # method(foo, bar, {
17
+ # baz: "a",
18
+ # qux: "b",
19
+ # })
20
+ #
15
21
  # # good
16
22
  # method(
17
23
  # foo, bar,
18
24
  # baz)
19
25
  #
26
+ # # good
27
+ # method(
28
+ # foo, bar, {
29
+ # baz: "a",
30
+ # qux: "b",
31
+ # })
32
+ #
33
+ # # ignored
34
+ # method foo, bar,
35
+ # baz
36
+ #
37
+ # @example AllowMultilineFinalElement: true
38
+ #
39
+ # # bad
40
+ # method(foo, bar,
41
+ # baz)
42
+ #
43
+ # # bad
44
+ # method(foo,
45
+ # bar,
46
+ # {
47
+ # baz: "a",
48
+ # qux: "b",
49
+ # }
50
+ # )
51
+ #
52
+ # # good
53
+ # method(foo, bar, {
54
+ # baz: "a",
55
+ # qux: "b",
56
+ # })
57
+ #
58
+ # # good
59
+ # method(
60
+ # foo, bar,
61
+ # baz)
62
+ #
63
+ # # good
64
+ # method(
65
+ # foo,
66
+ # bar,
67
+ # {
68
+ # baz: "a",
69
+ # qux: "b",
70
+ # }
71
+ # )
72
+ #
20
73
  # # ignored
21
74
  # method foo, bar,
22
75
  # baz
@@ -38,10 +91,16 @@ module RuboCop
38
91
  last_arg = args.last
39
92
  args.concat(args.pop.children) if last_arg&.hash_type? && !last_arg&.braces?
40
93
 
41
- check_method_line_break(node, args)
94
+ check_method_line_break(node, args, ignore_last: ignore_last_element?)
42
95
  end
43
96
  alias on_csend on_send
44
97
  alias on_super on_send
98
+
99
+ private
100
+
101
+ def ignore_last_element?
102
+ !!cop_config['AllowMultilineFinalElement']
103
+ end
45
104
  end
46
105
  end
47
106
  end