rubocop 1.19.1 → 1.20.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: 5866f1cc7ec3201aa598d3121997137dded621d0cf508430d3c9dd4b4c10274a
4
- data.tar.gz: d857fd81c1d461020a67b6139f9684082f778c8ecb227200d7a0d9f3307940b4
3
+ metadata.gz: e89a054e431c4e739e8e4b0bcdc36452704f1cffd6bda1e5710241e6712161a9
4
+ data.tar.gz: 632d9503cddcb39443ad8fc8b31bd1f1576803e9490ae77d22ee06f34d40784b
5
5
  SHA512:
6
- metadata.gz: 2162afd65c9b167710c41a61062d9e4daf67f7dcd58ab6ca1371454b89cd69b05316d27f0ded2ace8f458aeadda50d4618e4a09a56b51ccdfde97846943be45a
7
- data.tar.gz: bd53f2af2d7abbee2fb73b951cea6617d2345c976d3fde9ff017d37a17e35c074bc162ae0118706f0049e018c4b8bfbf383cf2705c9cdce908a3083989ef56f1
6
+ metadata.gz: b765c92c6118f7e8e958d0787714764c392453efa450048629002a69634fe032648a6f88654afc522d9ca01b2fb2c7f70c573d6bd9a227e85168cfcbe5613091
7
+ data.tar.gz: 22d0b80509def399c10586f579f08520e47c1d03a7eab14e0c9b998d9fea0b7a477333677c0af2aed3e768bb9c9d6a30f51a88f7d9e4973acd8057389ac01357
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.19', require: false
57
+ gem 'rubocop', '~> 1.20', 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
@@ -174,6 +174,20 @@ Bundler/GemComment:
174
174
  IgnoredGems: []
175
175
  OnlyFor: []
176
176
 
177
+ Bundler/GemFilename:
178
+ Description: 'Enforces the filename for managing gems.'
179
+ Enabled: true
180
+ VersionAdded: '1.20'
181
+ EnforcedStyle: 'Gemfile'
182
+ SupportedStyles:
183
+ - 'Gemfile'
184
+ - 'gems.rb'
185
+ Include:
186
+ - '**/Gemfile'
187
+ - '**/gems.rb'
188
+ - '**/Gemfile.lock'
189
+ - '**/gems.locked'
190
+
177
191
  Bundler/GemVersion:
178
192
  Description: 'Requires or forbids specifying gem versions.'
179
193
  Enabled: false
@@ -1519,6 +1533,11 @@ Lint/Debugger:
1519
1533
  Capybara:
1520
1534
  - save_and_open_page
1521
1535
  - save_and_open_screenshot
1536
+ debug.rb:
1537
+ - binding.b
1538
+ - binding.break
1539
+ - Kernel.binding.b
1540
+ - Kernel.binding.break
1522
1541
  Pry:
1523
1542
  - binding.pry
1524
1543
  - binding.remote_pry
@@ -1527,6 +1546,8 @@ Lint/Debugger:
1527
1546
  Rails:
1528
1547
  - debugger
1529
1548
  - Kernel.debugger
1549
+ RubyJard:
1550
+ - jard
1530
1551
  WebConsole:
1531
1552
  - binding.console
1532
1553
 
@@ -3149,7 +3170,7 @@ Style/CommentAnnotation:
3149
3170
  StyleGuide: '#annotate-keywords'
3150
3171
  Enabled: true
3151
3172
  VersionAdded: '0.10'
3152
- VersionChanged: '1.3'
3173
+ VersionChanged: '1.20'
3153
3174
  Keywords:
3154
3175
  - TODO
3155
3176
  - FIXME
@@ -4692,8 +4713,9 @@ Style/StructInheritance:
4692
4713
  Description: 'Checks for inheritance from Struct.new.'
4693
4714
  StyleGuide: '#no-extend-struct-new'
4694
4715
  Enabled: true
4716
+ SafeAutoCorrect: false
4695
4717
  VersionAdded: '0.29'
4696
- VersionChanged: '0.86'
4718
+ VersionChanged: '1.20'
4697
4719
 
4698
4720
  Style/SwapValues:
4699
4721
  Description: 'This cop enforces the use of shorthand-style swapping of 2 variables.'
