rubocop 1.22.3 → 1.23.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f74b024d0339618cd4782bd451e3bbe8be65eab0a5f3dbfc1d2137c74848c6fa
4
- data.tar.gz: ef762c615b2d68f52d5aac425abea13a40d097f1b7885cd1558a991dd23c136d
3
+ metadata.gz: cf11fe2d8f8be8879d6afc63d6f3be50b970c7910c83f1d22847d1fc9654d4aa
4
+ data.tar.gz: fd4c7ef3838a286efe3d1729672cc7a0e72f47cfb4c8d815699d53d2a974a372
5
5
  SHA512:
6
- metadata.gz: bfdba4bbc01e69d2a0873272815dc9c03d5e95f2dce5f02186b38f2f9e1e28ae34df246e6b548cdccf0831bace4f02bfad334f59548b2bf67430431b5760731b
7
- data.tar.gz: 662e06452c13b66a949e7a8035bc6b4b31e7af7a3cf161b9802634365a53a0b2452aeb7741ab13387186483cdf9027dc5cca736734d5d3022f78362bfabf9a5c
6
+ metadata.gz: 4014183525b58378a0e628ae5136d17c148a41c6c25dd51eddc810ca08fbd89d652f6c715b58eef149005af1145469936416864869b6e63a4c9e521627ab1cb4
7
+ data.tar.gz: 6e0e2a9a9d6fae829b73bd49814965a28d447207aaf7d1dda073f34130343557df965c77cc998d6ddb0d59eca09f1307d6ca56cbd3fcade575ca52950d1a56d3
data/README.md CHANGED
@@ -54,7 +54,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
54
54
  in your `Gemfile`:
55
55
 
56
56
  ```rb
57
- gem 'rubocop', '~> 1.22', require: false
57
+ gem 'rubocop', '~> 1.23', require: false
58
58
  ```
59
59
 
60
60
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -150,6 +150,7 @@ AllCops:
150
150
  rubocop-minitest: [minitest]
151
151
  rubocop-sequel: [sequel]
152
152
  rubocop-rake: [rake]
153
+ rubocop-graphql: [graphql]
153
154
 
154
155
  #################### Bundler ###############################
155
156
 
@@ -258,6 +259,15 @@ Gemspec/OrderedDependencies:
258
259
  Include:
259
260
  - '**/*.gemspec'
260
261
 
262
+ Gemspec/RequireMFA:
263
+ Description: 'Checks that the gemspec has metadata to require MFA from RubyGems.'
264
+ Enabled: pending
265
+ VersionAdded: '1.23'
266
+ Reference:
267
+ - https://guides.rubygems.org/mfa-requirement-opt-in/
268
+ Include:
269
+ - '**/*.gemspec'
270
+
261
271
  Gemspec/RequiredRubyVersion:
262
272
  Description: 'Checks that `required_ruby_version` of gemspec is specified and equal to `TargetRubyVersion` of .rubocop.yml.'
263
273
  Enabled: true
@@ -511,13 +521,13 @@ Layout/EmptyLineBetweenDefs:
511
521
  StyleGuide: '#empty-lines-between-methods'
512
522
  Enabled: true
513
523
  VersionAdded: '0.49'
514
- VersionChanged: '1.7'
524
+ VersionChanged: '1.23'
515
525
  EmptyLineBetweenMethodDefs: true
516
526
  EmptyLineBetweenClassDefs: true
517
527
  EmptyLineBetweenModuleDefs: true
518
- # If `true`, this parameter means that single line method definitions don't
519
- # need an empty line between them.
520
- AllowAdjacentOneLineDefs: false
528
+ # `AllowAdjacentOneLineDefs` means that single line method definitions don't
529
+ # need an empty line between them. `true` by default.
530
+ AllowAdjacentOneLineDefs: true
521
531
  # Can be array to specify minimum and maximum number of empty lines, e.g. [1, 2]
522
532
  NumberOfEmptyLines: 1
523
533
 
@@ -2308,6 +2318,11 @@ Lint/UselessMethodDefinition:
2308
2318
  Safe: false
2309
2319
  AllowComments: true
2310
2320
 
2321
+ Lint/UselessRuby2Keywords:
2322
+ Description: 'Finds unnecessary uses of `ruby2_keywords`.'
2323
+ Enabled: pending
2324
+ VersionAdded: '1.23'
2325
+
2311
2326
  Lint/UselessSetterCall:
2312
2327
  Description: 'Checks for useless setter call to a local variable.'
2313
2328
  Enabled: true
@@ -2501,6 +2516,7 @@ Naming/FileName:
2501
2516
  StyleGuide: '#snake-case-files'
2502
2517
  Enabled: true
2503
2518
  VersionAdded: '0.50'
2519
+ VersionChanged: '1.23'
2504
2520
  # Camel case file names listed in `AllCops:Include` and all file names listed
2505
2521
  # in `AllCops:Exclude` are excluded by default. Add extra excludes here.
2506
2522
  Exclude: []
@@ -2513,6 +2529,13 @@ Naming/FileName:
2513
2529
  # whether each source file's class or module name matches the file name --
2514
2530
  # not whether the nested module hierarchy matches the subdirectory path.
2515
2531
  CheckDefinitionPathHierarchy: true
2532
+ # paths that are considered root directories, for example "lib" in most ruby projects
2533
+ # or "app/models" in rails projects
2534
+ CheckDefinitionPathHierarchyRoots:
2535
+ - lib
2536
+ - spec
2537
+ - test
2538
+ - src
2516
2539
  # If non-`nil`, expect all source file names to match the following regex.
2517
2540
  # Only the file name itself is matched, not the entire file path.
2518
2541
  # Use anchors as necessary if you want to match the entire name rather than
@@ -4216,6 +4239,16 @@ Style/OneLineConditional:
4216
4239
  VersionAdded: '0.9'
4217
4240
  VersionChanged: '0.90'
4218
4241
 
4242
+ Style/OpenStructUse:
4243
+ Description: >-
4244
+ Avoid using OpenStruct. As of Ruby 3.0, use is officially discouraged due to performance,
4245
+ version compatibility, and potential security issues.
4246
+ Reference:
4247
+ - https://docs.ruby-lang.org/en/3.0.0/OpenStruct.html#class-OpenStruct-label-Caveats
4248
+
4249
+ Enabled: pending
4250
+ VersionAdded: '1.23'
4251
+
4219
4252
  Style/OptionHash:
4220
4253
  Description: "Don't use option hashes when you can use keyword arguments."
4221
4254
  Enabled: false
@@ -21,21 +21,13 @@ module RuboCop
21
21
  #
22
22
  class DateAssignment < Base
23
23
  include RangeHelp
24
+ include GemspecHelp
24
25
  extend AutoCorrector
25
26
 
26
27
  MSG = 'Do not use `date =` in gemspec, it is set automatically when the gem is packaged.'
27
28
 
28
- # @!method gem_specification(node)
29
- def_node_matcher :gem_specification, <<~PATTERN
30
- (block
31
- (send
32
- (const
33
- (const {cbase nil?} :Gem) :Specification) :new)
34
- ...)
35
- PATTERN
36
-
37
29
  def on_block(block_node)
38
- return unless gem_specification(block_node)
30
+ return unless gem_specification?(block_node)
39
31
 
40
32
  block_parameter = block_node.arguments.first.source
41
33
 
@@ -36,20 +36,11 @@ module RuboCop
36
36
  # end
37
37
  class DuplicatedAssignment < Base
38
38
  include RangeHelp
39
+ include GemspecHelp
39
40
 
40
41
  MSG = '`%<assignment>s` method calls already given on line '\
41
42
  '%<line_of_first_occurrence>d of the gemspec.'
42
43
 
43
- # @!method gem_specification(node)
44
- def_node_search :gem_specification, <<~PATTERN
45
- (block
46
- (send
47
- (const
48
- (const {cbase nil?} :Gem) :Specification) :new)
49
- (args
50
- (arg $_)) ...)
51
- PATTERN
52
-
53
44
  # @!method assignment_method_declarations(node)
54
45
  def_node_search :assignment_method_declarations, <<~PATTERN
