rubocop 1.22.3 → 1.23.0

Sign up to get free protection for your applications and to get access to all the features.
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: