rubocop 1.36.0 → 1.37.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +21 -1
  4. data/lib/rubocop/arguments_env.rb +17 -0
  5. data/lib/rubocop/arguments_file.rb +17 -0
  6. data/lib/rubocop/cli/command/execute_runner.rb +7 -7
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +8 -1
  8. data/lib/rubocop/cop/generator.rb +1 -2
  9. data/lib/rubocop/cop/internal_affairs/create_empty_file.rb +37 -0
  10. data/lib/rubocop/cop/internal_affairs/example_heredoc_delimiter.rb +111 -0
  11. data/lib/rubocop/cop/internal_affairs.rb +2 -0
  12. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -0
  13. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  14. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +13 -9
  15. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +28 -3
  16. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  17. data/lib/rubocop/cop/lint/duplicate_magic_comment.rb +73 -0
  18. data/lib/rubocop/cop/lint/duplicate_methods.rb +11 -1
  19. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +25 -6
  20. data/lib/rubocop/cop/lint/empty_class.rb +3 -1
  21. data/lib/rubocop/cop/lint/empty_conditional_body.rb +19 -7
  22. data/lib/rubocop/cop/lint/nested_method_definition.rb +50 -1
  23. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  24. data/lib/rubocop/cop/lint/ordered_magic_comments.rb +4 -5
  25. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +1 -1
  26. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
  27. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +7 -0
  28. data/lib/rubocop/cop/lint/redundant_require_statement.rb +29 -9
  29. data/lib/rubocop/cop/lint/require_parentheses.rb +1 -1
  30. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +3 -2
  31. data/lib/rubocop/cop/lint/shadowed_exception.rb +0 -10
  32. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +3 -0
  33. data/lib/rubocop/cop/lint/unreachable_loop.rb +1 -1
  34. data/lib/rubocop/cop/lint/unused_method_argument.rb +4 -0
  35. data/lib/rubocop/cop/mixin/comments_help.rb +12 -0
  36. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +4 -0
  37. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +6 -3
  38. data/lib/rubocop/cop/mixin/rescue_node.rb +3 -1
  39. data/lib/rubocop/cop/mixin/surrounding_space.rb +6 -5
  40. data/lib/rubocop/cop/naming/inclusive_language.rb +1 -1
  41. data/lib/rubocop/cop/style/access_modifier_declarations.rb +24 -2
  42. data/lib/rubocop/cop/style/accessor_grouping.rb +7 -3
  43. data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
  44. data/lib/rubocop/cop/style/character_literal.rb +1 -1
  45. data/lib/rubocop/cop/style/class_equality_comparison.rb +1 -1
  46. data/lib/rubocop/cop/style/collection_compact.rb +8 -1
  47. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  48. data/lib/rubocop/cop/style/endless_method.rb +1 -1
  49. data/lib/rubocop/cop/style/explicit_block_argument.rb +4 -0
  50. data/lib/rubocop/cop/style/format_string_token.rb +1 -1
  51. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  52. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +13 -2
  53. data/lib/rubocop/cop/style/negated_if_else_condition.rb +7 -1
  54. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  55. data/lib/rubocop/cop/style/operator_method_call.rb +40 -0
  56. data/lib/rubocop/cop/style/redundant_begin.rb +1 -0
  57. data/lib/rubocop/cop/style/redundant_condition.rb +5 -2
  58. data/lib/rubocop/cop/style/redundant_initialize.rb +3 -1
  59. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +8 -1
  60. data/lib/rubocop/cop/style/redundant_string_escape.rb +181 -0
  61. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -1
  62. data/lib/rubocop/cop/style/static_class.rb +32 -1
  63. data/lib/rubocop/cop/style/symbol_array.rb +2 -0
  64. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  65. data/lib/rubocop/cop/style/word_array.rb +2 -0
  66. data/lib/rubocop/formatter/disabled_config_formatter.rb +8 -2
  67. data/lib/rubocop/options.rb +13 -13
  68. data/lib/rubocop/rspec/shared_contexts.rb +13 -1
  69. data/lib/rubocop/server/cache.rb +5 -1
  70. data/lib/rubocop/server/cli.rb +9 -2
  71. data/lib/rubocop/server/client_command/exec.rb +5 -0
  72. data/lib/rubocop/server/core.rb +2 -1
  73. data/lib/rubocop/server/socket_reader.rb +5 -1
  74. data/lib/rubocop/server.rb +1 -1
  75. data/lib/rubocop/version.rb +8 -3
  76. data/lib/rubocop.rb +3 -0
  77. metadata +12 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4bc2d4bd29fd7e81f4d7118fef9f55bcd2f086fd20ce383d85c2a1b7b26ea4e9
4
- data.tar.gz: 9cb3cef8736b03dd0834b94fefe4415a301d78d842df184a1a1d290973376070
3
+ metadata.gz: caf1d6d45b2da199a82152b2c5b20ffa6a5f99df5115dbd8fbf4c4e895fd00c5
4
+ data.tar.gz: 50520bd1d5496971e8b38347dfea57a3387a372b7a815b3c9ef2eb7c1fdc448a
5
5
  SHA512:
6
- metadata.gz: abeba580bae46458f6427c3d21c6532e95ff88e7d981d196f5013fee29c8f911146f229daa8e6f92d2f28bb14790edc46373a4366f9881700df18a7f5a1352fa
7
- data.tar.gz: 4bee5fc42db940e95cd6e3fba83680e6d7fd573a8ed3b7c65136a48d5f3b67ba8d299ecdd30a422afb95961d84c9b0452e3d1b6c1952e0edee1cea97ca704952
6
+ metadata.gz: 0fa612429aa29b3ce4480fbbc7d77f1efc6862ac89098bfadf7c8f79a4e255709f6752f1b1075497b9ad17aa261afa006089152d10e0deb4f4361bce95537999
7
+ data.tar.gz: b55827239ce8508d6f884f03838c7fe8ca89268c93ac7ab88409af176e527c74d0d24a14680fa3c7c84353eb078a97699429015b69e6efdb051582914d37ff30
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.36', require: false
56
+ gem 'rubocop', '~> 1.37', 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
@@ -1707,6 +1707,11 @@ Lint/DuplicateHashKey:
1707
1707
  VersionAdded: '0.34'
1708
1708
  VersionChanged: '0.77'
1709
1709
 
1710
+ Lint/DuplicateMagicComment:
1711
+ Description: 'Check for duplicated magic comments.'
1712
+ Enabled: pending
1713
+ VersionAdded: '1.37'
1714
+
1710
1715
  Lint/DuplicateMethods:
1711
1716
  Description: 'Check for duplicate method definitions.'
1712
1717
  Enabled: true
@@ -1957,6 +1962,8 @@ Lint/NestedMethodDefinition:
1957
1962
  Description: 'Do not use nested method definitions.'
1958
1963
  StyleGuide: '#no-nested-methods'
1959
1964
  Enabled: true
1965
+ AllowedMethods: []
1966
+ AllowedPatterns: []
1960
1967
  VersionAdded: '0.32'
1961
1968
 
1962
1969
  Lint/NestedPercentLiteral:
@@ -2021,7 +2028,9 @@ Lint/OrAssignmentToConstant:
2021
2028
  Lint/OrderedMagicComments:
2022
2029
  Description: 'Checks the proper ordering of magic comments and whether a magic comment is not placed before a shebang.'
2023
2030
  Enabled: true
2031
+ SafeAutoCorrect: false
2024
2032
  VersionAdded: '0.53'
2033
+ VersionChanged: '1.37'
2025
2034
 
2026
2035
  Lint/OutOfRangeRegexpRef:
2027
2036
  Description: 'Checks for out of range reference for Regexp because it always returns nil.'
@@ -3850,7 +3859,7 @@ Style/HashSyntax:
3850
3859
  - never
3851
3860
  # accepts both shorthand and explicit use of hash literal value.
3852
3861
  - either
3853
- # like "always", but will avoid mixing styles in a single hash
3862
+ # like "either", but will avoid mixing styles in a single hash
3854
3863
  - consistent
3855
3864
  # Force hashes that have a symbol value to use hash rockets
3856
3865
  UseHashRocketsWithSymbolValues: false
@@ -4498,6 +4507,12 @@ Style/OpenStructUse:
4498
4507
  Enabled: pending
4499
4508
  VersionAdded: '1.23'
4500
4509
 
4510
+ Style/OperatorMethodCall:
4511
+ Description: 'Checks for redundant dot before operator method call.'
4512
+ StyleGuide: '#operator-method-call'
4513
+ Enabled: pending
4514
+ VersionAdded: '1.37'
4515
+
4501
4516
  Style/OptionHash:
4502
4517
  Description: "Don't use option hashes when you can use keyword arguments."
4503
4518
  Enabled: false
@@ -4792,6 +4807,11 @@ Style/RedundantSortBy:
4792
4807
  Enabled: true
4793
4808
  VersionAdded: '0.36'
4794
4809
 
4810
+ Style/RedundantStringEscape:
4811
+ Description: 'Checks for redundant escapes in string literals.'
4812
+ Enabled: pending
4813
+ VersionAdded: '1.37'
4814
+
4795
4815
  Style/RegexpLiteral:
4796
4816
  Description: 'Use / or %r around regular expressions.'
4797
4817
  StyleGuide: '#percent-r'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ # This is a class that reads optional command line arguments to rubocop from environment variable.
5
+ # @api private
6
+ class ArgumentsEnv
7
+ def self.read_as_arguments
8
+ if (arguments = ENV.fetch('RUBOCOP_OPTS', '')).empty?
9
+ []
10
+ else
11
+ require 'shellwords'
12
+
13
+ Shellwords.split(arguments)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ # This is a class that reads optional command line arguments to rubocop from .rubocop file.
5
+ # @api private
6
+ class ArgumentsFile
7
+ def self.read_as_arguments
8
+ if File.exist?('.rubocop') && !File.directory?('.rubocop')
9
+ require 'shellwords'
10
+
11
+ File.read('.rubocop').shellsplit
12
+ else
13
+ []
14
+ end
15
+ end
16
+ end
17
+ end
@@ -41,13 +41,13 @@ module RuboCop
41
41
 
42
42
  def with_redirect
43
43
  if @options[:stderr]
44
- orig_stdout = $stdout.dup
45
- $stdout.reopen($stderr)
46
-
47
- result = yield
48
-
49
- $stdout.reopen(orig_stdout)
50
- result
44
+ orig_stdout = $stdout
45
+ begin
46
+ $stdout = $stderr
47
+ yield
48
+ ensure
49
+ $stdout = orig_stdout
50
+ end
51
51
  else
52
52
  yield
53
53
  end
@@ -73,7 +73,14 @@ module RuboCop
73
73
  def all_extensions
74
74
  return [] unless lockfile.dependencies.any?
75
75
 
76
- extensions = @config_store.for_pwd.for_all_cops['SuggestExtensions'] || {}
76
+ extensions = @config_store.for_pwd.for_all_cops['SuggestExtensions']
77
+ case extensions
78
+ when true
79
+ extensions = ConfigLoader.default_configuration.for_all_cops['SuggestExtensions']
80
+ when false, nil
81
+ extensions = {}
82
+ end
83
+
77
84
  extensions.select { |_, v| (Array(v) & dependent_gems).any? }.keys
78
85
  end
79
86
 
@@ -206,9 +206,8 @@ module RuboCop
206
206
  end
207
207
 
208
208
  def snake_case(camel_case_string)
209
- return 'rspec' if camel_case_string == 'RSpec'
210
-
211
209
  camel_case_string
210
+ .gsub('RSpec', 'Rspec')
212
211
  .gsub(%r{([^A-Z/])([A-Z]+)}, '\1_\2')
213
212
  .gsub(%r{([A-Z])([A-Z][^A-Z\d/]+)}, '\1_\2')
214
213
  .downcase
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Checks for uses of `create_file` with empty string second argument.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # create_file(path, '')
12
+ #
13
+ # # good
14
+ # create_empty_file(path)
15
+ #
16
+ class CreateEmptyFile < Base
17
+ extend AutoCorrector
18
+
19
+ MSG = 'Use `%<replacement>s`.'
20
+ RESTRICT_ON_SEND = %i[create_file].freeze
21
+
22
+ def on_send(node)
23
+ return if node.receiver
24
+ return unless (argument = node.arguments[1])
25
+ return unless argument.str_type? && argument.value.empty?
26
+
27
+ replacement = "create_empty_file(#{node.first_argument.source})"
28
+ message = format(MSG, replacement: replacement)
29
+
30
+ add_offense(node, message: message) do |corrector|
31
+ corrector.replace(node, replacement)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Use `RUBY` for heredoc delimiter of example Ruby code.
7
+ #
8
+ # Some editors may apply better syntax highlighting by using appropriate language names for
9
+ # the delimiter.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # expect_offense(<<~CODE)
14
+ # example_ruby_code
15
+ # CODE
16
+ #
17
+ # # good
18
+ # expect_offense(<<~RUBY)
19
+ # example_ruby_code
20
+ # RUBY
21
+ class ExampleHeredocDelimiter < Base
22
+ extend AutoCorrector
23
+
24
+ EXPECTED_HEREDOC_DELIMITER = 'RUBY'
25
+
26
+ MSG = 'Use `RUBY` for heredoc delimiter of example Ruby code.'
27
+
28
+ RESTRICT_ON_SEND = %i[
29
+ expect_correction
30
+ expect_no_corrections
31
+ expect_no_offenses
32
+ expect_offense
33
+ ].freeze
34
+
35
+ # @param node [RuboCop::AST::SendNode]
36
+ # @return [void]
37
+ def on_send(node)
38
+ heredoc_node = heredoc_node_from(node)
39
+ return unless heredoc_node
40
+ return if expected_heredoc_delimiter?(heredoc_node)
41
+ return if expected_heredoc_delimiter_in_body?(heredoc_node)
42
+
43
+ add_offense(heredoc_node) do |corrector|
44
+ autocorrect(corrector, heredoc_node)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # @param corrector [RuboCop::Cop::Corrector]
51
+ # @param node [RuboCop::AST::StrNode]
52
+ # @return [void]
53
+ def autocorrect(corrector, node)
54
+ [
55
+ heredoc_openning_delimiter_range_from(node),
56
+ heredoc_closing_delimiter_range_from(node)
57
+ ].each do |range|
58
+ corrector.replace(range, EXPECTED_HEREDOC_DELIMITER)
59
+ end
60
+ end
61
+
62
+ # @param node [RuboCop::AST::StrNode]
63
+ # @return [Boolean]
64
+ def expected_heredoc_delimiter_in_body?(node)
65
+ node.location.heredoc_body.source.lines.any? do |line|
66
+ line.strip == EXPECTED_HEREDOC_DELIMITER
67
+ end
68
+ end
69
+
70
+ # @param node [RuboCop::AST::StrNode]
71
+ # @return [Boolean]
72
+ def expected_heredoc_delimiter?(node)
73
+ heredoc_delimiter_string_from(node) == EXPECTED_HEREDOC_DELIMITER
74
+ end
75
+
76
+ # @param node [RuboCop::AST::SendNode]
77
+ # @return [RuboCop::AST::StrNode, nil]
78
+ def heredoc_node_from(node)
79
+ return unless node.first_argument.respond_to?(:heredoc?)
80
+ return unless node.first_argument.heredoc?
81
+
82
+ node.first_argument
83
+ end
84
+
85
+ # @param node [RuboCop::AST::StrNode]
86
+ # @return [String]
87
+ def heredoc_delimiter_string_from(node)
88
+ node.source[Heredoc::OPENING_DELIMITER, 2]
89
+ end
90
+
91
+ # @param node [RuboCop::AST::StrNode]
92
+ # @return [Parser::Source::Range]
93
+ def heredoc_openning_delimiter_range_from(node)
94
+ match_data = node.source.match(Heredoc::OPENING_DELIMITER)
95
+ node.location.expression.begin.adjust(
96
+ begin_pos: match_data.begin(2),
97
+ end_pos: match_data.end(2)
98
+ )
99
+ end
100
+
101
+ # @param node [RuboCop::AST::StrNode]
102
+ # @return [Parser::Source::Range]
103
+ def heredoc_closing_delimiter_range_from(node)
104
+ node.location.heredoc_end.end.adjust(
105
+ begin_pos: -heredoc_delimiter_string_from(node).length
106
+ )
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'internal_affairs/cop_description'
4
+ require_relative 'internal_affairs/create_empty_file'
4
5
  require_relative 'internal_affairs/empty_line_between_expect_offense_and_correction'
5
6
  require_relative 'internal_affairs/example_description'
7
+ require_relative 'internal_affairs/example_heredoc_delimiter'
6
8
  require_relative 'internal_affairs/inherit_deprecated_cop_class'
7
9
  require_relative 'internal_affairs/location_line_equality_comparison'
8
10
  require_relative 'internal_affairs/method_name_end_with'
@@ -162,6 +162,7 @@ module RuboCop
162
162
  check_alignment([node.first_argument], indent)
163
163
  end
164
164
  alias on_csend on_send
165
+ alias on_super on_send
165
166
 
166
167
  private
167
168
 
@@ -343,7 +343,7 @@ module RuboCop
343
343
  end
344
344
 
345
345
  def skip_check?(base_loc, body_node)
346
- return true if ignored_line?(base_loc)
346
+ return true if allowed_line?(base_loc)
347
347
  return true unless body_node
348
348
 
349
349
  # Don't check if expression is on same line as "then" keyword, etc.
@@ -169,19 +169,22 @@ module RuboCop
169
169
 
170
170
  def qualifies_for_compact?(node, token, side: :right)
171
171
  if side == :right
172
- multi_dimensional_array?(node, token) && !next_to_bracket?(token)
172
+ multi_dimensional_array?(node, token) && token.space_before?
173
173
  else
174
- multi_dimensional_array?(node, token, side: :left) &&
175
- !next_to_bracket?(token, side: :left)
174
+ multi_dimensional_array?(node, token, side: :left) && token.space_after?
176
175
  end
177
176
  end
178
177
 
179
178
  def multi_dimensional_array?(node, token, side: :right)
180
- i = index_for(node, token)
179
+ offset = side == :right ? -1 : +1
180
+ i = index_for(node, token) + offset
181
+ # TODO: change this type check once
182
+ # https://github.com/rubocop/rubocop-ast/pull/240 is merged
183
+ i += offset while processed_source.tokens_within(node)[i].new_line?
181
184
  if side == :right
182
- processed_source.tokens_within(node)[i - 1].right_bracket?
185
+ processed_source.tokens_within(node)[i].right_bracket?
183
186
  else
184
- processed_source.tokens_within(node)[i + 1].left_array_bracket?
187
+ processed_source.tokens_within(node)[i].left_array_bracket?
185
188
  end
186
189
  end
187
190
 
@@ -200,12 +203,13 @@ module RuboCop
200
203
  end
201
204
 
202
205
  def compact_corrections(corrector, node, left, right)
203
- if qualifies_for_compact?(node, left, side: :left)
206
+ if multi_dimensional_array?(node, left, side: :left)
204
207
  compact(corrector, left, :right)
205
208
  elsif !left.space_after?
206
209
  corrector.insert_after(left.pos, ' ')
207
210
  end
208
- if qualifies_for_compact?(node, right)
211
+
212
+ if multi_dimensional_array?(node, right)
209
213
  compact(corrector, right, :left)
210
214
  elsif !right.space_before?
211
215
  corrector.insert_before(right.pos, ' ')
@@ -213,7 +217,7 @@ module RuboCop
213
217
  end
214
218
 
215
219
  def compact(corrector, bracket, side)
216
- range = side_space_range(range: bracket.pos, side: side)
220
+ range = side_space_range(range: bracket.pos, side: side, include_newlines: true)
217
221
  corrector.remove(range)
218
222
  end
219
223
  end
@@ -46,10 +46,13 @@ module RuboCop
46
46
  # # bad
47
47
  # foo = { }
48
48
  # bar = { }
49
+ # baz = {
50
+ # }
49
51
  #
50
52
  # # good
51
53
  # foo = {}
52
54
  # bar = {}
55
+ # baz = {}
53
56
  #
54
57
  # @example EnforcedStyleForEmptyBraces: space
55
58
  # # The `space` EnforcedStyleForEmptyBraces style enforces that
@@ -60,8 +63,9 @@ module RuboCop
60
63
  #
61
64
  # # good
62
65
  # foo = { }
63
- # foo = { }
64
- # foo = { }
66
+ # foo = { }
67
+ # foo = {
68
+ # }
65
69
  #
66
70
  class SpaceInsideHashLiteralBraces < Base
67
71
  include SurroundingSpace
@@ -77,6 +81,7 @@ module RuboCop
77
81
 
78
82
  check(tokens[0], tokens[1])
79
83
  check(tokens[-2], tokens[-1]) if tokens.size > 2
84
+ check_whitespace_only_hash(node) if enforce_no_space_style_for_empty_braces?
80
85
  end
81
86
 
82
87
  private
@@ -103,7 +108,7 @@ module RuboCop
103
108
  if is_same_braces && style == :compact
104
109
  false
105
110
  elsif is_empty_braces
106
- cop_config['EnforcedStyleForEmptyBraces'] != 'no_space'
111
+ !enforce_no_space_style_for_empty_braces?
107
112
  else
108
113
  style != :no_space
109
114
  end
@@ -175,6 +180,26 @@ module RuboCop
175
180
 
176
181
  range_between(begin_pos, range.end_pos - 1)
177
182
  end
183
+
184
+ def check_whitespace_only_hash(node)
185
+ range = range_inside_hash(node)
186
+ return unless range.source.match?(/\A\s+\z/m)
187
+
188
+ add_offense(
189
+ range,
190
+ message: format(MSG, problem: 'empty hash literal braces detected')
191
+ ) do |corrector|
192
+ corrector.remove(range)
193
+ end
194
+ end
195
+
196
+ def range_inside_hash(node)
197
+ range_between(node.location.begin.end_pos, node.location.end.begin_pos)
198
+ end
199
+
200
+ def enforce_no_space_style_for_empty_braces?
201
+ cop_config['EnforcedStyleForEmptyBraces'] == 'no_space'
202
+ end
178
203
  end
179
204
  end
180
205
  end
@@ -45,7 +45,7 @@ module RuboCop
45
45
  # # bad
46
46
  # expect { do_something }.to change { object.attribute }
47
47
  #
48
- # @example AllowedPatterns: [/change/]
48
+ # @example AllowedPatterns: ['change']
49
49
  #
50
50
  # # good
51
51
  # expect { do_something }.to change { object.attribute }
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for duplicated magic comments.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ #
12
+ # # encoding: ascii
13
+ # # encoding: ascii
14
+ #
15
+ # # good
16
+ #
17
+ # # encoding: ascii
18
+ #
19
+ # # bad
20
+ #
21
+ # # frozen_string_literal: true
22
+ # # frozen_string_literal: true
23
+ #
24
+ # # good
25
+ #
26
+ # # frozen_string_literal: true
27
+ #
28
+ class DuplicateMagicComment < Base
29
+ include FrozenStringLiteral
30
+ include RangeHelp
31
+ extend AutoCorrector
32
+
33
+ MSG = 'Duplicate magic comment detected.'
34
+
35
+ def on_new_investigation
36
+ return if processed_source.buffer.source.empty?
37
+
38
+ magic_comment_lines.each_value do |comment_lines|
39
+ next if comment_lines.count <= 1
40
+
41
+ comment_lines[1..].each do |comment_line|
42
+ range = processed_source.buffer.line_range(comment_line + 1)
43
+
44
+ register_offense(range)
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def magic_comment_lines
52
+ comment_lines = { encoding_magic_comments: [], frozen_string_literal_magic_comments: [] }
53
+
54
+ leading_magic_comments.each.with_index do |magic_comment, index|
55
+ if magic_comment.encoding_specified?
56
+ comment_lines[:encoding_magic_comments] << index
57
+ elsif magic_comment.frozen_string_literal_specified?
58
+ comment_lines[:frozen_string_literal_magic_comments] << index
59
+ end
60
+ end
61
+
62
+ comment_lines
63
+ end
64
+
65
+ def register_offense(range)
66
+ add_offense(range) do |corrector|
67
+ corrector.remove(range_by_whole_lines(range, include_final_newline: true))
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -133,7 +133,7 @@ module RuboCop
133
133
  end
134
134
 
135
135
  def found_instance_method(node, name)
136
- return unless (scope = node.parent_module_name)
136
+ return found_sclass_method(node, name) unless (scope = node.parent_module_name)
137
137
 
138
138
  # Humanize the scope
139
139
  scope = scope.sub(
@@ -145,6 +145,16 @@ module RuboCop
145
145
  found_method(node, "#{scope}#{name}")
146
146
  end
147
147
 
148
+ def found_sclass_method(node, name)
149
+ singleton_ancestor = node.each_ancestor.find(&:sclass_type?)
150
+ return unless singleton_ancestor
151
+
152
+ singleton_receiver_node = singleton_ancestor.children[0]
153
+ return unless singleton_receiver_node.send_type?
154
+
155
+ found_method(node, "#{singleton_receiver_node.method_name}.#{name}")
156
+ end
157
+
148
158
  def found_method(node, method_name)
149
159
  if @definitions.key?(method_name)
150
160
  loc = case node.type
@@ -32,26 +32,40 @@ module RuboCop
32
32
  end
33
33
  end
34
34
 
35
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
35
36
  def each_repeated_character_class_element_loc(node)
36
37
  node.parsed_tree&.each_expression do |expr|
37
- next if expr.type != :set || expr.token == :intersection
38
+ next if skip_expression?(expr)
38
39
 
39
40
  seen = Set.new
41
+ enum = expr.expressions.to_enum
42
+ expression_count = expr.expressions.count
40
43
 
41
- expr.expressions.each do |child|
42
- next if within_interpolation?(node, child)
44
+ expression_count.times do |current_number|
45
+ current_child = enum.next
46
+ next if within_interpolation?(node, current_child)
43
47
 
44
- child_source = child.to_s
48
+ current_child_source = current_child.to_s
49
+ next_child = enum.peek if current_number + 1 < expression_count
45
50
 
46
- yield child.expression if seen.include?(child_source)
51
+ if seen.include?(current_child_source)
52
+ next if start_with_escaped_zero_number?(current_child_source, next_child.to_s)
47
53
 
48
- seen << child_source
54
+ yield current_child.expression
55
+ end
56
+
57
+ seen << current_child_source
49
58
  end
50
59
  end
51
60
  end
61
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
52
62
 
53
63
  private
54
64
 
65
+ def skip_expression?(expr)
66
+ expr.type != :set || expr.token == :intersection
67
+ end
68
+
55
69
  # Since we blank interpolations with a space for every char of the interpolation, we would
56
70
  # mark every space (except the first) as duplicate if we do not skip regexp_parser nodes
57
71
  # that are within an interpolation.
@@ -61,6 +75,11 @@ module RuboCop
61
75
  interpolation_locs(node).any? { |il| il.overlaps?(parse_tree_child_loc) }
62
76
  end
63
77
 
78
+ def start_with_escaped_zero_number?(current_child, next_child)
79
+ # Represents escaped code from `"\00"` (`"\u0000"`) to `"\07"` (`"\a"`).
80
+ current_child == '\\0' && next_child.match?(/[0-7]/)
81
+ end
82
+
64
83
  def interpolation_locs(node)
65
84
  @interpolation_locs ||= {}
66
85
 
@@ -85,7 +85,9 @@ module RuboCop
85
85
  private
86
86
 
87
87
  def body_or_allowed_comment_lines?(node)
88
- node.body || (cop_config['AllowComments'] && comment_lines?(node))
88
+ return true if node.body
89
+
90
+ cop_config['AllowComments'] && processed_source.contains_comment?(node.source_range)
89
91
  end
90
92
  end
91
93
  end