rubocop 0.93.1 → 1.0.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +59 -54
  4. data/lib/rubocop.rb +0 -2
  5. data/lib/rubocop/cli/command/version.rb +1 -1
  6. data/lib/rubocop/config.rb +4 -0
  7. data/lib/rubocop/config_loader.rb +19 -2
  8. data/lib/rubocop/config_loader_resolver.rb +7 -5
  9. data/lib/rubocop/config_validator.rb +7 -6
  10. data/lib/rubocop/cop/badge.rb +9 -24
  11. data/lib/rubocop/cop/base.rb +16 -1
  12. data/lib/rubocop/cop/commissioner.rb +34 -20
  13. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
  14. data/lib/rubocop/cop/layout/class_structure.rb +7 -0
  15. data/lib/rubocop/cop/layout/space_around_operators.rb +4 -1
  16. data/lib/rubocop/cop/layout/trailing_whitespace.rb +37 -13
  17. data/lib/rubocop/cop/lint/to_json.rb +1 -1
  18. data/lib/rubocop/cop/metrics/parameter_lists.rb +4 -1
  19. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -1
  20. data/lib/rubocop/cop/security/open.rb +12 -10
  21. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -1
  22. data/lib/rubocop/cop/style/format_string_token.rb +47 -2
  23. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +10 -13
  24. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +6 -11
  25. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +7 -11
  26. data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -0
  27. data/lib/rubocop/cop/style/redundant_self.rb +3 -0
  28. data/lib/rubocop/cop/style/safe_navigation.rb +16 -4
  29. data/lib/rubocop/cop/style/string_concatenation.rb +13 -1
  30. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +3 -1
  31. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  32. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  33. data/lib/rubocop/options.rb +4 -1
  34. data/lib/rubocop/version.rb +56 -6
  35. metadata +4 -4
@@ -12,6 +12,9 @@ module RuboCop
12
12
  MSG = 'Avoid parameter lists longer than %<max>d parameters. ' \
13
13
  '[%<count>d/%<max>d]'
14
14
 
15
+ NAMED_KEYWORD_TYPES = %i[kwoptarg kwarg].freeze
16
+ private_constant :NAMED_KEYWORD_TYPES
17
+
15
18
  def on_args(node)
16
19
  count = args_count(node)
17
20
  return unless count > max_params
@@ -33,7 +36,7 @@ module RuboCop
33
36
  if count_keyword_args?
34
37
  node.children.size
35
38
  else
36
- node.children.count { |a| !%i[kwoptarg kwarg].include?(a.type) }
39
+ node.children.count { |a| !NAMED_KEYWORD_TYPES.include?(a.type) }
37
40
  end
38
41
  end
39
42
 
@@ -18,7 +18,7 @@ module RuboCop
18
18
  'name its argument `other`.'
19
19
 
20
20
  OP_LIKE_METHODS = %i[eql? equal?].freeze
21
- EXCLUDED = %i[+@ -@ [] []= << === `].freeze
21
+ EXCLUDED = %i[+@ -@ [] []= << === ` =~].freeze
22
22
 
23
23
  def_node_matcher :op_method_candidate?, <<~PATTERN
24
24
  (def [#op_method? $_] (args $(arg [!:other !:_other])) _)
@@ -3,35 +3,37 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Security
6
- # This cop checks for the use of `Kernel#open`.
6
+ # This cop checks for the use of `Kernel#open` and `URI.open`.
7
7
  #
8
- # `Kernel#open` enables not only file access but also process invocation
9
- # by prefixing a pipe symbol (e.g., `open("| ls")`). So, it may lead to
10
- # a serious security risk by using variable input to the argument of
11
- # `Kernel#open`. It would be better to use `File.open`, `IO.popen` or
12
- # `URI#open` explicitly.
8
+ # `Kernel#open` and `URI.open` enable not only file access but also process
9
+ # invocation by prefixing a pipe symbol (e.g., `open("| ls")`).
10
+ # So, it may lead to a serious security risk by using variable input to
11
+ # the argument of `Kernel#open` and `URI.open`. It would be better to use
12
+ # `File.open`, `IO.popen` or `URI.parse#open` explicitly.
13
13
  #
14
14
  # @example
15
15
  # # bad
16
16
  # open(something)
17
+ # URI.open(something)
17
18
  #
18
19
  # # good
19
20
  # File.open(something)
20
21
  # IO.popen(something)
21
22
  # URI.parse(something).open
22
23
  class Open < Base
23
- MSG = 'The use of `Kernel#open` is a serious security risk.'
24
+ MSG = 'The use of `%<receiver>sopen` is a serious security risk.'
24
25
  RESTRICT_ON_SEND = %i[open].freeze
25
26
 
26
27
  def_node_matcher :open?, <<~PATTERN
27
- (send nil? :open $!str ...)
28
+ (send ${nil? (const {nil? cbase} :URI)} :open $!str ...)
28
29
  PATTERN
29
30
 
30
31
  def on_send(node)
31
- open?(node) do |code|
32
+ open?(node) do |receiver, code|
32
33
  return if safe?(code)
33
34
 
34
- add_offense(node.loc.selector)
35
+ message = format(MSG, receiver: receiver ? "#{receiver.source}." : 'Kernel#')
36
+ add_offense(node.loc.selector, message: message)
35
37
  end
36
38
  end
37
39
 
@@ -7,7 +7,7 @@ module RuboCop
7
7
  # By default it enforces accessors to be placed in grouped declarations,
8
8
  # but it can be configured to enforce separating them in multiple declarations.
9
9
  #
10
- # Note: `Sorbet` is not compatible with "grouped" style. Consider "separated" style
10
+ # NOTE: `Sorbet` is not compatible with "grouped" style. Consider "separated" style
11
11
  # or disabling this cop.
12
12
  #
13
13
  # @example EnforcedStyle: grouped (default)
@@ -37,6 +37,27 @@ module RuboCop
37
37
  #
38
38
  # # good
39
39
  # format('%s', 'Hello')
40
+ #
41
+ # It is allowed to contain unannotated token
42
+ # if the number of them is less than or equals to
43
+ # `MaxUnannotatedPlaceholdersAllowed`.
44
+ #
45
+ # @example MaxUnannotatedPlaceholdersAllowed: 0
46
+ #
47
+ # # bad
48
+ # format('%06d', 10)
49
+ # format('%s %s.', 'Hello', 'world')
50
+ #
51
+ # # good
52
+ # format('%<number>06d', number: 10)
53
+ #
54
+ # @example MaxUnannotatedPlaceholdersAllowed: 1 (default)
55
+ #
56
+ # # bad
57
+ # format('%s %s.', 'Hello', 'world')
58
+ #
59
+ # # good
60
+ # format('%06d', 10)
40
61
  class FormatStringToken < Base
41
62
  include ConfigurableEnforcedStyle
42
63
 
@@ -44,8 +65,12 @@ module RuboCop
44
65
  return unless node.value.include?('%')
45
66
  return if node.each_ancestor(:xstr, :regexp).any?
46
67
 
47
- tokens(node) do |detected_style, token_range|
48
- if detected_style == style || unannotated_format?(node, detected_style)
68
+ detections = collect_detections(node)
69
+ return if detections.empty?
70
+ return if allowed_unannotated?(detections)
71
+
72
+ detections.each do |detected_style, token_range|
73
+ if detected_style == style
49
74
  correct_style_detected
50
75
  else
51
76
  style_detected(detected_style)
@@ -112,6 +137,26 @@ module RuboCop
112
137
  yield(detected_style, token)
113
138
  end
114
139
  end
140
+
141
+ def collect_detections(node)
142
+ detections = []
143
+ tokens(node) do |detected_style, token_range|
144
+ unless unannotated_format?(node, detected_style)
145
+ detections << [detected_style, token_range]
146
+ end
147
+ end
148
+ detections
149
+ end
150
+
151
+ def allowed_unannotated?(detections)
152
+ return false if detections.size > max_unannotated_placeholders_allowed
153
+
154
+ detections.all? { |detected_style,| detected_style == :unannotated }
155
+ end
156
+
157
+ def max_unannotated_placeholders_allowed
158
+ cop_config['MaxUnannotatedPlaceholdersAllowed']
159
+ end
115
160
  end
116
161
  end
117
162
  end
@@ -144,25 +144,22 @@ module RuboCop
144
144
  # # good
145
145
  # Array 1
146
146
  class MethodCallWithArgsParentheses < Base
147
+ require_relative 'method_call_with_args_parentheses/omit_parentheses'
148
+ require_relative 'method_call_with_args_parentheses/require_parentheses'
149
+
147
150
  include ConfigurableEnforcedStyle
148
151
  include IgnoredMethods
149
152
  include IgnoredPattern
153
+ include RequireParentheses
154
+ include OmitParentheses
150
155
  extend AutoCorrector
151
156
 
152
- def initialize(*)
153
- super
154
- return unless style_configured?
155
-
156
- case style
157
- when :require_parentheses
158
- extend RequireParentheses
159
- when :omit_parentheses
160
- extend OmitParentheses
161
- end
157
+ def on_send(node)
158
+ send(style, node) # call require_parentheses or omit_parentheses
162
159
  end
163
-
164
- # @abstract Overridden in style modules
165
- def autocorrect(_node); end
160
+ alias on_csend on_send
161
+ alias on_super on_send
162
+ alias on_yield on_send
166
163
 
167
164
  private
168
165
 
@@ -7,15 +7,19 @@ module RuboCop
7
7
  # Style omit_parentheses
8
8
  module OmitParentheses
9
9
  TRAILING_WHITESPACE_REGEX = /\s+\Z/.freeze
10
+ OMIT_MSG = 'Omit parentheses for method calls with arguments.'
11
+ private_constant :OMIT_MSG
10
12
 
11
- def on_send(node)
13
+ private
14
+
15
+ def omit_parentheses(node)
12
16
  return unless node.parenthesized?
13
17
  return if node.implicit_call?
14
18
  return if super_call_without_arguments?(node)
15
19
  return if allowed_camel_case_method_call?(node)
16
20
  return if legitimate_call_with_parentheses?(node)
17
21
 
18
- add_offense(offense_range(node)) do |corrector|
22
+ add_offense(offense_range(node), message: OMIT_MSG) do |corrector|
19
23
  if parentheses_at_the_end_of_multiline_call?(node)
20
24
  corrector.replace(args_begin(node), ' \\')
21
25
  else
@@ -24,20 +28,11 @@ module RuboCop
24
28
  corrector.remove(node.loc.end)
25
29
  end
26
30
  end
27
- alias on_csend on_send
28
- alias on_super on_send
29
- alias on_yield on_send
30
-
31
- private
32
31
 
33
32
  def offense_range(node)
34
33
  node.loc.begin.join(node.loc.end)
35
34
  end
36
35
 
37
- def message(_range = nil)
38
- 'Omit parentheses for method calls with arguments.'
39
- end
40
-
41
36
  def super_call_without_arguments?(node)
42
37
  node.super_type? && node.arguments.none?
43
38
  end
@@ -6,27 +6,23 @@ module RuboCop
6
6
  class MethodCallWithArgsParentheses
7
7
  # Style require_parentheses
8
8
  module RequireParentheses
9
- def on_send(node)
9
+ REQUIRE_MSG = 'Use parentheses for method calls with arguments.'
10
+ private_constant :REQUIRE_MSG
11
+
12
+ private
13
+
14
+ def require_parentheses(node)
10
15
  return if ignored_method?(node.method_name)
11
16
  return if matches_ignored_pattern?(node.method_name)
12
17
  return if eligible_for_parentheses_omission?(node)
13
18
  return unless node.arguments? && !node.parenthesized?
14
19
 
15
- add_offense(node) do |corrector|
20
+ add_offense(node, message: REQUIRE_MSG) do |corrector|
16
21
  corrector.replace(args_begin(node), '(')
17
22
 
18
23
  corrector.insert_after(args_end(node), ')') unless args_parenthesized?(node)
19
24
  end
20
25
  end
21
- alias on_csend on_send
22
- alias on_super on_send
23
- alias on_yield on_send
24
-
25
- def message(_node = nil)
26
- 'Use parentheses for method calls with arguments.'
27
- end
28
-
29
- private
30
26
 
31
27
  def eligible_for_parentheses_omission?(node)
32
28
  node.operator_method? || node.setter_method? || ignored_macro?(node)
@@ -104,9 +104,13 @@ module RuboCop
104
104
  return offense(begin_node, 'a variable') if node.variable?
105
105
  return offense(begin_node, 'a constant') if node.const_type?
106
106
 
107
+ return offense(begin_node, 'an interpolated expression') if interpolation?(begin_node)
108
+
107
109
  check_send(begin_node, node) if node.call_type?
108
110
  end
109
111
 
112
+ def_node_matcher :interpolation?, '[^begin ^^dstr]'
113
+
110
114
  def check_send(begin_node, node)
111
115
  return check_unary(begin_node, node) if node.unary_operation?
112
116
 
@@ -129,6 +129,9 @@ module RuboCop
129
129
  def allowed_send_node?(node)
130
130
  @allowed_send_nodes.include?(node) ||
131
131
  @local_variables_scopes[node].include?(node.method_name) ||
132
+ node.each_ancestor.any? do |ancestor|
133
+ @local_variables_scopes[ancestor].include?(node.method_name)
134
+ end ||
132
135
  KERNEL_METHODS.include?(node.method_name)
133
136
  end
134
137
 
@@ -142,10 +142,22 @@ module RuboCop
142
142
  end
143
143
 
144
144
  def comments(node)
145
- processed_source.each_comment_in_lines(
146
- node.loc.first_line...
147
- node.loc.last_line
148
- ).to_a
145
+ relevant_comment_ranges(node).each.with_object([]) do |range, comments|
146
+ comments.concat(processed_source.each_comment_in_lines(range).to_a)
147
+ end
148
+ end
149
+
150
+ def relevant_comment_ranges(node)
151
+ # Get source lines ranges inside the if node that aren't inside an inner node
152
+ # Comments inside an inner node should remain attached to that node, and not
153
+ # moved.
154
+ begin_pos = node.loc.first_line
155
+ end_pos = node.loc.last_line
156
+
157
+ node.child_nodes.each.with_object([]) do |child, ranges|
158
+ ranges << (begin_pos...child.loc.first_line)
159
+ begin_pos = child.loc.last_line
160
+ end << (begin_pos...end_pos)
149
161
  end
150
162
 
151
163
  def allowed_if_condition?(node)
@@ -33,6 +33,10 @@ module RuboCop
33
33
  }
34
34
  PATTERN
35
35
 
36
+ def on_new_investigation
37
+ @corrected_nodes = nil
38
+ end
39
+
36
40
  def on_send(node)
37
41
  return unless string_concatenation?(node)
38
42
 
@@ -42,8 +46,12 @@ module RuboCop
42
46
  collect_parts(topmost_plus_node, parts)
43
47
 
44
48
  add_offense(topmost_plus_node) do |corrector|
45
- if parts.none? { |part| uncorrectable?(part) }
49
+ correctable_parts = parts.none? { |part| uncorrectable?(part) }
50
+ if correctable_parts && !corrected_ancestor?(topmost_plus_node)
46
51
  corrector.replace(topmost_plus_node, replacement(parts))
52
+
53
+ @corrected_nodes ||= Set.new.compare_by_identity
54
+ @corrected_nodes.add(topmost_plus_node)
47
55
  end
48
56
  end
49
57
  end
@@ -80,6 +88,10 @@ module RuboCop
80
88
  part.each_descendant(:block).any?
81
89
  end
82
90
 
91
+ def corrected_ancestor?(node)
92
+ node.each_ancestor(:send).any? { |ancestor| @corrected_nodes&.include?(ancestor) }
93
+ end
94
+
83
95
  def replacement(parts)
84
96
  interpolated_parts =
85
97
  parts.map do |part|
@@ -36,6 +36,8 @@ module RuboCop
36
36
  MSG = 'Do not use trailing `_`s in parallel assignment. ' \
37
37
  'Prefer `%<code>s`.'
38
38
  UNDERSCORE = '_'
39
+ DISALLOW = %i[lvasgn splat].freeze
40
+ private_constant :DISALLOW
39
41
 
40
42
  def on_masgn(node)
41
43
  ranges = unneeded_ranges(node)
@@ -64,7 +66,7 @@ module RuboCop
64
66
 
65
67
  def find_first_possible_offense(variables)
66
68
  variables.reduce(nil) do |offense, variable|
67
- break offense unless %i[lvasgn splat].include?(variable.type)
69
+ break offense unless DISALLOW.include?(variable.type)
68
70
 
69
71
  var, = *variable
70
72
  var, = *var
@@ -67,7 +67,7 @@ module RuboCop
67
67
  end
68
68
 
69
69
  def total_offense_count(offense_counts)
70
- offense_counts.values.inject(0, :+)
70
+ offense_counts.values.sum
71
71
  end
72
72
  end
73
73
  end
@@ -55,7 +55,7 @@ module RuboCop
55
55
  end
56
56
 
57
57
  def total_offense_count(offense_counts)
58
- offense_counts.values.inject(0, :+)
58
+ offense_counts.values.sum
59
59
  end
60
60
  end
61
61
  end
@@ -242,6 +242,9 @@ module RuboCop
242
242
  # @api private
243
243
  class OptionsValidator
244
244
  class << self
245
+ SYNTAX_DEPARTMENTS = %w[Syntax Lint/Syntax].freeze
246
+ private_constant :SYNTAX_DEPARTMENTS
247
+
245
248
  # Cop name validation must be done later than option parsing, so it's not
246
249
  # called from within Options.
247
250
  def validate_cop_list(names)
@@ -253,7 +256,7 @@ module RuboCop
253
256
  names.each do |name|
254
257
  next if cop_names.include?(name)
255
258
  next if departments.include?(name)
256
- next if %w[Syntax Lint/Syntax].include?(name)
259
+ next if SYNTAX_DEPARTMENTS.include?(name)
257
260
 
258
261
  raise IncorrectCopNameError, format_message_from(name, cop_names)
259
262
  end
@@ -3,24 +3,74 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '0.93.1'
6
+ STRING = '1.0.0'
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, '\
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
10
10
  'running on %<ruby_engine>s %<ruby_version>s %<ruby_platform>s)'
11
11
 
12
+ CANONICAL_FEATURE_NAMES = { 'Rspec' => 'RSpec' }.freeze
13
+
12
14
  # @api private
13
- def self.version(debug: false)
15
+ def self.version(debug: false, env: nil)
14
16
  if debug
15
- format(MSG, version: STRING, parser_version: Parser::VERSION,
16
- rubocop_ast_version: RuboCop::AST::Version::STRING,
17
- ruby_engine: RUBY_ENGINE, ruby_version: RUBY_VERSION,
18
- ruby_platform: RUBY_PLATFORM)
17
+ verbose_version = format(MSG, version: STRING, parser_version: Parser::VERSION,
18
+ rubocop_ast_version: RuboCop::AST::Version::STRING,
19
+ ruby_engine: RUBY_ENGINE, ruby_version: RUBY_VERSION,
20
+ ruby_platform: RUBY_PLATFORM)
21
+ return verbose_version unless env
22
+
23
+ extension_versions = extension_versions(env)
24
+ return verbose_version if extension_versions.empty?
25
+
26
+ <<~VERSIONS
27
+ #{verbose_version}
28
+ #{extension_versions.join("\n")}
29
+ VERSIONS
19
30
  else
20
31
  STRING
21
32
  end
22
33
  end
23
34
 
35
+ # @api private
36
+ def self.extension_versions(env)
37
+ env.config_store.for_pwd.loaded_features.sort.map do |loaded_feature|
38
+ next unless (match = loaded_feature.match(/rubocop-(?<feature>.*)/))
39
+
40
+ feature = match[:feature]
41
+ begin
42
+ require "rubocop/#{feature}/version"
43
+ rescue LoadError
44
+ # Not worth mentioning libs that are not installed
45
+ else
46
+ next unless (feature_version = feature_version(feature))
47
+
48
+ " - #{loaded_feature} #{feature_version}"
49
+ end
50
+ end.compact
51
+ end
52
+
53
+ # Returns feature version in one of two ways:
54
+ #
55
+ # * Find by RuboCop core version style (e.g. rubocop-performance, rubocop-rspec)
56
+ # * Find by `bundle gem` version style (e.g. rubocop-rake)
57
+ #
58
+ # @api private
59
+ def self.feature_version(feature)
60
+ capitalized_feature = feature.capitalize
61
+ extension_name = CANONICAL_FEATURE_NAMES.fetch(capitalized_feature, capitalized_feature)
62
+
63
+ # Find by RuboCop core version style (e.g. rubocop-performance, rubocop-rspec)
64
+ RuboCop.const_get(extension_name)::Version::STRING
65
+ rescue NameError
66
+ begin
67
+ # Find by `bundle gem` version style (e.g. rubocop-rake, rubocop-packaging)
68
+ RuboCop.const_get(extension_name)::VERSION
69
+ rescue NameError
70
+ # noop
71
+ end
72
+ end
73
+
24
74
  # @api private
25
75
  def self.document_version
26
76
  STRING.match('\d+\.\d+').to_s