@@ -161,7 +161,7 @@ module RuboCop
161
161
  def warn_pending_cop(cop)
162
162
  version = cop.metadata['VersionAdded'] || 'N/A'
163
163
 
164
- warn Rainbow("#{cop.name}: # (new in #{version})").yellow
164
+ warn Rainbow("#{cop.name}: # new in #{version}").yellow
165
165
  warn Rainbow(' Enabled: true').yellow
166
166
  end
167
167
 
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Bundler
6
+ # This cop verifies that a project contains Gemfile or gems.rb file and correct
7
+ # associated lock file based on the configuration.
8
+ #
9
+ # @example EnforcedStyle: Gemfile (default)
10
+ # # bad
11
+ # Project contains gems.rb and gems.locked files
12
+ #
13
+ # # bad
14
+ # Project contains Gemfile and gems.locked file
15
+ #
16
+ # # good
17
+ # Project contains Gemfile and Gemfile.lock
18
+ #
19
+ # @example EnforcedStyle: gems.rb
20
+ # # bad
21
+ # Project contains Gemfile and Gemfile.lock files
22
+ #
23
+ # # bad
24
+ # Project contains gems.rb and Gemfile.lock file
25
+ #
26
+ # # good
27
+ # Project contains gems.rb and gems.locked files
28
+ class GemFilename < Base
29
+ include ConfigurableEnforcedStyle
30
+ include RangeHelp
31
+
32
+ MSG_GEMFILE_REQUIRED = '`gems.rb` file was found but `Gemfile` is required '\
33
+ '(file path: %<file_path>s).'
34
+ MSG_GEMS_RB_REQUIRED = '`Gemfile` was found but `gems.rb` file is required '\
35
+ '(file path: %<file_path>s).'
36
+ MSG_GEMFILE_MISMATCHED = 'Expected a `Gemfile.lock` with `Gemfile` but found '\
37
+ '`gems.locked` file (file path: %<file_path>s).'
38
+ MSG_GEMS_RB_MISMATCHED = 'Expected a `gems.locked` file with `gems.rb` but found '\
39
+ '`Gemfile.lock` (file path: %<file_path>s).'
40
+ GEMFILE_FILES = %w[Gemfile Gemfile.lock].freeze
41
+ GEMS_RB_FILES = %w[gems.rb gems.locked].freeze
42
+
43
+ def on_new_investigation
44
+ file_path = processed_source.file_path
45
+ basename = File.basename(file_path)
46
+ return if expected_gemfile?(basename)
47
+
48
+ register_offense(file_path, basename)
49
+ end
50
+
51
+ private
52
+
53
+ def register_offense(file_path, basename)
54
+ register_gemfile_offense(file_path, basename) if gemfile_offense?(basename)
55
+ register_gems_rb_offense(file_path, basename) if gems_rb_offense?(basename)
56
+ end
57
+
58
+ def register_gemfile_offense(file_path, basename)
59
+ message = case basename
60
+ when 'gems.rb'
61
+ MSG_GEMFILE_REQUIRED
62
+ when 'gems.locked'
63
+ MSG_GEMFILE_MISMATCHED
64
+ end
65
+
66
+ add_global_offense(format(message, file_path: file_path))
67
+ end
68
+
69
+ def register_gems_rb_offense(file_path, basename)
70
+ message = case basename
71
+ when 'Gemfile'
72
+ MSG_GEMS_RB_REQUIRED
73
+ when 'Gemfile.lock'
74
+ MSG_GEMS_RB_MISMATCHED
75
+ end
76
+
77
+ add_global_offense(format(message, file_path: file_path))
78
+ end
79
+
80
+ def gemfile_offense?(basename)
81
+ gemfile_required? && GEMS_RB_FILES.include?(basename)
82
+ end
83
+
84
+ def gems_rb_offense?(basename)
85
+ gems_rb_required? && GEMFILE_FILES.include?(basename)
86
+ end
87
+
88
+ def expected_gemfile?(basename)
89
+ (gemfile_required? && GEMFILE_FILES.include?(basename)) ||
90
+ (gems_rb_required? && GEMS_RB_FILES.include?(basename))
91
+ end
92
+
93
+ def gemfile_required?
94
+ style == :Gemfile
95
+ end
96
+
97
+ def gems_rb_required?
98
+ style == :'gems.rb'
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -6,6 +6,9 @@ module RuboCop
6
6
  # This cop ensures that each argument in a multi-line method call