55
46
  (send
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Gemspec
6
+ # Requires a gemspec to have `rubygems_mfa_required` metadata set.
7
+ #
8
+ # This setting tells RubyGems that MFA is required for accounts to
9
+ # be able perform any of these privileged operations:
10
+ #
11
+ # * gem push
12
+ # * gem yank
13
+ # * gem owner --add/remove
14
+ # * adding or removing owners using gem ownership page
15
+ #
16
+ # This helps make your gem more secure, as users can be more
17
+ # confident that gem updates were pushed by maintainers.
18
+ #
19
+ # @example
20
+ #
21
+ # # bad
22
+ # Gem::Specification.new do |spec|
23
+ # # no `rubygems_mfa_required` metadata specified
24
+ # end
25
+ #
26
+ # # good
27
+ # Gem::Specification.new do |spec|
28
+ # spec.metadata = {
29
+ # 'rubygems_mfa_required' => 'true'
30
+ # }
31
+ # end
32
+ #
33
+ # # good
34
+ # Gem::Specification.new do |spec|
35
+ # spec.metadata['rubygems_mfa_required'] = 'true'
36
+ # end
37
+ #
38
+ # # bad
39
+ # Gem::Specification.new do |spec|
40
+ # spec.metadata = {
41
+ # 'rubygems_mfa_required' => 'false'
42
+ # }
43
+ # end
44
+ #
45
+ # # good
46
+ # Gem::Specification.new do |spec|
47
+ # spec.metadata = {
48
+ # 'rubygems_mfa_required' => 'true'
49
+ # }
50
+ # end
51
+ #
52
+ # # bad
53
+ # Gem::Specification.new do |spec|
54
+ # spec.metadata['rubygems_mfa_required'] = 'false'
55
+ # end
56
+ #
57
+ # # good
58
+ # Gem::Specification.new do |spec|
59
+ # spec.metadata['rubygems_mfa_required'] = 'true'
60
+ # end
61
+ #
62
+ class RequireMFA < Base
63
+ include GemspecHelp
64
+ extend AutoCorrector
65
+
66
+ MSG = "`metadata['rubygems_mfa_required']` must be set to `'true'`."
67
+
68
+ # @!method metadata(node)
69
+ def_node_matcher :metadata, <<~PATTERN
70
+ `{
71
+ (send _ :metadata= $_)
72
+ (send (send _ :metadata) :[]= (str "rubygems_mfa_required") $_)
73
+ }
74
+ PATTERN
75
+
76
+ # @!method rubygems_mfa_required(node)
77
+ def_node_search :rubygems_mfa_required, <<~PATTERN
78
+ (pair (str "rubygems_mfa_required") $_)
79
+ PATTERN
80
+
81
+ # @!method true_string?(node)
82
+ def_node_matcher :true_string?, <<~PATTERN
83
+ (str "true")
84
+ PATTERN
85
+
86
+ def on_block(node) # rubocop:disable Metrics/MethodLength
87
+ gem_specification(node) do |block_var|
88
+ metadata_value = metadata(node)
89
+ mfa_value = mfa_value(metadata_value)
90
+
91
+ if mfa_value
92
+ unless true_string?(mfa_value)
93
+ add_offense(mfa_value) do |corrector|
94
+ change_value(corrector, mfa_value)
95
+ end
96
+ end
97
+ else
98
+ add_offense(node) do |corrector|
99
+ autocorrect(corrector, node, block_var, metadata_value)
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def mfa_value(metadata_value)
108
+ return unless metadata_value
109
+ return metadata_value if metadata_value.str_type?
110
+
111
+ rubygems_mfa_required(metadata_value).first
112
+ end
113
+
114
+ def autocorrect(corrector, node, block_var, metadata)
115
+ if metadata
116
+ return unless metadata.hash_type?
117
+
118
+ correct_metadata(corrector, metadata)
119
+ else
120
+ correct_missing_metadata(corrector, node, block_var)
121
+ end
122
+ end
123
+
124
+ def correct_metadata(corrector, metadata)
125
+ if metadata.pairs.any?
126
+ corrector.insert_after(metadata.pairs.last, ",\n'rubygems_mfa_required' => 'true'")
127
+ else
128
+ corrector.insert_before(metadata.loc.end, "'rubygems_mfa_required' => 'true'")
129
+ end
130
+ end
131
+
132
+ def correct_missing_metadata(corrector, node, block_var)
133
+ corrector.insert_before(node.loc.end, <<~RUBY)
134
+ #{block_var}.metadata = {
135
+ 'rubygems_mfa_required' => 'true'
136
+ }
137
+ RUBY
138
+ end
139
+
140
+ def change_value(corrector, value)
141
+ corrector.replace(value, "'true'")
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -26,20 +26,13 @@ module RuboCop
26
26
  # end
27
27
  #
28
28
  class RubyVersionGlobalsUsage < Base
29
+ include GemspecHelp
30
+
29
31
  MSG = 'Do not use `RUBY_VERSION` in gemspec file.'
30
32
 
31
33
  # @!method ruby_version?(node)
32
34
  def_node_matcher :ruby_version?, '(const {cbase nil?} :RUBY_VERSION)'
33
35
 
34
- # @!method gem_specification?(node)
35
- def_node_search :gem_specification?, <<~PATTERN
36
- (block
37
- (send
38
- (const
39
- (const {cbase nil?} :Gem) :Specification) :new)
40
- ...)
41
- PATTERN
42
-
43
36
  def on_const(node)
44
37
  return unless gem_spec_with_ruby_version?(node)
45
38
 
@@ -49,7 +42,7 @@ module RuboCop
49
42
  private
50
43
 
51
44
  def gem_spec_with_ruby_version?(node)
52
- gem_specification?(processed_source.ast) && ruby_version?(node)
45
+ gem_specification(processed_source.ast) && ruby_version?(node)
53
46
  end
54
47
  end
55
48
  end
@@ -150,7 +150,7 @@ module RuboCop
150
150
  1. Modify the description of #{badge} in config/default.yml
151
151
  2. Implement your new cop in the generated file!
152
152
  3. Commit your new cop with a message such as
153
- e.g. "Add new `#{badge}` cop."
153
+ e.g. "Add new `#{badge}` cop"
154
154
  4. Run `bundle exec rake changelog:new` to generate a changelog entry
155
155
  for your new cop.
156
156
  TODO
@@ -77,13 +77,34 @@ module RuboCop
77
77
  # def b
78
78
  # end
79
79
  #
80
- # @example AllowAdjacentOneLineDefs: true
80
+ # @example AllowAdjacentOneLineDefs: true (default)
81
81
  #
82
82
  # # good
83
83
  # class ErrorA < BaseError; end
84
84
  # class ErrorB < BaseError; end
85
85
  # class ErrorC < BaseError; end
86
86
  #
87
+ # # good
88
+ # class ErrorA < BaseError; end
89
+ #
90
+ # class ErrorB < BaseError; end
91
+ #
92
+ # class ErrorC < BaseError; end
93
+ #
94
+ # @example AllowAdjacentOneLineDefs: false
95
+ #
96
+ # # bad
97
+ # class ErrorA < BaseError; end
98
+ # class ErrorB < BaseError; end
99
+ # class ErrorC < BaseError; end
100
+ #
101
+ # # good
102
+ # class ErrorA < BaseError; end
103
+ #
104
+ # class ErrorB < BaseError; end
105
+ #
106
+ # class ErrorC < BaseError; end
107
+ #
87
108
  class EmptyLineBetweenDefs < Base
88
109
  include RangeHelp
89
110
  extend AutoCorrector
@@ -30,15 +30,17 @@ module RuboCop
30
30
  def on_send(node)
31
31
  return unless arrow_lambda_with_args?(node)
32
32
 
33
- lambda_node = range_of_offense(node)
34
-
35
33
  if style == :require_space && !space_after_arrow?(node)
34
+ lambda_node = range_of_offense(node)
35
+
36
36
  add_offense(lambda_node, message: MSG_REQUIRE_SPACE) do |corrector|
37
- corrector.insert_before(node.parent.children[1], ' ')
37
+ corrector.insert_before(lambda_arguments(node), ' ')
38
38
  end
39
39
  elsif style == :require_no_space && space_after_arrow?(node)
40
- add_offense(lambda_node, message: MSG_REQUIRE_NO_SPACE) do |corrector|
41
- corrector.remove(space_after_arrow(node))
40
+ space = space_after_arrow(node)
41
+
42
+ add_offense(space, message: MSG_REQUIRE_NO_SPACE) do |corrector|
43
+ corrector.remove(space)
42
44
  end
43
45
  end
44
46
  end
@@ -66,6 +68,10 @@ module RuboCop
66
68
  node.parent.arguments.loc.expression.end_pos
67
69
  )
68
70
  end
71
+
72
+ def lambda_arguments(node)
73
+ node.parent.children[1]
74
+ end
69
75
  end
70
76
  end
71
77
  end
@@ -8,7 +8,7 @@ module RuboCop
8
8
  # Ranges have quite low precedence, which leads to unexpected behaviour when
9
9
  # using a range with other operators. This cop avoids that by making ranges
10
10
  # explicit by requiring parenthesis around complex range boundaries (anything
11
- # that is not a basic literal: numerics, strings, symbols, etc.).
11
+ # that is not a literal: numerics, strings, symbols, etc.).
12
12
  #
13
13
  # This cop can be configured with `RequireParenthesesForMethodChains` in order to
14
14
  # specify whether method chains (including `self.foo`) should be wrapped in parens
@@ -81,7 +81,7 @@ module RuboCop
81
81
 
82
82
  def acceptable?(node)
83
83
  node.begin_type? ||
84
- node.basic_literal? ||
84
+ node.literal? ||
85
85
  node.variable? || node.const_type? || node.self_type? ||
86
86
  (node.call_type? && acceptable_call?(node))
87
87
  end
@@ -30,6 +30,7 @@ module RuboCop
30
30
  # '10'.to_i
31
31
  # '10.2'.to_f
32
32
  # '10'.to_c
33
+ # '1/3'.to_r
33
34
  # ['1', '2', '3'].map(&:to_i)
34
35
  # foo.try(:to_f)
35
36
  # bar.send(:to_c)
@@ -39,6 +40,7 @@ module RuboCop
39
40
  # Integer('10', 10)
40
41
  # Float('10.2')
41
42
  # Complex('10')
43
+ # Rational('1/3')
42
44
  # ['1', '2', '3'].map { |i| Integer(i, 10) }
43
45
  # foo.try { |i| Float(i) }
44
46
  # bar.send { |i| Complex(i) }
@@ -59,13 +61,14 @@ module RuboCop
59
61
  CONVERSION_METHOD_CLASS_MAPPING = {
60
62
  to_i: "#{Integer.name}(%<number_object>s, 10)",
61
63
  to_f: "#{Float.name}(%<number_object>s)",
62
- to_c: "#{Complex.name}(%<number_object>s)"
64
+ to_c: "#{Complex.name}(%<number_object>s)",
65
+ to_r: "#{Rational.name}(%<number_object>s)"
63
66
  }.freeze
