rubocop 0.77.0 → 0.81.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 (129) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +3 -3
  4. data/config/default.yml +136 -60
  5. data/lib/rubocop.rb +20 -4
  6. data/lib/rubocop/ast/builder.rb +45 -42
  7. data/lib/rubocop/ast/node.rb +11 -18
  8. data/lib/rubocop/ast/node/block_node.rb +5 -1
  9. data/lib/rubocop/ast/node/case_match_node.rb +56 -0
  10. data/lib/rubocop/ast/node/def_node.rb +11 -0
  11. data/lib/rubocop/ast/node/forward_args_node.rb +18 -0
  12. data/lib/rubocop/ast/node/regexp_node.rb +2 -4
  13. data/lib/rubocop/ast/traversal.rb +29 -10
  14. data/lib/rubocop/cli/command/auto_genenerate_config.rb +7 -7
  15. data/lib/rubocop/cli/command/show_cops.rb +11 -4
  16. data/lib/rubocop/comment_config.rb +6 -1
  17. data/lib/rubocop/config.rb +28 -10
  18. data/lib/rubocop/config_loader.rb +19 -19
  19. data/lib/rubocop/config_obsoletion.rb +6 -4
  20. data/lib/rubocop/config_validator.rb +55 -95
  21. data/lib/rubocop/cop/autocorrect_logic.rb +7 -4
  22. data/lib/rubocop/cop/bundler/insecure_protocol_source.rb +2 -2
  23. data/lib/rubocop/cop/cop.rb +3 -1
  24. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  25. data/lib/rubocop/cop/generator.rb +3 -4
  26. data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
  27. data/lib/rubocop/cop/layout/array_alignment.rb +53 -10
  28. data/lib/rubocop/cop/layout/block_end_newline.rb +5 -3
  29. data/lib/rubocop/cop/layout/else_alignment.rb +8 -0
  30. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +2 -1
  31. data/lib/rubocop/cop/layout/first_argument_indentation.rb +2 -2
  32. data/lib/rubocop/cop/layout/hash_alignment.rb +8 -4
  33. data/lib/rubocop/cop/layout/heredoc_indentation.rb +4 -4
  34. data/lib/rubocop/cop/layout/leading_comment_space.rb +33 -2
  35. data/lib/rubocop/cop/{metrics → layout}/line_length.rb +35 -79
  36. data/lib/rubocop/cop/layout/multiline_block_layout.rb +14 -5
  37. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +0 -4
  38. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +1 -1
  39. data/lib/rubocop/cop/layout/space_around_operators.rb +49 -6
  40. data/lib/rubocop/cop/layout/space_before_block_braces.rb +17 -0
  41. data/lib/rubocop/cop/layout/space_before_first_arg.rb +8 -0
  42. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -9
  43. data/lib/rubocop/cop/lint/boolean_symbol.rb +12 -0
  44. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  45. data/lib/rubocop/cop/lint/each_with_object_argument.rb +1 -1
  46. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  47. data/lib/rubocop/cop/lint/loop.rb +6 -4
  48. data/lib/rubocop/cop/lint/nested_method_definition.rb +2 -2
  49. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +89 -0
  50. data/lib/rubocop/cop/lint/raise_exception.rb +39 -0
  51. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +3 -3
  52. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +13 -8
  53. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -1
  54. data/lib/rubocop/cop/lint/struct_new_override.rb +58 -0
  55. data/lib/rubocop/cop/lint/suppressed_exception.rb +12 -22
  56. data/lib/rubocop/cop/lint/unused_method_argument.rb +32 -6
  57. data/lib/rubocop/cop/lint/useless_setter_call.rb +4 -0
  58. data/lib/rubocop/cop/migration/department_name.rb +47 -6
  59. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  60. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +4 -0
  61. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +6 -1
  62. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +7 -7
  63. data/lib/rubocop/cop/mixin/hash_transform_method.rb +171 -0
  64. data/lib/rubocop/cop/mixin/line_length_help.rb +88 -0
  65. data/lib/rubocop/cop/mixin/method_complexity.rb +5 -0
  66. data/lib/rubocop/cop/mixin/rational_literal.rb +18 -0
  67. data/lib/rubocop/cop/mixin/statement_modifier.rb +2 -2
  68. data/lib/rubocop/cop/mixin/trailing_comma.rb +8 -12
  69. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  70. data/lib/rubocop/cop/naming/method_name.rb +30 -0
  71. data/lib/rubocop/cop/naming/method_parameter_name.rb +1 -1
  72. data/lib/rubocop/cop/offense.rb +11 -0
  73. data/lib/rubocop/cop/registry.rb +7 -2
  74. data/lib/rubocop/cop/style/access_modifier_declarations.rb +26 -6
  75. data/lib/rubocop/cop/style/attr.rb +8 -0
  76. data/lib/rubocop/cop/style/block_delimiters.rb +60 -1
  77. data/lib/rubocop/cop/style/collection_methods.rb +2 -0
  78. data/lib/rubocop/cop/style/conditional_assignment.rb +2 -2
  79. data/lib/rubocop/cop/style/documentation.rb +43 -5
  80. data/lib/rubocop/cop/style/end_block.rb +6 -0
  81. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +89 -11
  82. data/lib/rubocop/cop/style/guard_clause.rb +3 -2
  83. data/lib/rubocop/cop/style/hash_each_methods.rb +89 -0
  84. data/lib/rubocop/cop/style/hash_transform_keys.rb +83 -0
  85. data/lib/rubocop/cop/style/hash_transform_values.rb +83 -0
  86. data/lib/rubocop/cop/style/if_unless_modifier.rb +38 -3
  87. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  88. data/lib/rubocop/cop/style/inverse_methods.rb +9 -5
  89. data/lib/rubocop/cop/style/lambda.rb +1 -0
  90. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +7 -205
  91. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +169 -0
  92. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +54 -0
  93. data/lib/rubocop/cop/style/module_function.rb +56 -10
  94. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -1
  95. data/lib/rubocop/cop/style/multiline_when_then.rb +5 -1
  96. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +4 -4
  97. data/lib/rubocop/cop/style/numeric_predicate.rb +4 -3
  98. data/lib/rubocop/cop/style/one_line_conditional.rb +3 -2
  99. data/lib/rubocop/cop/style/or_assignment.rb +3 -2
  100. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +7 -7
  101. data/lib/rubocop/cop/style/redundant_condition.rb +17 -4
  102. data/lib/rubocop/cop/style/redundant_sort.rb +2 -2
  103. data/lib/rubocop/cop/style/symbol_array.rb +2 -2
  104. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  105. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +34 -22
  106. data/lib/rubocop/cop/style/trailing_comma_in_array_literal.rb +41 -0
  107. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +85 -0
  108. data/lib/rubocop/cop/style/trailing_comma_in_hash_literal.rb +44 -0
  109. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +7 -1
  110. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  111. data/lib/rubocop/cop/style/yoda_condition.rb +16 -1
  112. data/lib/rubocop/cop/variable_force.rb +4 -1
  113. data/lib/rubocop/formatter/base_formatter.rb +2 -2
  114. data/lib/rubocop/formatter/clang_style_formatter.rb +1 -1
  115. data/lib/rubocop/formatter/formatter_set.rb +1 -0
  116. data/lib/rubocop/formatter/json_formatter.rb +6 -5
  117. data/lib/rubocop/formatter/junit_formatter.rb +74 -0
  118. data/lib/rubocop/formatter/tap_formatter.rb +1 -1
  119. data/lib/rubocop/node_pattern.rb +97 -11
  120. data/lib/rubocop/options.rb +8 -8
  121. data/lib/rubocop/processed_source.rb +1 -1
  122. data/lib/rubocop/result_cache.rb +2 -0
  123. data/lib/rubocop/rspec/shared_contexts.rb +5 -0
  124. data/lib/rubocop/runner.rb +5 -1
  125. data/lib/rubocop/target_ruby.rb +151 -0
  126. data/lib/rubocop/version.rb +1 -1
  127. metadata +38 -10
  128. data/lib/rubocop/cop/lint/end_in_method.rb +0 -40
  129. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +0 -209
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/ModuleLength
3
4
  module RuboCop
4
5
  module AST
5
6
  # Provides methods for traversing an AST.
@@ -18,15 +19,21 @@ module RuboCop
18
19
  rational str sym regopt self lvar
19
20
  ivar cvar gvar nth_ref back_ref cbase
20
21
  arg restarg blockarg shadowarg
21
- kwrestarg zsuper lambda redo retry].freeze
22
+ kwrestarg zsuper lambda redo retry
23
+ forward_args forwarded_args
24
+ match_var match_nil_pattern empty_else].freeze
22
25
  ONE_CHILD_NODE = %i[splat kwsplat block_pass not break next
23
26
  preexe postexe match_current_line defined?
24
- arg_expr].freeze
27
+ arg_expr pin match_rest if_guard unless_guard
28
+ match_with_trailing_comma].freeze
25
29
  MANY_CHILD_NODES = %i[dstr dsym xstr regexp array hash pair
26
30
  mlhs masgn or_asgn and_asgn
27
31
  undef alias args super yield or and
28
32
  while_post until_post iflipflop eflipflop
29
- match_with_lvasgn begin kwbegin return].freeze
33
+ match_with_lvasgn begin kwbegin return
34
+ in_match match_alt
35
+ match_as array_pattern array_pattern_with_tail
36
+ hash_pattern const_pattern].freeze
30
37
  SECOND_CHILD_ONLY = %i[lvasgn ivasgn cvasgn gvasgn optarg kwarg
31
38
  kwoptarg].freeze
