rubocop 1.22.1 → 1.24.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +88 -8
  4. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  5. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  6. data/lib/rubocop/cli/command/show_docs_url.rb +48 -0
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  8. data/lib/rubocop/cli.rb +1 -0
  9. data/lib/rubocop/config_loader_resolver.rb +1 -1
  10. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  11. data/lib/rubocop/cop/bundler/gem_comment.rb +3 -3
  12. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  13. data/lib/rubocop/cop/correctors/if_then_corrector.rb +55 -0
  14. data/lib/rubocop/cop/documentation.rb +19 -2
  15. data/lib/rubocop/cop/gemspec/date_assignment.rb +2 -10
  16. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +1 -10
  17. data/lib/rubocop/cop/gemspec/require_mfa.rb +146 -0
  18. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +30 -23
  19. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -10
  20. data/lib/rubocop/cop/generator.rb +1 -1
  21. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +60 -0
  22. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +46 -0
  23. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +3 -1
  24. data/lib/rubocop/cop/internal_affairs.rb +2 -0
  25. data/lib/rubocop/cop/layout/assignment_indentation.rb +1 -1
  26. data/lib/rubocop/cop/layout/block_alignment.rb +3 -3
  27. data/lib/rubocop/cop/layout/comment_indentation.rb +31 -2
  28. data/lib/rubocop/cop/layout/dot_position.rb +13 -7
  29. data/lib/rubocop/cop/layout/empty_comment.rb +1 -1
  30. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +22 -1
  31. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +7 -4
  32. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  33. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
  34. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +1 -1
  35. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +1 -1
  36. data/lib/rubocop/cop/layout/hash_alignment.rb +2 -2
  37. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -1
  38. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  39. data/lib/rubocop/cop/layout/line_length.rb +1 -1
  40. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -1
  41. data/lib/rubocop/cop/layout/multiline_block_layout.rb +2 -2
  42. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +1 -1
  43. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
  44. data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
  45. data/lib/rubocop/cop/layout/space_before_comment.rb +1 -1
  46. data/lib/rubocop/cop/layout/space_before_first_arg.rb +4 -0
  47. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +11 -5
  48. data/lib/rubocop/cop/layout/space_inside_parens.rb +0 -4
  49. data/lib/rubocop/cop/lint/ambiguous_range.rb +3 -3
  50. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +1 -1
  51. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +16 -4
  52. data/lib/rubocop/cop/lint/deprecated_constants.rb +3 -2
  53. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +6 -0
  54. data/lib/rubocop/cop/lint/else_layout.rb +1 -1
  55. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +4 -0
  56. data/lib/rubocop/cop/lint/number_conversion.rb +5 -2
  57. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +117 -0
  58. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  59. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -9
  60. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  61. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  62. data/lib/rubocop/cop/metrics/parameter_lists.rb +5 -2
  63. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  64. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -2
  65. data/lib/rubocop/cop/mixin/enforce_superclass.rb +5 -0
  66. data/lib/rubocop/cop/mixin/gemspec_help.rb +30 -0
  67. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +4 -3
  68. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +56 -0
  69. data/lib/rubocop/cop/mixin/hash_transform_method.rb +3 -3
  70. data/lib/rubocop/cop/mixin/multiline_element_indentation.rb +1 -1
  71. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +1 -1
  72. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +1 -1
  73. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  74. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  75. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -5
  76. data/lib/rubocop/cop/mixin/trailing_body.rb +1 -1
  77. data/lib/rubocop/cop/naming/block_forwarding.rb +102 -0
  78. data/lib/rubocop/cop/naming/file_name.rb +37 -4
  79. data/lib/rubocop/cop/security/json_load.rb +1 -1
  80. data/lib/rubocop/cop/security/open.rb +11 -1
  81. data/lib/rubocop/cop/style/character_literal.rb +8 -1
  82. data/lib/rubocop/cop/style/collection_compact.rb +31 -13
  83. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  84. data/lib/rubocop/cop/style/commented_keyword.rb +5 -3
  85. data/lib/rubocop/cop/style/documentation.rb +1 -1
  86. data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
  87. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  88. data/lib/rubocop/cop/style/file_read.rb +112 -0
  89. data/lib/rubocop/cop/style/file_write.rb +98 -0
  90. data/lib/rubocop/cop/style/format_string_token.rb +2 -1
  91. data/lib/rubocop/cop/style/hash_conversion.rb +2 -1
  92. data/lib/rubocop/cop/style/hash_syntax.rb +22 -0
  93. data/lib/rubocop/cop/style/if_inside_else.rb +15 -0
  94. data/lib/rubocop/cop/style/line_end_concatenation.rb +1 -1
  95. data/lib/rubocop/cop/style/map_to_hash.rb +68 -0
  96. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +1 -1
  97. data/lib/rubocop/cop/style/multiline_when_then.rb +1 -1
  98. data/lib/rubocop/cop/style/numeric_literals.rb +10 -1
  99. data/lib/rubocop/cop/style/one_line_conditional.rb +18 -39
  100. data/lib/rubocop/cop/style/open_struct_use.rb +69 -0
  101. data/lib/rubocop/cop/style/parentheses_around_condition.rb +12 -2
  102. data/lib/rubocop/cop/style/quoted_symbols.rb +11 -1
  103. data/lib/rubocop/cop/style/redundant_interpolation.rb +17 -3
  104. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +5 -1
  105. data/lib/rubocop/cop/style/redundant_self.rb +1 -1
  106. data/lib/rubocop/cop/style/safe_navigation.rb +1 -5
  107. data/lib/rubocop/cop/style/select_by_regexp.rb +9 -3
  108. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  109. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  110. data/lib/rubocop/cop/team.rb +1 -1
  111. data/lib/rubocop/cop/util.rb +11 -1
  112. data/lib/rubocop/formatter/html_formatter.rb +5 -2
  113. data/lib/rubocop/formatter/json_formatter.rb +4 -1
  114. data/lib/rubocop/options.rb +6 -1
  115. data/lib/rubocop/remote_config.rb +2 -4
  116. data/lib/rubocop/result_cache.rb +1 -1
  117. data/lib/rubocop/rspec/parallel_formatter.rb +90 -0
  118. data/lib/rubocop/rspec/support.rb +1 -0
  119. data/lib/rubocop/target_finder.rb +1 -1
  120. data/lib/rubocop/version.rb +1 -1
  121. data/lib/rubocop/yaml_duplication_checker.rb +1 -1
  122. data/lib/rubocop.rb +11 -0
  123. metadata +24 -9
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Naming
6
+ # In Ruby 3.1, anonymous block forwarding has been added.
7
+ #
8
+ # This cop identifies places where `do_something(&block)` can be replaced
9
+ # by `do_something(&)`.
10
+ #
11
+ # It also supports the opposite style by alternative `explicit` option.
12
+ #
13
+ # @example EnforcedStyle: anonymous (default)
14
+ #
15
+ # # bad
16
+ # def foo(&block)
17
+ # bar(&block)
18
+ # end
19
+ #
20
+ # # good
21
+ # def foo(&)
22
+ # bar(&)
23
+ # end
24
+ #
25
+ # @example EnforcedStyle: explicit
26
+ #
27
+ # # bad
28
+ # def foo(&)
29
+ # bar(&)
30
+ # end
31
+ #
32
+ # # good
33
+ # def foo(&block)
34
+ # bar(&block)
35
+ # end
36
+ #
37
+ class BlockForwarding < Base
38
+ include ConfigurableEnforcedStyle
39
+ extend AutoCorrector
40
+ extend TargetRubyVersion
41
+
42
+ minimum_target_ruby_version 3.1
43
+
44
+ MSG = 'Use %<style>s block forwarding.'
45
+
46
+ def on_def(node)
47
+ return if node.arguments.empty?
48
+
49
+ last_argument = node.arguments.last
50
+ return if expected_block_forwarding_style?(node, last_argument)
51
+
52
+ register_offense(last_argument)
53
+
54
+ node.each_descendant(:block_pass) do |block_pass_node|
55
+ next if block_pass_node.children.first&.sym_type?
56
+
57
+ register_offense(block_pass_node)
58
+ end
59
+ end
60
+ alias on_defs on_def
61
+
62
+ private
63
+
64
+ def expected_block_forwarding_style?(node, last_argument)
65
+ if style == :anonymous
66
+ !explicit_block_argument?(last_argument) ||
67
+ use_kwarg_in_method_definition?(node) ||
68
+ use_block_argument_as_local_variable?(node, last_argument)
69
+ else
70
+ !anonymous_block_argument?(last_argument)
71
+ end
72
+ end
73
+
74
+ def use_kwarg_in_method_definition?(node)
75
+ node.arguments.each_descendant(:kwarg, :kwoptarg).any?
76
+ end
77
+
78
+ def anonymous_block_argument?(node)
79
+ node.blockarg_type? && node.name.nil?
80
+ end
81
+
82
+ def explicit_block_argument?(node)
83
+ node.blockarg_type? && !node.name.nil?
84
+ end
85
+
86
+ def use_block_argument_as_local_variable?(node, last_argument)
87
+ return if node.body.nil?
88
+
89
+ node.body.each_descendant(:lvar).any? do |lvar|
90
+ !lvar.parent.block_pass_type? && lvar.source == last_argument.source[1..-1]
91
+ end
92
+ end
93
+
94
+ def register_offense(block_argument)
95
+ add_offense(block_argument, message: format(MSG, style: style)) do |corrector|
96
+ corrector.replace(block_argument, '&')
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -14,6 +14,18 @@ module RuboCop
14
14
  # (i.e. `bundler-console` becomes `Bundler::Console`). As such, the