7
7
  # starts on a separate line.
8
8
  #
9
+ # NOTE: this cop does not move the first argument, if you want that to
10
+ # be on a separate line, see `Layout/FirstMethodArgumentLineBreak`.
11
+ #
9
12
  # @example
10
13
  #
11
14
  # # bad
@@ -7,8 +7,8 @@ module RuboCop
7
7
  # not be kept for production code.
8
8
  #
9
9
  # The cop can be configured using `DebuggerMethods`. By default, a number of gems
10
- # debug entrypoints are configured (`Kernel`, `Byebug`, `Capybara`, `Pry`, `Rails`,
11
- # and `WebConsole`). Additional methods can be added.
10
+ # debug entrypoints are configured (`Kernel`, `Byebug`, `Capybara`, `debug.rb`,
11
+ # `Pry`, `Rails`, `RubyJard`, and `WebConsole`). Additional methods can be added.
12
12
  #
13
13
  # Specific default groups can be disabled if necessary:
14
14
  #
@@ -2,40 +2,63 @@
2
2
 
3
3
  module RuboCop
4
4
  module Cop
5
- module Style
6
- # Common functionality related to annotation comments.
7
- module AnnotationComment
8
- private
9
-
10
- # @api public
11
- def annotation?(comment)
12
- _margin, first_word, colon, space, note = split_comment(comment)
13
- keyword_appearance?(first_word, colon, space) &&
14
- !just_first_word_of_sentence?(first_word, colon, space, note)
15
- end
16
-
17
- # @api public
18
- def split_comment(comment)
19
- match = comment.text.match(/^(# ?)([A-Za-z]+)(\s*:)?(\s+)?(\S+)?/)
20
- return false unless match
21
-
22
- match.captures
23
- end
24
-
25
- # @api public
26
- def keyword_appearance?(first_word, colon, space)
27
- first_word && keyword?(first_word.upcase) && (colon || space)
28
- end
29
-
30
- # @api private
31
- def just_first_word_of_sentence?(first_word, colon, space, note)
32
- first_word == first_word.capitalize && !colon && space && note
33
- end
34
-
35
- # @api public
36
- def keyword?(word)
37
- config.for_cop('Style/CommentAnnotation')['Keywords'].include?(word)
38
- end
5
+ # Representation of an annotation comment in source code (eg. `# TODO: blah blah blah`).
6
+ class AnnotationComment
7
+ extend Forwardable
8
+
9
+ attr_reader :comment, :margin, :keyword, :colon, :space, :note
10
+
11
+ # @param [Parser::Source::Comment] comment
12
+ # @param [Array<String>] keywords
13
+ def initialize(comment, keywords)
14
+ @comment = comment
15
+ @keywords = keywords
16
+ @margin, @keyword, @colon, @space, @note = split_comment(comment)
17
+ end
18
+
19
+ def annotation?
20
+ keyword_appearance? && !just_keyword_of_sentence?
21
+ end
22
+
23
+ def correct?(colon:)
24
+ return false unless keyword && space && note
25
+ return false unless keyword == keyword.upcase
26
+
27
+ self.colon.nil? == !colon
28
+ end
29
+
30
+ # Returns the range bounds for just the annotation
31
+ def bounds
32
+ start = comment.loc.expression.begin_pos + margin.length
33
+ length = [keyword, colon, space].reduce(0) { |len, elem| len + elem.to_s.length }
34
+ [start, start + length]
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :keywords
40
+
41
+ def split_comment(comment)
42
+ # Sort keywords by reverse length so that if a keyword is in a phrase
43
+ # but also on its own, both will match properly.
44
+ keywords_regex = Regexp.new(
45
+ Regexp.union(keywords.sort_by { |w| -w.length }).source,
46
+ Regexp::IGNORECASE
47
+ )
48
+ regex = /^(# ?)(\b#{keywords_regex}\b)(\s*:)?(\s+)?(\S+)?/i
49
+
50
+ match = comment.text.match(regex)
51
+ return false unless match
52
+
53
+ match.captures
54
+ end
55
+
56
+ def keyword_appearance?
57
+ keyword && (colon || space)
58
+ end
59
+
60
+ def just_keyword_of_sentence?
61
+ keyword == keyword.capitalize && !colon && space && note
39
62
  end
40
63
  end
41
64
  end
@@ -5,7 +5,6 @@ module RuboCop
5
5
  # Common functionality for checking documentation.
6
6
  module DocumentationComment
7
7
  extend NodePattern::Macros
8
- include Style::AnnotationComment
9
8
 
10
9
  private
11
10
 
@@ -15,7 +14,7 @@ module RuboCop
15
14
  return false unless preceding_comment?(node, preceding_lines.last)
16
15
 
17
16
  preceding_lines.any? do |comment|
18
- !annotation?(comment) &&
17
+ !AnnotationComment.new(comment, annotation_keywords).annotation? &&
19
18
  !interpreter_directive_comment?(comment) &&
20
19
  !rubocop_directive_comment?(comment)
21
20
  end
@@ -44,6 +43,10 @@ module RuboCop
44
43
  def rubocop_directive_comment?(comment)
45
44
  !!DirectiveComment.new(comment).match_captures
46
45
  end
46
+
47
+ def annotation_keywords
48
+ config.for_cop('Style/CommentAnnotation')['Keywords']
49
+ end
47
50
  end
48
51
  end
49
52
  end
@@ -8,7 +8,10 @@ module RuboCop
8
8
 
9
9
  FROZEN_STRING_LITERAL = '# frozen_string_literal:'
10
10
  FROZEN_STRING_LITERAL_ENABLED = '# frozen_string_literal: true'
11
- FROZEN_STRING_LITERAL_TYPES = %i[str dstr].freeze
11
+ FROZEN_STRING_LITERAL_TYPES_RUBY27 = %i[str dstr].freeze
12
+ FROZEN_STRING_LITERAL_TYPES_RUBY30 = %i[str].freeze
13
+
14
+ private_constant :FROZEN_STRING_LITERAL_TYPES_RUBY27, :FROZEN_STRING_LITERAL_TYPES_RUBY30
12
15
 
13
16
  def frozen_string_literal_comment_exists?
14
17
  leading_comment_lines.any? { |line| MagicComment.parse(line).valid_literal_value? }
@@ -16,6 +19,16 @@ module RuboCop
16
19
 
17
20
  private
18
21
 
22
+ def frozen_string_literal?(node)
23
+ literal_types = if target_ruby_version >= 3.0
24
+ FROZEN_STRING_LITERAL_TYPES_RUBY30
25
+ else
26
+ FROZEN_STRING_LITERAL_TYPES_RUBY27
27
+ end
28
+
29
+ literal_types.include?(node.type) && frozen_string_literals_enabled?
30
+ end
31
+
19
32
  def frozen_string_literals_enabled?
20
33
  ruby_version = processed_source.ruby_version
21
34
  return false unless ruby_version
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/ClassLength
3
4
  module RuboCop
4
5
  module Cop
5
6
  module Style
@@ -191,7 +192,7 @@ module RuboCop
191
192
  if node.braces?
192
193
  replace_braces_with_do_end(corrector, node.loc)
193
194
  else
194
- replace_do_end_with_braces(corrector, node.loc)
195
+ replace_do_end_with_braces(corrector, node)
195
196
  end
196
197
  end
197
198
 
@@ -256,7 +257,8 @@ module RuboCop
256
257
  corrector.replace(e, 'end')
257
258
  end
258
259
 
259
- def replace_do_end_with_braces(corrector, loc)
260
+ def replace_do_end_with_braces(corrector, node)
261
+ loc = node.loc
260
262
  b = loc.begin
261
263
  e = loc.end
262
264
 
@@ -264,6 +266,8 @@ module RuboCop
264
266
 
265
267
  corrector.replace(b, '{')
266
268
  corrector.replace(e, '}')
269
+
270
+ corrector.wrap(node.body, "begin\n", "\nend") if begin_required?(node)
267
271
  end
268
272
 
269
273
  def whitespace_before?(range)
@@ -275,14 +279,20 @@ module RuboCop
275
279
  end
276
280
 
277
281
  def move_comment_before_block(corrector, comment, block_node, closing_brace)
278
- range = range_between(closing_brace.end_pos, comment.loc.expression.end_pos)
279
-
280
- corrector.remove(range_with_surrounding_space(range: range, side: :right))
281
- corrector.insert_after(closing_brace, "\n")
282
+ range = block_node.chained? ? end_of_chain(block_node.parent).source_range : closing_brace
283
+ comment_range = range_between(range.end_pos, comment.loc.expression.end_pos)
284
+ corrector.remove(range_with_surrounding_space(range: comment_range, side: :right))
285
+ corrector.insert_after(range, "\n")
282
286
 
283
287
  corrector.insert_before(block_node, "#{comment.text}\n")
284
288
  end
285
289
 
290
+ def end_of_chain(node)
291
+ return node unless node.chained?
292
+
293
+ end_of_chain(node.parent)
294
+ end
295
+
286
296
  def get_blocks(node, &block)
287
297
  case node.type
288
298
  when :block
@@ -407,7 +417,14 @@ module RuboCop
407
417
  def array_or_range?(node)
408
418
  node.array_type? || node.range_type?
409
419
  end
420
+
421
+ def begin_required?(block_node)
422
+ # If the block contains `rescue` or `ensure`, it needs to be wrapped in
423
+ # `begin`...`end` when changing `do-end` to `{}`.
424
+ block_node.each_child_node(:rescue, :ensure).any? && !block_node.single_line?
425
+ end
410
426
  end
411
427
  end
412
428
  end
413
429
  end
430
+ # rubocop:enable Metrics/ClassLength
@@ -6,6 +6,9 @@ module RuboCop
6
6
  # This cop checks that comment annotation keywords are written according
7
7
  # to guidelines.
8
8
  #
9
+ # Annotation keywords can be specified by overriding the cop's `Keywords`
10
+ # configuration. Keywords are allowed to be single words or phrases.
11
+ #
9
12
  # NOTE: With a multiline comment block (where each line is only a
10
13
  # comment), only the first line will be able to register an offense, even
11
14
  # if an annotation keyword starts another line. This is done to prevent
@@ -56,7 +59,6 @@ module RuboCop
56
59
  # # good
57
60
  # # OPTIMIZE does not work
58
61
  class CommentAnnotation < Base
59
- include AnnotationComment
60
62
  include RangeHelp
61
63
  extend AutoCorrector
62
64
 
@@ -73,27 +75,27 @@ module RuboCop
73
75
  next unless first_comment_line?(processed_source.comments, index) ||
74
76
  inline_comment?(comment)
75
77
 
76
- margin, first_word, colon, space, note = split_comment(comment)
77
- next unless annotation?(comment) && !correct_annotation?(first_word, colon, space, note)
78
-
79
- range = annotation_range(comment, margin, first_word, colon, space)
78
+ annotation = AnnotationComment.new(comment, keywords)
79
+ next unless annotation.annotation? && !annotation.correct?(colon: requires_colon?)
80
80
 
81
- register_offense(range, note, first_word)
81
+ register_offense(annotation)
82
82
  end
83
83
  end
84
84
 
85
85
  private
86
86
 
87
- def register_offense(range, note, first_word)
88
- message = requires_colon? ? MSG_COLON_STYLE : MSG_SPACE_STYLE
87
+ def register_offense(annotation)
88
+ range = annotation_range(annotation)
89
+ message = if annotation.note
90
+ requires_colon? ? MSG_COLON_STYLE : MSG_SPACE_STYLE
91
+ else
92
+ MISSING_NOTE
93
+ end
89
94
 
90
- add_offense(
91
- range,
92
- message: format(note ? message : MISSING_NOTE, keyword: first_word)
93
- ) do |corrector|
94
- next if note.nil?
95
+ add_offense(range, message: format(message, keyword: annotation.keyword)) do |corrector|
96
+ next if annotation.note.nil?
95
97
 
96
- correct_offense(corrector, range, first_word)
98
+ correct_offense(corrector, range, annotation.keyword)
97
99
  end
98
100
  end
99
101
 
@@ -105,39 +107,23 @@ module RuboCop
105
107
  !comment_line?(comment.loc.expression.source_line)
106
108
  end
107
109
 
108
- def annotation_range(comment, margin, first_word, colon, space)
109
- start = comment.loc.expression.begin_pos + margin.length
110
- length = concat_length(first_word, colon, space)
111
- range_between(start, start + length)
112
- end
113
-
114
- def concat_length(*args)
115
- args.reduce(0) { |acc, elem| acc + elem.to_s.length }
110
+ def annotation_range(annotation)
111
+ range_between(*annotation.bounds)
116
112
  end
117
113
 
118
- def correct_annotation?(first_word, colon, space, note)
119
- return correct_colon_annotation?(first_word, colon, space, note) if requires_colon?
114
+ def correct_offense(corrector, range, keyword)
115
+ return corrector.replace(range, "#{keyword.upcase}: ") if requires_colon?
120
116
 
121
- correct_space_annotation?(first_word, colon, space, note)
122
- end
123
-
124
- def correct_colon_annotation?(first_word, colon, space, note)
125
- keyword?(first_word) && (colon && space && note || !colon && !note)
126
- end
127
-
128
- def correct_space_annotation?(first_word, colon, space, note)
129
- keyword?(first_word) && (!colon && space && note || !colon && !note)
130
- end
131
-
132
- def correct_offense(corrector, range, first_word)
133
- return corrector.replace(range, "#{first_word.upcase}: ") if requires_colon?
134
-
135
- corrector.replace(range, "#{first_word.upcase} ")
117
+ corrector.replace(range, "#{keyword.upcase} ")
136
118
  end
137
119
 
138
120
  def requires_colon?
139
121
  cop_config['RequireColon']
140
122
  end
123
+
124
+ def keywords
125
+ cop_config['Keywords']
126
+ end
141
127
  end
142
128
  end
143
129
  end
@@ -141,7 +141,7 @@ module RuboCop
141
141
 
142
142
  def frozen_string_literal_comment(processed_source)
143
143
  processed_source.find_token do |token|
144
- token.text.start_with?(FrozenStringLiteral::FROZEN_STRING_LITERAL)
144
+ token.text.start_with?(FROZEN_STRING_LITERAL)
145
145
  end
146
146
  end
147
147
 
@@ -49,7 +49,7 @@ module RuboCop
49
49
  return unless bad_method?(block) && semantically_except_method?(node, block)
50
50
 
51
51
  except_key = except_key(block)
52
- return unless safe_to_register_offense?(block, except_key)
52
+ return if except_key.nil? || !safe_to_register_offense?(block, except_key)
53
53
 
54
54
  range = offense_range(node)
55
55
  preferred_method = "except(#{except_key.source})"
@@ -81,10 +81,11 @@ module RuboCop
81
81
  end
82
82
 
83
83
  def except_key(node)
84
- key_argument = node.argument_list.first
84
+ key_argument = node.argument_list.first.source
85
85
  lhs, _method_name, rhs = *node.body
86
+ return if [lhs, rhs].map(&:source).none?(key_argument)
86
87
 
87
- [lhs, rhs].find { |operand| operand.source != key_argument.source }
88
+ [lhs, rhs].find { |operand| operand.source != key_argument }
88
89
  end
89
90
 
90
91
  def offense_range(node)
@@ -14,8 +14,16 @@ module RuboCop
14
14
  # positives. Luckily, there is no harm in freezing an already
15
15
  # frozen object.
16
16
  #
17
+ # From Ruby 3.0, this cop honours the magic comment
18
+ # 'shareable_constant_value'. When this magic comment is set to any
19
+ # acceptable value other than none, it will suppress the offenses
20
+ # raised by this cop. It enforces frozen state.
21
+ #
17
22
  # NOTE: Regexp and Range literals are frozen objects since Ruby 3.0.
18
23
  #
24
+ # NOTE: From Ruby 3.0, this cop allows explicit freezing of interpolated
25
+ # string literals when `# frozen-string-literal: true` is used.
26
+ #
19
27
  # @example EnforcedStyle: literals (default)
20
28
  # # bad
21
29
  # CONST = [1, 2, 3]
@@ -52,7 +60,59 @@ module RuboCop
52
60
  # puts 1
53
61
  # end
54
62
  # end.freeze
63
+ #
64
+ # @example
65
+ # # Magic comment - shareable_constant_value: literal
66
+ #
67
+ # # bad
68
+ # CONST = [1, 2, 3]
69
+ #
70
+ # # good
71
+ # # shareable_constant_value: literal
72
+ # CONST = [1, 2, 3]
73
+ #
74
+ # NOTE: This special directive helps to create constants
75
+ # that hold only immutable objects, or Ractor-shareable
76
+ # constants. - ruby docs
77
+ #
55
78
  class MutableConstant < Base
79
+ # Handles magic comment shareable_constant_value with O(n ^ 2) complexity
80
+ # n - number of lines in the source
81
+ # Iterates over all lines before a CONSTANT
82
+ # until it reaches shareable_constant_value
83
+ module ShareableConstantValue
84
+ module_function
85
+
86
+ def recent_shareable_value?(node)
87
+ shareable_constant_comment = magic_comment_in_scope node
88
+ return false if shareable_constant_comment.nil?
89
+
90
+ shareable_constant_value = MagicComment.parse(shareable_constant_comment)
91
+ .shareable_constant_value
92
+ shareable_constant_value_enabled? shareable_constant_value
93
+ end
94
+
95
+ # Identifies the most recent magic comment with valid shareable constant values
96
+ # thats in scope for this node
97
+ def magic_comment_in_scope(node)
98
+ processed_source_till_node(node).reverse_each.find do |line|
99
+ MagicComment.parse(line).valid_shareable_constant_value?
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def processed_source_till_node(node)
106
+ processed_source.lines[0..(node.last_line - 1)]
107
+ end
108
+
109
+ def shareable_constant_value_enabled?(value)
110
+ %w[literal experimental_everything experimental_copy].include? value
111
+ end
112
+ end
113
+ private_constant :ShareableConstantValue
114
+
115
+ include ShareableConstantValue
56
116
  include FrozenStringLiteral
57
117
  include ConfigurableEnforcedStyle
58
118
  extend AutoCorrector
@@ -85,18 +145,18 @@ module RuboCop
85
145
  return if immutable_literal?(value)
86
146
  return if operation_produces_immutable_object?(value)
87
147
  return if frozen_string_literal?(value)
148
+ return if shareable_constant_value?(value)
88
149
 
89
150
  add_offense(value) { |corrector| autocorrect(corrector, value) }
90
151
  end
91
152
 
92
153
  def check(value)
93
154
  range_enclosed_in_parentheses = range_enclosed_in_parentheses?(value)
94
-
95
155
  return unless mutable_literal?(value) ||
96
156
  target_ruby_version <= 2.7 && range_enclosed_in_parentheses
97
157
 
98
- return if FROZEN_STRING_LITERAL_TYPES.include?(value.type) &&
99
- frozen_string_literals_enabled?
158
+ return if frozen_string_literal?(value)
159
+ return if shareable_constant_value?(value)
100
160
 
101
161
  add_offense(value) { |corrector| autocorrect(corrector, value) }
102
162
  end
@@ -126,8 +186,10 @@ module RuboCop
126
186
  frozen_regexp_or_range_literals?(node) || node.immutable_literal?
127
187
  end
128
188
 
129
- def frozen_string_literal?(node)
130
- FROZEN_STRING_LITERAL_TYPES.include?(node.type) && frozen_string_literals_enabled?
189
+ def shareable_constant_value?(node)
190
+ return false if target_ruby_version < 3.0
191
+
192
+ recent_shareable_value? node
131
193
  end
132
194
 
133
195
  def frozen_regexp_or_range_literals?(node)
@@ -7,6 +7,9 @@ module RuboCop
7
7
  #
8
8
  # NOTE: Regexp and Range literals are frozen objects since Ruby 3.0.
9
9
  #
10
+ # NOTE: From Ruby 3.0, this cop allows explicit freezing of interpolated
11
+ # string literals when `# frozen-string-literal: true` is used.
12
+ #
10
13
  # @example
11
14
  # # bad
12
15
  # CONST = 1.freeze
@@ -37,9 +40,7 @@ module RuboCop
37
40
  node = strip_parenthesis(node)
38
41
 
39
42
  return true if node.immutable_literal?
40
-
41
- return true if FROZEN_STRING_LITERAL_TYPES.include?(node.type) &&
42
- frozen_string_literals_enabled?
43
+ return true if frozen_string_literal?(node)
43
44
 
44
45
  target_ruby_version >= 3.0 && (node.regexp_type? || node.range_type?)
45
46
  end
@@ -5,6 +5,9 @@ module RuboCop
5
5
  module Style
6
6
  # This cop checks for inheritance from Struct.new.
7
7
  #
8
+ # It is marked as unsafe auto-correction because it will change the
9
+ # inheritance tree (e.g. return value of `Module#ancestors`).
10
+ #
8
11
  # @example
9
12
  # # bad
10
13
  # class Person < Struct.new(:first_name, :last_name)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.19.1'
6
+ STRING = '1.20.0'
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, '\
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
data/lib/rubocop.rb CHANGED
@@ -57,7 +57,6 @@ require_relative 'rubocop/cop/variable_force/reference'
57
57
  require_relative 'rubocop/cop/variable_force/scope'
58
58
  require_relative 'rubocop/cop/variable_force/variable_table'
59
59
 
60
- require_relative 'rubocop/cop/mixin/annotation_comment'
61
60
  require_relative 'rubocop/cop/mixin/array_min_size'
62
61
  require_relative 'rubocop/cop/mixin/array_syntax'
63
62
  require_relative 'rubocop/cop/mixin/alignment'
@@ -76,6 +75,7 @@ require_relative 'rubocop/cop/mixin/def_node'
76
75
  require_relative 'rubocop/cop/mixin/documentation_comment'
77
76
  require_relative 'rubocop/cop/mixin/duplication'
78
77
  require_relative 'rubocop/cop/mixin/range_help'
78
+ require_relative 'rubocop/cop/mixin/annotation_comment' # relies on range
79
79
  require_relative 'rubocop/cop/mixin/empty_lines_around_body' # relies on range
80
80
  require_relative 'rubocop/cop/mixin/empty_parameter'
81
81
  require_relative 'rubocop/cop/mixin/end_keyword_alignment'
@@ -152,6 +152,7 @@ require_relative 'rubocop/cop/correctors/unused_arg_corrector'
152
152
 
153
153
  require_relative 'rubocop/cop/bundler/duplicated_gem'
154
154
  require_relative 'rubocop/cop/bundler/gem_comment'
155
+ require_relative 'rubocop/cop/bundler/gem_filename'
155
156
  require_relative 'rubocop/cop/bundler/gem_version'
156
157
  require_relative 'rubocop/cop/bundler/insecure_protocol_source'
157
158
  require_relative 'rubocop/cop/bundler/ordered_gems'
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.19.1
4
+ version: 1.20.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-08-19 00:00:00.000000000 Z
13
+ date: 2021-08-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: parallel
@@ -221,6 +221,7 @@ files:
221
221
  - lib/rubocop/cop/base.rb
222
222
  - lib/rubocop/cop/bundler/duplicated_gem.rb
223
223
  - lib/rubocop/cop/bundler/gem_comment.rb
224
+ - lib/rubocop/cop/bundler/gem_filename.rb
224
225
  - lib/rubocop/cop/bundler/gem_version.rb
225
226
  - lib/rubocop/cop/bundler/insecure_protocol_source.rb
226
227
  - lib/rubocop/cop/bundler/ordered_gems.rb
@@ -889,7 +890,7 @@ metadata:
889
890
  homepage_uri: https://rubocop.org/
890
891
  changelog_uri: https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md
891
892
  source_code_uri: https://github.com/rubocop/rubocop/
892
- documentation_uri: https://docs.rubocop.org/rubocop/1.19/
893
+ documentation_uri: https://docs.rubocop.org/rubocop/1.20/
893
894
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
894
895
  post_install_message:
895
896
  rdoc_options: []