32
39
 
@@ -171,13 +178,25 @@ module RuboCop
171
178
  nil
172
179
  end
173
180
 
174
- alias on_rescue on_case
175
- alias on_resbody on_case
176
- alias on_ensure on_case
177
- alias on_for on_case
178
- alias on_when on_case
179
- alias on_irange on_case
180
- alias on_erange on_case
181
+ alias on_rescue on_case
182
+ alias on_resbody on_case
183
+ alias on_ensure on_case
184
+ alias on_for on_case
185
+ alias on_when on_case
186
+ alias on_case_match on_case
187
+ alias on_in_pattern on_case
188
+ alias on_irange on_case
189
+ alias on_erange on_case
190
+
191
+ def on_numblock(node)
192
+ children = node.children
193
+ child = children[0]
194
+ send(:"on_#{child.type}", child)
195
+ return unless (child = children[2])
196
+
197
+ send(:"on_#{child.type}", child)
198
+ end
181
199
  end
182
200
  end
183
201
  end
202
+ # rubocop:enable Metrics/ModuleLength
@@ -7,13 +7,13 @@ module RuboCop
7
7
  class AutoGenerateConfig < Base
8
8
  self.command_name = :auto_gen_config
9
9
 
10
- PHASE_1 = 'Phase 1 of 2: run Metrics/LineLength cop'
10
+ PHASE_1 = 'Phase 1 of 2: run Layout/LineLength cop'
11
11
  PHASE_2 = 'Phase 2 of 2: run all cops'
12
12
 
13
13
  PHASE_1_OVERRIDDEN =
14
- '(skipped because the default Metrics/LineLength:Max is overridden)'
14
+ '(skipped because the default Layout/LineLength:Max is overridden)'
15
15
  PHASE_1_DISABLED =
16
- '(skipped because Metrics/LineLength is disabled)'
16
+ '(skipped because Layout/LineLength is disabled)'
17
17
 
18
18
  def run
19
19
  add_formatter
@@ -54,15 +54,15 @@ module RuboCop
54
54
  end
55
55
 
56
56
  def line_length_cop(config)
57
- config.for_cop('Metrics/LineLength')
57
+ config.for_cop('Layout/LineLength')
58
58
  end
59
59
 
60
- # Do an initial run with only Metrics/LineLength so that cops that
61
- # depend on Metrics/LineLength:Max get the correct value for that
60
+ # Do an initial run with only Layout/LineLength so that cops that
61
+ # depend on Layout/LineLength:Max get the correct value for that
62
62
  # parameter.
63
63
  def run_line_length_cop
64
64
  puts Rainbow(PHASE_1).yellow
65
- @options[:only] = ['Metrics/LineLength']
65
+ @options[:only] = ['Layout/LineLength']
66
66
  execute_runner
67
67
  @options.delete(:only)
68
68
  @config_store = ConfigStore.new
@@ -8,6 +8,13 @@ module RuboCop
8
8
  class ShowCops < Base
9
9
  self.command_name = :show_cops
10
10
 
11
+ def initialize(env)
12
+ super
13
+
14
+ # Load the configs so the require()s are done for custom cops
15
+ @config = @config_store.for(Dir.pwd)
16
+ end
17
+
11
18
  def run
12
19
  print_available_cops
13
20
  end
@@ -15,8 +22,6 @@ module RuboCop
15
22
  private
16
23
 
17
24
  def print_available_cops
18
- # Load the configs so the require()s are done for custom cops
19
- @config_store.for(Dir.pwd)
20
25
  registry = Cop::Cop.registry
21
26
  show_all = @options[:show_cops].empty?
22
27
 
@@ -46,7 +51,9 @@ module RuboCop
46
51
 
47
52
  def print_cop_details(cops)
48
53
  cops.each do |cop|
49
- puts '# Supports --auto-correct' if cop.new.support_autocorrect?
54
+ if cop.new(@config).support_autocorrect?
55
+ puts '# Supports --auto-correct'
56
+ end
50
57
  puts "#{cop.cop_name}:"
51
58
  puts config_lines(cop)
52
59
  puts
@@ -64,7 +71,7 @@ module RuboCop
64
71
  end
65
72
 
66
73
  def config_lines(cop)
67
- cnf = @config_store.for(Dir.pwd).for_cop(cop)
74
+ cnf = @config.for_cop(cop)
68
75
  cnf.to_yaml.lines.to_a.drop(1).map { |line| ' ' + line }
69
76
  end
70
77
  end
@@ -113,7 +113,8 @@ module RuboCop
113
113
  def each_mentioned_cop
114
114
  each_directive do |comment, cop_names, disabled|
115
115
  comment_line_number = comment.loc.expression.line
116
- single_line = !comment_only_line?(comment_line_number)
116
+ single_line = !comment_only_line?(comment_line_number) ||
117
+ directive_on_comment_line?(comment)
117
118
 
118
119
  cop_names.each do |cop_name|
119
120
  yield qualified_cop_name(cop_name), disabled, comment_line_number,
@@ -122,6 +123,10 @@ module RuboCop
122
123
  end
123
124
  end
124
125
 
126
+ def directive_on_comment_line?(comment)
127
+ comment.text[1..-1].match(COMMENT_DIRECTIVE_REGEXP)
128
+ end
129
+
125
130
  def each_directive
126
131
  return if processed_source.comments.nil?
127
132
 
@@ -2,6 +2,9 @@
2
2
 
3
3
  require 'pathname'
4
4
 
5
+ # FIXME: Moving Rails department code to RuboCop Rails will remove
6
+ # the following rubocop:disable comment.
7
+ # rubocop:disable Metrics/ClassLength
5
8
  module RuboCop
6
9
  # This class represents the configuration of the RuboCop application
7
10
  # and all its cops. A Config is associated with a YAML configuration
@@ -13,6 +16,8 @@ module RuboCop
13
16
  include FileFinder
14
17
  extend Forwardable
15
18
 
19
+ CopConfig = Struct.new(:name, :metadata)
20
+
16
21
  DEFAULT_RAILS_VERSION = 5.0
17
22
  attr_reader :loaded_path
18
23
 
@@ -115,7 +120,7 @@ module RuboCop
115
120
  relative_file_path = path_relative_to_config(file)
116
121
 
117
122
  # Optimization to quickly decide if the given file is hidden (on the top
118
- # level) and can not be matched by any pattern.
123
+ # level) and cannot be matched by any pattern.
119
124
  is_hidden = relative_file_path.start_with?('.') &&
120
125
  !relative_file_path.start_with?('..')
121
126
  return false if is_hidden && !possibly_include_hidden?
@@ -215,6 +220,18 @@ module RuboCop
215
220
  nil
216
221
  end
217
222
 
223
+ def pending_cops
224
+ keys.each_with_object([]) do |qualified_cop_name, pending_cops|
225
+ department = department_of(qualified_cop_name)
226
+ next if department && department['Enabled'] == false
227
+
228
+ cop_metadata = self[qualified_cop_name]
229
+ next unless cop_metadata['Enabled'] == 'pending'
230
+
231
+ pending_cops << CopConfig.new(qualified_cop_name, cop_metadata)
232
+ end
233
+ end
234
+
218
235
  private
219
236
 
220
237
  def target_rails_version_from_bundler_lock_file
@@ -235,17 +252,18 @@ module RuboCop
235
252
  end
236
253
 
237
254
  def enable_cop?(qualified_cop_name, cop_options)
238
- cop_department, cop_name = qualified_cop_name.split('/')
239
- department = cop_name.nil?
240
-
241
- unless department
242
- department_options = self[cop_department]
243
- if department_options && department_options['Enabled'] == false
244
- return false
245
- end
246
- end
255
+ department = department_of(qualified_cop_name)
256
+ return false if department && department['Enabled'] == false
247
257
 
248
258
  cop_options.fetch('Enabled') { !for_all_cops['DisabledByDefault'] }
249
259
  end
260
+
261
+ def department_of(qualified_cop_name)
262
+ cop_department, cop_name = qualified_cop_name.split('/')
263
+ return nil if cop_name.nil?
264
+
265
+ self[cop_department]
266
+ end
250
267
  end
251
268
  end
269
+ # rubocop:enable Metrics/ClassLength
@@ -91,7 +91,9 @@ module RuboCop
91
91
  else
92
92
  add_excludes_from_files(config, config_file)
93
93
  end
94
- merge_with_default(config, config_file)
94
+ merge_with_default(config, config_file).tap do |merged_config|
95
+ warn_on_pending_cops(merged_config.pending_cops)
96
+ end
95
97
  end
96
98
 
97
99
  def add_excludes_from_files(config, config_file)
@@ -114,6 +116,22 @@ module RuboCop
114
116
  end
115
117
  end
116
118
 
119
+ def warn_on_pending_cops(pending_cops)
120
+ return if pending_cops.empty?
121
+
122
+ warn Rainbow('The following cops were added to RuboCop, but are not ' \
123
+ 'configured. Please set Enabled to either `true` or ' \
124
+ '`false` in your `.rubocop.yml` file:').yellow
125
+
126
+ pending_cops.each do |cop|
127
+ warn Rainbow(
128
+ " - #{cop.name} (#{cop.metadata['VersionAdded']})"
129
+ ).yellow
130
+ end
131
+
132
+ warn Rainbow('For more information: https://docs.rubocop.org/en/latest/versioning/').yellow
133
+ end
134
+
117
135
  # Merges the given configuration with the default one. If
118
136
  # AllCops:DisabledByDefault is true, it changes the Enabled params so
119
137
  # that only cops from user configuration are enabled.
@@ -199,8 +217,6 @@ module RuboCop
199
217
  raise(TypeError, "Malformed configuration in #{absolute_path}")
200
218
  end
201
219
 
202
- check_cop_config_value(hash)
203
-
204
220
  hash
205
221
  end
206
222
 
@@ -222,22 +238,6 @@ module RuboCop
222
238
  end
223
239
  end
224
240
 
225
- def check_cop_config_value(hash, parent = nil)
226
- hash.each do |key, value|
227
- check_cop_config_value(value, key) if value.is_a?(Hash)
228
-
229
- next unless %w[Enabled
230
- Safe
231
- SafeAutoCorrect
232
- AutoCorrect].include?(key) && value.is_a?(String)
233
-
234
- abort(
235
- "Property #{Rainbow(key).yellow} of cop #{Rainbow(parent).yellow}" \
236
- " is supposed to be a boolean and #{Rainbow(value).yellow} is not."
237
- )
238
- end
239
- end
240
-
241
241
  # Read the specified file, or exit with a friendly, concise message on
242
242
  # stderr. Care is taken to use the standard OS exit code for a "file not
243
243
  # found" error.
@@ -19,6 +19,7 @@ module RuboCop
19
19
  'Layout/LeadingBlankLines' => 'Layout/LeadingEmptyLines',
20
20
  'Layout/TrailingBlankLines' => 'Layout/TrailingEmptyLines',
21
21
  'Lint/DuplicatedKey' => 'Lint/DuplicateHashKey',
22
+ 'Lint/EndInMethod' => 'Style/EndBlock',
22
23
  'Lint/HandleExceptions' => 'Lint/SuppressedException',
23
24
  'Lint/MultipleCompare' => 'Lint/MultipleComparison',
24
25
  'Lint/StringConversionInInterpolation' => 'Lint/RedundantStringCoercion',
@@ -48,7 +49,7 @@ module RuboCop
48
49
  Style/VariableName Style/VariableNumber
49
50
  Style/AccessorMethodName Style/AsciiIdentifiers],
50
51
  'Layout' => %w[Lint/BlockAlignment Lint/EndAlignment
51
- Lint/DefEndAlignment],
52
+ Lint/DefEndAlignment Metrics/LineLength],
52
53
  'Lint' => 'Style/FlipFlop'
53
54
  }.map do |new_department, old_names|
54
55
  Array(old_names).map do |old_name|
@@ -69,7 +70,8 @@ module RuboCop
69
70
  'Style/TrailingCommaInHashLiteral',
70
71
  'Style/TrailingCommaInLiteral' => 'Style/TrailingCommaInArrayLiteral ' \
71
72
  'and/or ' \
72
- 'Style/TrailingCommaInHashLiteral'
73
+ 'Style/TrailingCommaInHashLiteral',
74
+ 'Style/BracesAroundHashParameters' => nil
73
75
  }.map do |old_name, other_cops|
74
76
  if other_cops
75
77
  more = ". Please use #{other_cops} instead".gsub(%r{[A-Z]\w+/\w+},
@@ -114,13 +116,13 @@ module RuboCop
114
116
  cops: 'Style/IfUnlessModifier',
115
117
  parameters: 'MaxLineLength',
116
118
  alternative: '`Style/IfUnlessModifier: MaxLineLength` has been ' \
117
- 'removed. Use `Metrics/LineLength: Max` instead'
119
+ 'removed. Use `Layout/LineLength: Max` instead'
118
120
  },
119
121
  {
120
122
  cops: 'Style/WhileUntilModifier',
121
123
  parameters: 'MaxLineLength',
122
124
  alternative: '`Style/WhileUntilModifier: MaxLineLength` has been ' \
123
- 'removed. Use `Metrics/LineLength: Max` instead'
125
+ 'removed. Use `Layout/LineLength: Max` instead'
124
126
  },
125
127
  {
126
128
  cops: 'AllCops',
@@ -14,25 +14,19 @@ module RuboCop
14
14
  VersionAdded VersionChanged VersionRemoved
15
15
  Reference Safe SafeAutoCorrect].freeze
16
16
 
17
- # 2.3 is the oldest officially supported Ruby version.
18
- DEFAULT_RUBY_VERSION = 2.3
19
- KNOWN_RUBIES = [2.3, 2.4, 2.5, 2.6, 2.7].freeze
20
- OBSOLETE_RUBIES = {
21
- 1.9 => '0.50', 2.0 => '0.50', 2.1 => '0.58', 2.2 => '0.69'
22
- }.freeze
23
- RUBY_VERSION_FILENAME = '.ruby-version'
24
-
25
- def_delegators :@config,
26
- :smart_loaded_path, :for_all_cops, :find_file_upwards,
27
- :base_dir_for_path_parameters, :bundler_lock_file_path
17
+ def_delegators :@config, :smart_loaded_path, :for_all_cops
28
18
 
29
19
  def initialize(config)
30
20
  @config = config
31
21
  @config_obsoletion = ConfigObsoletion.new(config)
22
+ @target_ruby = TargetRuby.new(config)
32
23
  end
33
24
 
34
25
  def validate
35
- # Don't validate RuboCop's own files. Avoids infinite recursion.
26
+ check_cop_config_value(@config)
27
+ reject_conflicting_safe_settings
28
+
29
+ # Don't validate RuboCop's own files further. Avoids infinite recursion.
36
30
  return if @config.internal?
37
31
 
38
32
  valid_cop_names, invalid_cop_names = @config.keys.partition do |key|
@@ -41,7 +35,7 @@ module RuboCop
41
35
 
42
36
  @config_obsoletion.reject_obsolete_cops_and_parameters
43
37
 
44
- warn_about_unrecognized_cops(invalid_cop_names)
38
+ alert_about_unrecognized_cops(invalid_cop_names)
45
39
  check_target_ruby
46
40
  validate_parameter_names(valid_cop_names)
47
41
  validate_enforced_styles(valid_cop_names)
@@ -50,23 +44,7 @@ module RuboCop
50
44
  end
51
45
 
52
46
  def target_ruby_version
53
- @target_ruby_version ||= begin
54
- if for_all_cops['TargetRubyVersion']
55
- @target_ruby_version_source = :rubocop_yml
56
-
57
- for_all_cops['TargetRubyVersion'].to_f
58
- elsif target_ruby_version_from_version_file
59
- @target_ruby_version_source = :ruby_version_file
60
-
61
- target_ruby_version_from_version_file
62
- elsif target_ruby_version_from_bundler_lock_file
63
- @target_ruby_version_source = :bundler_lock_file
64
-
65
- target_ruby_version_from_bundler_lock_file
66
- else
67
- DEFAULT_RUBY_VERSION
68
- end
69
- end
47
+ target_ruby.version
70
48
  end
71
49
 
72
50
  def validate_section_presence(name)
@@ -78,25 +56,30 @@ module RuboCop
78
56
 
79
57
  private
80
58
 
59
+ attr_reader :target_ruby
60
+
81
61
  def check_target_ruby
82
- return if KNOWN_RUBIES.include?(target_ruby_version)
62
+ return if target_ruby.supported?
83
63
 
84
- msg = if OBSOLETE_RUBIES.include?(target_ruby_version)
64
+ source = target_ruby.source
65
+ last_version = target_ruby.rubocop_version_with_support
66
+
67
+ msg = if last_version
85
68
  "RuboCop found unsupported Ruby version #{target_ruby_version} " \
86
- "in #{target_ruby_source}. #{target_ruby_version}-compatible " \
87
- 'analysis was dropped after version ' \
88
- "#{OBSOLETE_RUBIES[target_ruby_version]}."
69
+ "in #{source}. #{target_ruby_version}-compatible " \
70
+ "analysis was dropped after version #{last_version}."
89
71
  else
90
72
  'RuboCop found unknown Ruby version ' \
91
- "#{target_ruby_version.inspect} in #{target_ruby_source}."
73
+ "#{target_ruby_version.inspect} in #{source}."
92
74
  end
93
75
 
94
- msg += "\nSupported versions: #{KNOWN_RUBIES.join(', ')}"
76
+ msg += "\nSupported versions: #{TargetRuby.supported_versions.join(', ')}"
95
77
 
96
78
  raise ValidationError, msg
97
79
  end
98
80
 
99
- def warn_about_unrecognized_cops(invalid_cop_names)
81
+ def alert_about_unrecognized_cops(invalid_cop_names)
82
+ unknown_cops = []
100
83
  invalid_cop_names.each do |name|
101
84
  # There could be a custom cop with this name. If so, don't warn
102
85
  next if Cop::Cop.registry.contains_cop_matching?([name])
@@ -106,9 +89,10 @@ module RuboCop
106
89
  # to do so than to pass the value around to various methods.
107
90
  next if name == 'inherit_mode'
108
91
 
109
- warn Rainbow("Warning: unrecognized cop #{name} found in " \
110
- "#{smart_loaded_path}").yellow
92
+ unknown_cops << "unrecognized cop #{name} found in " \
93
+ "#{smart_loaded_path}"
111
94
  end
95
+ raise ValidationError, unknown_cops.join(', ') if unknown_cops.any?
112
96
  end
113
97
 
114
98
  def validate_syntax_cop
@@ -176,71 +160,47 @@ module RuboCop
176
160
  formats.all? { |format| valid.include?(format) }
177
161
  end
178
162
 
179
- def target_ruby_source
180
- case @target_ruby_version_source
181
- when :ruby_version_file
182
- "`#{RUBY_VERSION_FILENAME}`"
183
- when :bundler_lock_file
184
- "`#{bundler_lock_file_path}`"
185
- when :rubocop_yml
186
- "`TargetRubyVersion` parameter (in #{smart_loaded_path})"
187
- end
188
- end
163
+ def reject_mutually_exclusive_defaults
164
+ disabled_by_default = for_all_cops['DisabledByDefault']
165
+ enabled_by_default = for_all_cops['EnabledByDefault']
166
+ return unless disabled_by_default && enabled_by_default
189
167
 
190
- def ruby_version_file
191
- @ruby_version_file ||=
192
- find_file_upwards(RUBY_VERSION_FILENAME, base_dir_for_path_parameters)
168
+ msg = 'Cops cannot be both enabled by default and disabled by default'
169
+ raise ValidationError, msg
193
170
  end
194
171
 
195
- def target_ruby_version_from_version_file
196
- file = ruby_version_file
197
- return unless file && File.file?(file)
172
+ def reject_conflicting_safe_settings
173
+ @config.each do |name, cop_config|
174
+ next unless cop_config.is_a?(Hash)
175
+ next unless cop_config['Safe'] == false &&
176
+ cop_config['SafeAutoCorrect'] == true
198
177
 
199
- @target_ruby_version_from_version_file ||=
200
- File.read(file).match(/\A(ruby-)?(?<version>\d+\.\d+)/) do |md|
201
- md[:version].to_f
202
- end
178
+ msg = 'Unsafe cops cannot have a safe auto-correction ' \
179
+ "(section #{name} in #{smart_loaded_path})"
180
+ raise ValidationError, msg
181
+ end
203
182
  end
204
183
 
205
- def target_ruby_version_from_bundler_lock_file
206
- @target_ruby_version_from_bundler_lock_file ||=
207
- read_ruby_version_from_bundler_lock_file
208
- end
184
+ def check_cop_config_value(hash, parent = nil)
185
+ hash.each do |key, value|
186
+ check_cop_config_value(value, key) if value.is_a?(Hash)
187
+
188
+ next unless %w[Enabled
189
+ Safe
190
+ SafeAutoCorrect
191
+ AutoCorrect].include?(key) && value.is_a?(String)
192
+
193
+ next if key == 'Enabled' && value == 'pending'
209
194
 
210
- def read_ruby_version_from_bundler_lock_file
211
- lock_file_path = bundler_lock_file_path
212
- return nil unless lock_file_path
213
-
214
- in_ruby_section = false
215
- File.foreach(lock_file_path) do |line|
216
- # If ruby is in Gemfile.lock or gems.lock, there should be two lines
217
- # towards the bottom of the file that look like:
218
- # RUBY VERSION
219
- # ruby W.X.YpZ
220
- # We ultimately want to match the "ruby W.X.Y.pZ" line, but there's
221
- # extra logic to make sure we only start looking once we've seen the
222
- # "RUBY VERSION" line.
223
- in_ruby_section ||= line.match(/^\s*RUBY\s*VERSION\s*$/)
224
- next unless in_ruby_section
225
-
226
- # We currently only allow this feature to work with MRI ruby. If jruby
227
- # (or something else) is used by the project, it's lock file will have a
228
- # line that looks like:
229
- # RUBY VERSION
230
- # ruby W.X.YpZ (jruby x.x.x.x)
231
- # The regex won't match in this situation.
232
- result = line.match(/^\s*ruby\s+(\d+\.\d+)[p.\d]*\s*$/)
233
- return result.captures.first.to_f if result
195
+ raise ValidationError, msg_not_boolean(parent, key, value)
234
196
  end
235
197
  end
236
198
 
237
- def reject_mutually_exclusive_defaults
238
- disabled_by_default = for_all_cops['DisabledByDefault']
239
- enabled_by_default = for_all_cops['EnabledByDefault']
240
- return unless disabled_by_default && enabled_by_default
241
-
242
- msg = 'Cops cannot be both enabled by default and disabled by default'
243
- raise ValidationError, msg
199
+ # FIXME: Handling colors in exception messages like this is ugly.
200
+ def msg_not_boolean(parent, key, value)
201
+ "#{Rainbow('').reset}" \
202
+ "Property #{Rainbow(key).yellow} of cop #{Rainbow(parent).yellow}" \
203
+ " is supposed to be a boolean and #{Rainbow(value).yellow} is not."
244
204
  end
245
205
  end
246
206
  end