15
15
  # gemspec is supposed to be named `bundler-console.gemspec`.
16
16
  #
17
+ # When `ExpectMatchingDefinition` (default: `false`) is `true`, the cop requires
18
+ # each file to have a class, module or `Struct` defined in it that matches
19
+ # the filename. This can be further configured using
20
+ # `CheckDefinitionPathHierarchy` (default: `true`) to determine whether the
21
+ # path should match the namespace of the above definition.
22
+ #
23
+ # When `IgnoreExecutableScripts` (default: `true`) is `true`, files that start
24
+ # with a shebang line are not considered by the cop.
25
+ #
26
+ # When `Regex` is set, the cop will flag any filename that does not match
27
+ # the regular expression.
28
+ #
17
29
  # @example
18
30
  # # bad
19
31
  # lib/layoutManager.rb
@@ -28,11 +40,19 @@ module RuboCop
28
40
  include RangeHelp
29
41
 
30
42
  MSG_SNAKE_CASE = 'The name of this source file (`%<basename>s`) should use snake_case.'
31
- MSG_NO_DEFINITION = '%<basename>s should define a class or module called `%<namespace>s`.'
43
+ MSG_NO_DEFINITION = '`%<basename>s` should define a class or module called `%<namespace>s`.'
32
44
  MSG_REGEX = '`%<basename>s` should match `%<regex>s`.'
