rubocop 1.40.0 → 1.41.0

Sign up to get free protection for your applications and to get access to all the features.
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