64
67
  MSG = 'Replace unsafe number conversion with number '\
65
68
  'class parsing, instead of using '\
66
69
  '`%<current>s`, use stricter '\
67
70
  '`%<corrected_method>s`.'
68
- CONVERSION_METHODS = %i[Integer Float Complex to_i to_f to_c].freeze
71
+ CONVERSION_METHODS = %i[Integer Float Complex Rational to_i to_f to_c to_r].freeze
69
72
  METHODS = CONVERSION_METHOD_CLASS_MAPPING.keys.map(&:inspect).join(' ')
70
73
 
71
74
  # @!method to_method(node)
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop looks for `ruby2_keywords` calls for methods that do not need it.
7
+ #
8
+ # `ruby2_keywords` should only be called on methods that accept an argument splat
9
+ # (`*args`) but do not explicit keyword arguments (`k:` or `k: true`) or
10
+ # a keyword splat (`**kwargs`).
11
+ #
12
+ # @example
13
+ # # good (splat argument without keyword arguments)
14
+ # ruby2_keywords def foo(*args); end
15
+ #
16
+ # # bad (no arguments)
17
+ # ruby2_keywords def foo; end
18
+ #
19
+ # # good
20
+ # def foo; end
21
+ #
22
+ # # bad (positional argument)
23
+ # ruby2_keywords def foo(arg); end
24
+ #
25
+ # # good
26
+ # def foo(arg); end
27
+ #
28
+ # # bad (double splatted argument)
29
+ # ruby2_keywords def foo(**args); end
30
+ #
31
+ # # good
32
+ # def foo(**args); end
33
+ #
34
+ # # bad (keyword arguments)
35
+ # ruby2_keywords def foo(i:, j:); end
36
+ #
37
+ # # good
38
+ # def foo(i:, j:); end
39
+ #
40
+ # # bad (splat argument with keyword arguments)
41
+ # ruby2_keywords def foo(*args, i:, j:); end
42
+ #
43
+ # # good
44
+ # def foo(*args, i:, j:); end
45
+ #
46
+ # # bad (splat argument with double splat)
47
+ # ruby2_keywords def foo(*args, **kwargs); end
48
+ #
49
+ # # good
50
+ # def foo(*args, **kwargs); end
51
+ #
52
+ # # bad (ruby2_keywords given a symbol)
53
+ # def foo; end
54
+ # ruby2_keywords :foo
55
+ #
56
+ # # good
57
+ # def foo; end
58
+ #
59
+ # # bad (ruby2_keywords with dynamic method)
60
+ # define_method(:foo) { |arg| }
61
+ # ruby2_keywords :foo
62
+ #
63
+ # # good
64
+ # define_method(:foo) { |arg| }
65
+ #
66
+ class UselessRuby2Keywords < Base
67
+ MSG = '`ruby2_keywords` is unnecessary for method `%<method_name>s`.'
68
+ RESTRICT_ON_SEND = %i[ruby2_keywords].freeze
69
+
70
+ # Looks for statically or dynamically defined methods with a given name
71
+ # @!method method_definition(node, method_name)
72
+ def_node_matcher :method_definition, <<~PATTERN
73
+ {
74
+ (def %1 ...)
75
+ ({block numblock} (send _ :define_method (sym %1)) ...)
76
+ }
77
+ PATTERN
78
+
79
+ def on_send(node)
80
+ if node.first_argument.def_type?
81
+ inspect_def(node, node.first_argument)
82
+ elsif node.first_argument.sym_type?
83
+ inspect_sym(node, node.first_argument)
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def inspect_def(node, def_node)
90
+ return if allowed_arguments(def_node.arguments)
91
+
92
+ add_offense(node.loc.selector, message: format(MSG, method_name: def_node.method_name))
93
+ end
94
+
95
+ def inspect_sym(node, sym_node)
96
+ return unless node.parent
97
+
98
+ method_name = sym_node.value
99
+ definition = node.parent.each_child_node.detect { |n| method_definition(n, method_name) }
100
+
101
+ return unless definition
102
+ return if allowed_arguments(definition.arguments)
103
+
104
+ add_offense(node, message: format(MSG, method_name: method_name))
105
+ end
106
+
107
+ # `ruby2_keywords` is only allowed if there's a `restarg` and no keyword arguments
108
+ def allowed_arguments(arguments)
109
+ return false if arguments.empty?
110
+
111
+ arguments.each_child_node(:restarg).any? &&
112
+ arguments.each_child_node(:kwarg, :kwoptarg, :kwrestarg).none?
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # as they add less complexity than positional or optional parameters.
11
11
  #
12
12
  # NOTE: Explicit block argument `&block` is not counted to prevent
13
- # erroneous change that is avoided by making block argument implicit.
13
+ # erroneous change that is avoided by making block argument implicit.
14
14
  #
15
15
  # @example Max: 3
16
16
  # # good
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for checking gem declarations.
6
+ module GemspecHelp
7
+ extend NodePattern::Macros
8
+
9
+ # @!method gem_specification?(node)
10
+ def_node_matcher :gem_specification?, <<~PATTERN
11
+ (block
12
+ (send
13
+ (const
14
+ (const {cbase nil?} :Gem) :Specification) :new)
15
+ (args
16
+ (arg $_)) ...)
17
+ PATTERN
18
+
19
+ # @!method gem_specification(node)
20
+ def_node_search :gem_specification, <<~PATTERN
21
+ (block
22
+ (send
23
+ (const
24
+ (const {cbase nil?} :Gem) :Specification) :new)
25
+ (args
26
+ (arg $_)) ...)
27
+ PATTERN
28
+ end
29
+ end
30
+ end
@@ -13,11 +13,7 @@ module RuboCop
13
13
  if style == :single_quotes
14
14
  !double_quotes_required?(src)
15
15
  else
16
- # The string needs single quotes if:
17
- # 1. It contains a double quote
18
- # 2. It contains text that would become an escape sequence with double quotes
19
- # 3. It contains text that would become an interpolation with double quotes
20
- !/" | (?<!\\)\\[aAbcdefkMnprsStuUxzZ0-7] | \#[@{$]/x.match?(src)
16
+ !/" | \\[^'\\] | \#[@{$]/x.match?(src)
21
17
  end
22
18
  end
23
19
  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
@@ -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
@@ -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
@@ -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)
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop flags uses of OpenStruct, as it is now officially discouraged
7
+ # to be used for performance, version compatibility, and potential security issues.
8
+ #
9
+ # @safety
10
+ #
11
+ # Note that this cop may flag false positives; for instance, the following legal
12
+ # use of a hand-rolled `OpenStruct` type would be considered an offense:
13
+ #
14
+ # ```
15
+ # module MyNamespace
16
+ # class OpenStruct # not the OpenStruct we're looking for
17
+ # end
18
+ #
19
+ # def new_struct
20
+ # OpenStruct.new # resolves to MyNamespace::OpenStruct
21
+ # end
22
+ # end
23
+ # ```
24
+ #
25
+ # @example
26
+ #
27
+ # # bad
28
+ # point = OpenStruct.new(x: 0, y: 1)
29
+ #
30
+ # # good
31
+ # Point = Struct.new(:x, :y)
32
+ # point = Point.new(0, 1)
33
+ #
34
+ # # also good
35
+ # point = { x: 0, y: 1 }
36
+ #
37
+ # # bad
38
+ # test_double = OpenStruct.new(a: 'b')
39
+ #
40
+ # # good (assumes test using rspec-mocks)
41
+ # test_double = double
42
+ # allow(test_double).to receive(:a).and_return('b')
43
+ #
44
+ class OpenStructUse < Base
45
+ MSG = 'Avoid using `OpenStruct`; use `Struct`, `Hash`, a class or test doubles instead.'
46
+
47
+ # @!method uses_open_struct?(node)
48
+ def_node_matcher :uses_open_struct?, <<-PATTERN
49
+ (const {nil? (cbase)} :OpenStruct)
50
+ PATTERN
51
+
52
+ def on_const(node)
53
+ return unless uses_open_struct?(node)
54
+ return if custom_class_or_module_definition?(node)
55
+
56
+ add_offense(node)
57
+ end
58
+
59
+ private
60
+
61
+ def custom_class_or_module_definition?(node)
62
+ parent = node.parent
63
+
64
+ (parent.class_type? || parent.module_type?) && node.left_siblings.empty?
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -56,6 +56,7 @@ module RuboCop
56
56
  class ParenthesesAroundCondition < Base
57
57
  include SafeAssignment
58
58
  include Parentheses
59
+ include RangeHelp
59
60
  extend AutoCorrector
60
61
 
61
62
  def on_if(node)
@@ -73,13 +74,14 @@ module RuboCop
73
74
 
74
75
  # @!method control_op_condition(node)
75
76
  def_node_matcher :control_op_condition, <<~PATTERN
76
- (begin $_ ...)
77
+ (begin $_ $...)
77
78
  PATTERN
78
79
 
79
80
  def process_control_op(node)
80
81
  cond = node.condition
81
82
 
82
- control_op_condition(cond) do |first_child|
83
+ control_op_condition(cond) do |first_child, rest_children|
84
+ return if semicolon_separated_expressions?(first_child, rest_children)
83
85
  return if modifier_op?(first_child)
84
86
  return if parens_allowed?(cond)
85
87
 
@@ -90,6 +92,14 @@ module RuboCop
90
92
  end
91
93
  end
92
94
 
95
+ def semicolon_separated_expressions?(first_exp, rest_exps)
96
+ return false unless (second_exp = rest_exps.first)
97
+
98
+ range = range_between(first_exp.source_range.end_pos, second_exp.source_range.begin_pos)
99
+
100
+ range.source.include?(';')
101
+ end
102
+
93
103
  def modifier_op?(node)
94
104
  return false if node.if_type? && node.ternary?
95
105
  return true if node.rescue_type?
@@ -46,7 +46,7 @@ module RuboCop
46
46
 
47
47
  message = style == :single_quotes ? MSG_SINGLE : MSG_DOUBLE
48
48
 
49
- if wrong_quotes?(node)
49
+ if wrong_quotes?(node) || invalid_double_quotes?(node.source)
50
50
  add_offense(node, message: message) do |corrector|
51
51
  opposite_style_detected
52
52
  autocorrect(corrector, node)
@@ -58,6 +58,16 @@ module RuboCop
58
58
 
59
59
  private
60
60
 
61
+ def invalid_double_quotes?(source)
62
+ return false unless style == :double_quotes
63
+
64
+ # The string needs single quotes if:
65
+ # 1. It contains a double quote
66
+ # 2. It contains text that would become an escape sequence with double quotes
67
+ # 3. It contains text that would become an interpolation with double quotes
68
+ !/" | (?<!\\)\\[aAbcdefkMnprsStuUxzZ0-7] | \#[@{$]/x.match?(source)
69
+ end
70
+
61
71
  def autocorrect(corrector, node)
62
72
  str = if hash_colon_key?(node)
63
73
  # strip quotes
@@ -23,12 +23,15 @@ module RuboCop
23
23
  end
24
24
  end
25
25
 
26
+ Summary = Struct.new(:offense_count, :inspected_files, :target_files, keyword_init: true)
27
+ FileOffenses = Struct.new(:path, :offenses, keyword_init: true)
28
+
26
29
  attr_reader :files, :summary
27
30
 
28
31
  def initialize(output, options = {})
29
32
  super
30
33
  @files = []
31
- @summary = OpenStruct.new(offense_count: 0)
34
+ @summary = Summary.new(offense_count: 0)
32
35
  end
33
36
 
34
37
  def started(target_files)
@@ -36,7 +39,7 @@ module RuboCop
36
39
  end
37
40
 
38
41
  def file_finished(file, offenses)
39
- files << OpenStruct.new(path: file, offenses: offenses)
42
+ files << FileOffenses.new(path: file, offenses: offenses)
40
43
  summary.offense_count += offenses.count
41
44
  end
42
45
 
@@ -59,12 +59,15 @@ module RuboCop
59
59
  end
60
60
 
61
61
  # TODO: Consider better solution for Offense#real_column.
62
+ # The minimum value of `start_column: real_column` is 1.
63
+ # So, the minimum value of `last_column` should be 1.
64
+ # And non-zero value of `last_column` should be used as is.
62
65
  def hash_for_location(offense)