33
45
 
34
46
  SNAKE_CASE = /^[\d[[:lower:]]_.?!]+$/.freeze
35
47
 
48
+ # @!method struct_definition(node)
49
+ def_node_matcher :struct_definition, <<~PATTERN
50
+ {
51
+ (casgn $_ $_ (send (const {nil? cbase} :Struct) :new ...))
52
+ (casgn $_ $_ (block (send (const {nil? cbase} :Struct) :new ...) ...))
53
+ }
54
+ PATTERN
55
+
36
56
  def on_new_investigation
37
57
  file_path = processed_source.file_path
38
58
  return if config.file_to_exclude?(file_path) || config.allowed_camel_case_file?(file_path)
@@ -103,6 +123,10 @@ module RuboCop
103
123
  cop_config['CheckDefinitionPathHierarchy']
104
124
  end
105
125
 
126
+ def definition_path_hierarchy_roots
127
+ cop_config['CheckDefinitionPathHierarchyRoots'] || []
128
+ end
129
+
106
130
  def regex
107
131
  cop_config['Regex']
108
132
  end
@@ -126,7 +150,7 @@ module RuboCop
126
150
  name = namespace.pop
127
151
 
128
152
  on_node(%i[class module casgn], node) do |child|
129
- next unless (const = child.defined_module)
153
+ next unless (const = find_definition(child))
130
154
 
131
155
  const_namespace, const_name = *const
132
156
  next if name != const_name && !match_acronym?(name, const_name)
@@ -138,6 +162,15 @@ module RuboCop
138
162
  nil
139
163
  end
140
164
 
165
+ def find_definition(node)
166
+ node.defined_module || defined_struct(node)
167
+ end
168
+
169
+ def defined_struct(node)
170
+ namespace, name = *struct_definition(node)
171
+ s(:const, namespace, name) if name
172
+ end
173
+
141
174
  def match_namespace(node, namespace, expected)
142
175
  match_partial = partial_matcher!(expected)
143
176
 
@@ -177,13 +210,13 @@ module RuboCop
177
210
  allowed_acronyms.any? { |acronym| expected.gsub(acronym.capitalize, acronym) == name }
178
211
  end
179
212
 
180
- def to_namespace(path)
213
+ def to_namespace(path) # rubocop:disable Metrics/AbcSize
181
214
  components = Pathname(path).each_filename.to_a
182
215
  # To convert a pathname to a Ruby namespace, we need a starting point
183
216
  # But RC can be run from any working directory, and can check any path
184
217
  # We can't assume that the working directory, or any other, is the
185
218
  # "starting point" to build a namespace.
186
- start = %w[lib spec test src]
219
+ start = definition_path_hierarchy_roots
187
220
  start_index = nil
188
221
 
189
222
  # To find the closest namespace root take the path components, and
@@ -7,7 +7,7 @@ module RuboCop
7
7
  # security issues.
8
8
  #
9
9
  # @safety
10
- # Autocorrect is disabled by default because it's potentially dangerous.
10
+ # This cop's autocorrection is unsafe because it's potentially dangerous.
11
11
  # If using a stream, like `JSON.load(open('file'))`, it will need to call
12
12
  # `#read` manually, like `JSON.parse(open('file').read)`.
13
13
  # If reading single values (rather than proper JSON objects), like
@@ -3,7 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Security
6
- # This cop checks for the use of `Kernel#open` and `URI.open`.
6
+ # This cop checks for the use of `Kernel#open` and `URI.open` with dynamic
7
+ # data.
7
8
  #
8
9
  # `Kernel#open` and `URI.open` enable not only file access but also process
9
10
  # invocation by prefixing a pipe symbol (e.g., `open("| ls")`).
@@ -11,6 +12,9 @@ module RuboCop
11
12
  # the argument of `Kernel#open` and `URI.open`. It would be better to use
12
13
  # `File.open`, `IO.popen` or `URI.parse#open` explicitly.
13
14
  #
15
+ # NOTE: `open` and `URI.open` with literal strings are not flagged by this
16
+ # cop.
17
+ #
14
18
  # @safety
15
19
  # This cop could register false positives if `open` is redefined
16
20
  # in a class and then used without a receiver in that class.
@@ -18,12 +22,18 @@ module RuboCop
18
22
  # @example
19
23
  # # bad
20
24
  # open(something)
25
+ # open("| #{something}")
21
26
  # URI.open(something)
22
27
  #
23
28
  # # good
24
29
  # File.open(something)
25
30
  # IO.popen(something)
26
31
  # URI.parse(something).open
32
+ #
33
+ # # good (literal strings)
34
+ # open("foo.text")
35
+ # open("| foo")
36
+ # URI.open("http://example.com")
27
37
  class Open < Base
28
38
  MSG = 'The use of `%<receiver>sopen` is a serious security risk.'
29
39
  RESTRICT_ON_SEND = %i[open].freeze
@@ -4,6 +4,12 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for uses of the character literal ?x.
7
+ # Starting with Ruby 1.9 character literals are
8
+ # essentially one-character strings, so this syntax
9
+ # is mostly redundant at this point.
10
+ #
11
+ # ? character literal can be used to express meta and control character.
12
+ # That's a good use case of ? literal so it doesn't count it as an offense.
7
13
  #
8
14
  # @example
9
15
  # # bad
@@ -12,8 +18,9 @@ module RuboCop
12
18
  # # good
13
19
  # 'x'
14
20
  #
15
- # # good
21
+ # # good - control & meta escapes
16
22
  # ?\C-\M-d
23
+ # "\C-\M-d" # same as above
17
24
  class CharacterLiteral < Base
18
25
  include StringHelp
19
26
  extend AutoCorrector
@@ -16,6 +16,7 @@ module RuboCop
16
16
  #
17
17
  # @example
18
18
  # # bad
19
+ # array.reject(&:nil?)
19
20
  # array.reject { |e| e.nil? }
20
21
  # array.select { |e| !e.nil? }
21
22
  #
@@ -23,6 +24,7 @@ module RuboCop
23
24
  # array.compact
24
25
  #
25
26
  # # bad
27
+ # hash.reject!(&:nil?)
26
28
  # hash.reject! { |k, v| v.nil? }
27
29
  # hash.select! { |k, v| !v.nil? }
28
30
  #
@@ -37,11 +39,18 @@ module RuboCop
37
39
 
38
40
  RESTRICT_ON_SEND = %i[reject reject! select select!].freeze
39
41
 
42
+ # @!method reject_method_with_block_pass?(node)
43
+ def_node_matcher :reject_method_with_block_pass?, <<~PATTERN
44
+ (send _ {:reject :reject!}
45
+ (block_pass
46
+ (sym :nil?)))
47
+ PATTERN
48
+
40
49
  # @!method reject_method?(node)
41
50
  def_node_matcher :reject_method?, <<~PATTERN
42
51
  (block
43
52
  (send
44
- _ ${:reject :reject!})
53
+ _ {:reject :reject!})
45
54
  $(args ...)
46
55
  (send
47
56
  $(lvar _) :nil?))
@@ -51,7 +60,7 @@ module RuboCop
51
60
  def_node_matcher :select_method?, <<~PATTERN
52
61
  (block
53
62
  (send
54
- _ ${:select :select!})
63
+ _ {:select :select!})
55
64
  $(args ...)
56
65
  (send
57
66
  (send
@@ -59,16 +68,9 @@ module RuboCop
59
68
  PATTERN
60
69
 
61
70
  def on_send(node)
62
- block_node = node.parent
63
- return unless block_node&.block_type?
64
-
65
- return unless (method_name, args, receiver =
66
- reject_method?(block_node) || select_method?(block_node))
71
+ return unless (range = offense_range(node))
67
72
 
68
- return unless args.last.source == receiver.source
69
-
70
- range = offense_range(node, block_node)
71
- good = good_method_name(method_name)
73
+ good = good_method_name(node.method_name)
72
74
  message = format(MSG, good: good, bad: range.source)
73
75
 
74
76
  add_offense(range, message: message) { |corrector| corrector.replace(range, good) }
@@ -76,6 +78,22 @@ module RuboCop
76
78
 
77
79
  private
78
80
 
81
+ def offense_range(node)
82
+ if reject_method_with_block_pass?(node)
83
+ range(node, node)
84
+ else
85
+ block_node = node.parent
86
+
87
+ return unless block_node&.block_type?
88
+ unless (args, receiver = reject_method?(block_node) || select_method?(block_node))
89
+ return
90
+ end
91
+ return unless args.last.source == receiver.source
92
+
93
+ range(node, block_node)
94
+ end
95
+ end
96
+
79
97
  def good_method_name(method_name)
80
98
  if method_name.to_s.end_with?('!')
81
99
  'compact!'
@@ -84,8 +102,8 @@ module RuboCop
84
102
  end
85
103
  end
86
104
 
87
- def offense_range(send_node, block_node)
88
- range_between(send_node.loc.selector.begin_pos, block_node.loc.end.end_pos)
105
+ def range(begin_pos_node, end_pos_node)
106
+ range_between(begin_pos_node.loc.selector.begin_pos, end_pos_node.loc.end.end_pos)
89
107
  end
90
108
  end
91
109
  end
@@ -77,14 +77,14 @@ module RuboCop
77
77
 
78
78
  def collection_looping_method?(node)
79
79
  # TODO: Remove `Symbol#to_s` after supporting only Ruby >= 2.7.
80
- method_name = node.send_node.method_name.to_s
80
+ method_name = node.method_name.to_s
81
81
  method_name.start_with?('each') || method_name.end_with?('_each')
82
82
  end
83
83
 
84
84
  def same_collection_looping?(node, sibling)
85
85
  sibling&.block_type? &&
86
86
  sibling.send_node.method?(node.method_name) &&
87
- sibling.send_node.receiver == node.send_node.receiver &&
87
+ sibling.receiver == node.receiver &&
88
88
  sibling.send_node.arguments == node.send_node.arguments
89
89
  end
90
90
  end
@@ -52,9 +52,11 @@ module RuboCop
52
52
  ALLOWED_COMMENTS = %w[:nodoc: :yields: rubocop:disable rubocop:todo].freeze
53
53
  ALLOWED_COMMENT_REGEXES = ALLOWED_COMMENTS.map { |c| /#\s*#{c}/ }.freeze
54
54
 
55
+ REGEXP = /(?<keyword>\S+).*#/.freeze
56
+
55
57
  def on_new_investigation
56
58
  processed_source.comments.each do |comment|
57
- next unless offensive?(comment) && (match = line(comment).match(/(?<keyword>\S+).*#/))
59
+ next unless offensive?(comment) && (match = source_line(comment).match(REGEXP))
58
60
 
59
61
  register_offense(comment, match[:keyword])
60
62
  end
@@ -76,12 +78,12 @@ module RuboCop
76
78
  end
77
79
 
78
80
  def offensive?(comment)
79
- line = line(comment)
81
+ line = source_line(comment)
80
82
  KEYWORD_REGEXES.any? { |r| r.match?(line) } &&
81
83
  ALLOWED_COMMENT_REGEXES.none? { |r| r.match?(line) }
82
84
  end
83
85
 
84
- def line(comment)
86
+ def source_line(comment)
85
87
  comment.location.expression.source_line
86
88
  end
87
89
  end
@@ -176,7 +176,7 @@ module RuboCop
176
176
  end
177
177
 
178
178
  def qualify_const(node)
179
- return if node.nil?
179
+ return if node.nil? || node.cbase_type?
180
180
 
181
181
  [qualify_const(node.namespace), node.short_name].compact
182
182
  end
@@ -79,6 +79,8 @@ module RuboCop
79
79
  when_nodes.each do |when_node|
80
80
  conditions = when_node.conditions
81
81
 
82
+ replace_then_with_line_break(corrector, conditions, when_node)
83
+
82
84
  next unless conditions.size > 1
83
85
 
84
86
  range = range_between(conditions.first.source_range.begin_pos,
@@ -97,6 +99,14 @@ module RuboCop
97
99
  line_beginning = case_range.adjust(begin_pos: -case_range.column)
98
100
  corrector.insert_before(line_beginning, comments)
99
101
  end
102
+
103
+ def replace_then_with_line_break(corrector, conditions, when_node)
104
+ return unless when_node.parent.parent && when_node.then?
105
+
106
+ range = range_between(conditions.last.source_range.end_pos, when_node.loc.begin.end_pos)
107
+
108
+ corrector.replace(range, "\n")
109
+ end
100
110
  end
101
111
  end
102
112
  end
@@ -9,7 +9,7 @@ module RuboCop
9
9
  # to go on its own line (expanded style).
10
10
  #
11
11
  # NOTE: A method definition is not considered empty if it contains
12
- # comments.
12
+ # comments.
13
13
  #
14
14
  # @example EnforcedStyle: compact (default)
15
15
  # # bad
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Favor `File.(bin)read` convenience methods.
7
+ #
8
+ # @example
9
+ # ## text mode
10
+ # # bad
11
+ # File.open(filename).read
12
+ # File.open(filename, &:read)
13
+ # File.open(filename) { |f| f.read }
14
+ # File.open(filename) do |f|
15
+ # f.read
16
+ # end
17
+ # File.open(filename, 'r').read
18
+ # File.open(filename, 'r', &:read)
19
+ # File.open(filename, 'r') do |f|
20
+ # f.read
21
+ # end
22
+ #
23
+ # # good
24
+ # File.read(filename)
25
+ #
26
+ # @example
27
+ # ## binary mode
28
+ # # bad
29
+ # File.open(filename, 'rb').read
30
+ # File.open(filename, 'rb', &:read)
31
+ # File.open(filename, 'rb') do |f|
32
+ # f.read
33
+ # end
34
+ #
35
+ # # good
36
+ # File.binread(filename)
37
+ #
38
+ class FileRead < Base
39
+ extend AutoCorrector
40
+ include RangeHelp
41
+
42
+ MSG = 'Use `File.%<read_method>s`.'
43
+
44
+ RESTRICT_ON_SEND = %i[open].freeze
45
+
46
+ READ_FILE_START_TO_FINISH_MODES = %w[r rt rb r+ r+t r+b].to_set.freeze
47
+
48
+ # @!method file_open?(node)
49
+ def_node_matcher :file_open?, <<~PATTERN
50
+ (send
51
+ (const {nil? cbase} :File)
52
+ :open
53
+ $_
54
+ (str $%READ_FILE_START_TO_FINISH_MODES)?
55
+ $(block-pass (sym :read))?
56
+ )
57
+ PATTERN
58
+
59
+ # @!method send_read?(node)
60
+ def_node_matcher :send_read?, <<~PATTERN
61
+ (send _ :read)
62
+ PATTERN
63
+
64
+ # @!method block_read?(node)
65
+ def_node_matcher :block_read?, <<~PATTERN
66
+ (block _ (args (arg $_)) (send (lvar $_) :read))
67
+ PATTERN
68
+
69
+ def on_send(node)
70
+ evidence(node) do |filename, mode, read_node|
71
+ message = format(MSG, read_method: read_method(mode))
72
+
73
+ add_offense(read_node, message: message) do |corrector|
74
+ range = range_between(node.loc.selector.begin_pos, read_node.loc.expression.end_pos)
75
+ replacement = "#{read_method(mode)}(#{filename.source})"
76
+
77
+ corrector.replace(range, replacement)
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def evidence(node)
85
+ file_open?(node) do |filename, mode_array, block_pass|
86
+ read_node?(node, block_pass) do |read_node|
87
+ yield(filename, mode_array.first || 'r', read_node)
88
+ end
89
+ end
90
+ end
91
+
92
+ def read_node?(node, block_pass)
93
+ if block_pass.any?
94
+ yield(node)
95
+ elsif file_open_read?(node.parent)
96
+ yield(node.parent)
97
+ end
98
+ end
99
+
100
+ def file_open_read?(node)
101
+ return true if send_read?(node)
102
+
103
+ block_read?(node) { |block_arg, read_lvar| block_arg == read_lvar }
104
+ end
105
+
106
+ def read_method(mode)
107
+ mode.end_with?('b') ? :binread : :read
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Favor `File.(bin)write` convenience methods.
7
+ #
8
+ # @example
9
+ # ## text mode
10
+ # # bad
11
+ # File.open(filename, 'w').write(content)
12
+ # File.open(filename, 'w') do |f|
13
+ # f.write(content)
14
+ # end
15
+ #
16
+ # # good
17
+ # File.write(filename, content)
18
+ #
19
+ # @example
20
+ # ## binary mode
21
+ # # bad
22
+ # File.open(filename, 'wb').write(content)
23
+ # File.open(filename, 'wb') do |f|
24
+ # f.write(content)
25
+ # end
26
+ #
27
+ # # good
28
+ # File.binwrite(filename, content)
29
+ #
30
+ class FileWrite < Base
31
+ extend AutoCorrector
32
+ include RangeHelp
33
+
34
+ MSG = 'Use `File.%<write_method>s`.'
35
+
36
+ RESTRICT_ON_SEND = %i[open].to_set.freeze
37
+
38
+ TRUNCATING_WRITE_MODES = %w[w wt wb w+ w+t w+b].to_set.freeze
39
+
40
+ # @!method file_open?(node)
41
+ def_node_matcher :file_open?, <<~PATTERN
42
+ (send
43
+ (const {nil? cbase} :File)
44
+ :open
45
+ $_
46
+ (str $%TRUNCATING_WRITE_MODES)
47
+ (block-pass (sym :write))?
48
+ )
49
+ PATTERN
50
+
51
+ # @!method send_write?(node)
52
+ def_node_matcher :send_write?, <<~PATTERN
53
+ (send _ :write $_)
54
+ PATTERN
55
+
56
+ # @!method block_write?(node)
57
+ def_node_matcher :block_write?, <<~PATTERN
58
+ (block _ (args (arg $_)) (send (lvar $_) :write $_))
59
+ PATTERN
60
+
61
+ def on_send(node)
62
+ evidence(node) do |filename, mode, content, write_node|
63
+ message = format(MSG, write_method: write_method(mode))
64
+
65
+ add_offense(write_node, message: message) do |corrector|
66
+ range = range_between(node.loc.selector.begin_pos, write_node.loc.expression.end_pos)
67
+ replacement = "#{write_method(mode)}(#{filename.source}, #{content.source})"
68
+
69
+ corrector.replace(range, replacement)
70
+ end
71
+ end
72
+ end
73
+
74
+ def evidence(node)
75
+ file_open?(node) do |filename, mode|
76
+ file_open_write?(node.parent) do |content|
77
+ yield(filename, mode, content, node.parent)
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def file_open_write?(node)
85
+ content = send_write?(node) || block_write?(node) do |block_arg, lvar, write_arg|
86
+ write_arg if block_arg == lvar
87
+ end
88
+
89
+ yield(content) if content
90
+ end
91
+
92
+ def write_method(mode)
93
+ mode.end_with?('b') ? :binwrite : :write
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -102,7 +102,8 @@ module RuboCop
102
102
  end
103
103
 
104
104
  def use_ignored_method?(node)
105
- (parent = node.parent) && parent.send_type? && ignored_method?(parent.method_name)
105
+ send_parent = node.each_ancestor(:send).first
106
+ send_parent && ignored_method?(send_parent.method_name)
106
107
  end
107
108
 
108
109
  def unannotated_format?(node, detected_style)
@@ -101,7 +101,8 @@ module RuboCop
101
101
  end
102
102
 
103
103
  def requires_parens?(node)
104
- node.call_type? && node.arguments.any? && !node.parenthesized?
104
+ (node.call_type? && node.arguments.any? && !node.parenthesized?) ||
105
+ node.or_type? || node.and_type?
105
106
  end
106
107
 
107
108
  def multi_argument(node)