63
66
  {
64
67
  start_line: offense.line,
65
68
  start_column: offense.real_column,
66
69
  last_line: offense.last_line,
67
- last_column: offense.last_column,
70
+ last_column: offense.last_column.zero? ? 1 : offense.last_column,
68
71
  length: offense.location.length,
69
72
  # `line` and `column` exist for compatibility.
70
73
  # Use `start_line` and `start_column` instead.
@@ -33,7 +33,7 @@ module RuboCop
33
33
 
34
34
  def inherit_from_remote(file, path)
35
35
  new_uri = @uri.dup
36
- new_uri.path.gsub!(%r{/[^/]*$}, "/#{file}")
36
+ new_uri.path.gsub!(%r{/[^/]*$}, "/#{file.delete_prefix('./')}")
37
37
  RemoteConfig.new(new_uri.to_s, File.dirname(path))
38
38
  end
39
39
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.22.3'
6
+ STRING = '1.23.0'
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, '\
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
@@ -23,7 +23,7 @@ module RuboCop
23
23
  when Psych::Nodes::Mapping
24
24
  tree.children.each_slice(2).with_object([]) do |(key, value), keys|
25
25
  exist = keys.find { |key2| key2.value == key.value }
26
- on_duplicated.call(exist, key) if exist
26
+ yield(exist, key) if exist
27
27
  keys << key
28
28
  traverse(value, &on_duplicated)
29
29
  end
data/lib/rubocop.rb CHANGED
@@ -83,6 +83,7 @@ require_relative 'rubocop/cop/mixin/enforce_superclass'
83
83
  require_relative 'rubocop/cop/mixin/first_element_line_break'
84
84
  require_relative 'rubocop/cop/mixin/frozen_string_literal'
85
85
  require_relative 'rubocop/cop/mixin/gem_declaration'
86
+ require_relative 'rubocop/cop/mixin/gemspec_help'
86
87
  require_relative 'rubocop/cop/mixin/hash_alignment_styles'
87
88
  require_relative 'rubocop/cop/mixin/hash_transform_method'
88
89
  require_relative 'rubocop/cop/mixin/ignored_pattern'
@@ -160,6 +161,7 @@ require_relative 'rubocop/cop/bundler/ordered_gems'
160
161
  require_relative 'rubocop/cop/gemspec/date_assignment'
161
162
  require_relative 'rubocop/cop/gemspec/duplicated_assignment'
162
163
  require_relative 'rubocop/cop/gemspec/ordered_dependencies'
164
+ require_relative 'rubocop/cop/gemspec/require_mfa'
163
165
  require_relative 'rubocop/cop/gemspec/required_ruby_version'
164
166
  require_relative 'rubocop/cop/gemspec/ruby_version_globals_usage'
165
167
 
@@ -382,6 +384,7 @@ require_relative 'rubocop/cop/lint/useless_access_modifier'
382
384
  require_relative 'rubocop/cop/lint/useless_assignment'
383
385
  require_relative 'rubocop/cop/lint/useless_else_without_rescue'
384
386
  require_relative 'rubocop/cop/lint/useless_method_definition'
387
+ require_relative 'rubocop/cop/lint/useless_ruby2_keywords'
385
388
  require_relative 'rubocop/cop/lint/useless_setter_call'
386
389
  require_relative 'rubocop/cop/lint/useless_times'
387
390
  require_relative 'rubocop/cop/lint/void'
@@ -514,6 +517,7 @@ require_relative 'rubocop/cop/style/method_call_without_args_parentheses'
514
517
  require_relative 'rubocop/cop/style/method_call_with_args_parentheses'
515
518
  require_relative 'rubocop/cop/style/multiline_in_pattern_then'
516
519
  require_relative 'rubocop/cop/style/numbered_parameters'
520
+ require_relative 'rubocop/cop/style/open_struct_use'
517
521
  require_relative 'rubocop/cop/style/redundant_assignment'
518
522
  require_relative 'rubocop/cop/style/redundant_fetch_block'
519
523
  require_relative 'rubocop/cop/style/redundant_file_extension_in_require'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.22.3
4
+ version: 1.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2021-10-27 00:00:00.000000000 Z
13
+ date: 2021-11-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: parallel
@@ -250,6 +250,7 @@ files:
250
250
  - lib/rubocop/cop/gemspec/date_assignment.rb
251
251
  - lib/rubocop/cop/gemspec/duplicated_assignment.rb
252
252
  - lib/rubocop/cop/gemspec/ordered_dependencies.rb
253
+ - lib/rubocop/cop/gemspec/require_mfa.rb
253
254
  - lib/rubocop/cop/gemspec/required_ruby_version.rb
254
255
  - lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb
255
256
  - lib/rubocop/cop/generator.rb
@@ -493,6 +494,7 @@ files:
493
494
  - lib/rubocop/cop/lint/useless_assignment.rb
494
495
  - lib/rubocop/cop/lint/useless_else_without_rescue.rb
495
496
  - lib/rubocop/cop/lint/useless_method_definition.rb
497
+ - lib/rubocop/cop/lint/useless_ruby2_keywords.rb
496
498
  - lib/rubocop/cop/lint/useless_setter_call.rb
497
499
  - lib/rubocop/cop/lint/useless_times.rb
498
500
  - lib/rubocop/cop/lint/void.rb
@@ -538,6 +540,7 @@ files:
538
540
  - lib/rubocop/cop/mixin/first_element_line_break.rb
539
541
  - lib/rubocop/cop/mixin/frozen_string_literal.rb
540
542
  - lib/rubocop/cop/mixin/gem_declaration.rb
543
+ - lib/rubocop/cop/mixin/gemspec_help.rb
541
544
  - lib/rubocop/cop/mixin/hash_alignment_styles.rb
542
545
  - lib/rubocop/cop/mixin/hash_transform_method.rb
543
546
  - lib/rubocop/cop/mixin/heredoc.rb
@@ -740,6 +743,7 @@ files:
740
743
  - lib/rubocop/cop/style/numeric_literals.rb
741
744
  - lib/rubocop/cop/style/numeric_predicate.rb
742
745
  - lib/rubocop/cop/style/one_line_conditional.rb
746
+ - lib/rubocop/cop/style/open_struct_use.rb
743
747
  - lib/rubocop/cop/style/option_hash.rb
744
748
  - lib/rubocop/cop/style/optional_arguments.rb
745
749
  - lib/rubocop/cop/style/optional_boolean_parameter.rb
@@ -899,8 +903,9 @@ metadata:
899
903
  homepage_uri: https://rubocop.org/
900
904
  changelog_uri: https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md
901
905
  source_code_uri: https://github.com/rubocop/rubocop/
902
- documentation_uri: https://docs.rubocop.org/rubocop/1.22/
906
+ documentation_uri: https://docs.rubocop.org/rubocop/1.23/
903
907
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
908
+ rubygems_mfa_required: 'true'
904
909
  post_install_message:
905
910
  rdoc_options: []
906
911
  require_paths: