rubocop 1.60.2 → 1.62.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/assets/output.css.erb +159 -0
  4. data/assets/output.html.erb +1 -160
  5. data/config/default.yml +41 -12
  6. data/lib/rubocop/cli/command/auto_generate_config.rb +12 -3
  7. data/lib/rubocop/cli/command/lsp.rb +2 -2
  8. data/lib/rubocop/cli.rb +6 -1
  9. data/lib/rubocop/config.rb +4 -0
  10. data/lib/rubocop/config_finder.rb +12 -2
  11. data/lib/rubocop/config_validator.rb +14 -5
  12. data/lib/rubocop/cop/autocorrect_logic.rb +6 -1
  13. data/lib/rubocop/cop/base.rb +17 -8
  14. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +4 -8
  15. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +5 -13
  16. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +5 -1
  17. data/lib/rubocop/cop/internal_affairs/method_name_end_with.rb +8 -6
  18. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +122 -28
  19. data/lib/rubocop/cop/internal_affairs/redundant_expect_offense_arguments.rb +34 -0
  20. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  21. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +14 -7
  22. data/lib/rubocop/cop/layout/end_alignment.rb +3 -1
  23. data/lib/rubocop/cop/layout/redundant_line_break.rb +11 -3
  24. data/lib/rubocop/cop/layout/space_before_block_braces.rb +19 -10
  25. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +1 -1
  26. data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
  27. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +14 -9
  28. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -0
  29. data/lib/rubocop/cop/lint/rescue_type.rb +1 -3
  30. data/lib/rubocop/cop/lint/script_permission.rb +3 -3
  31. data/lib/rubocop/cop/lint/syntax.rb +1 -1
  32. data/lib/rubocop/cop/lint/to_enum_arguments.rb +7 -2
  33. data/lib/rubocop/cop/lint/useless_times.rb +1 -1
  34. data/lib/rubocop/cop/lint/void.rb +6 -1
  35. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  36. data/lib/rubocop/cop/naming/predicate_name.rb +2 -2
  37. data/lib/rubocop/cop/registry.rb +1 -1
  38. data/lib/rubocop/cop/style/arguments_forwarding.rb +29 -8
  39. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  40. data/lib/rubocop/cop/style/class_vars.rb +3 -3
  41. data/lib/rubocop/cop/style/commented_keyword.rb +5 -2
  42. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -5
  43. data/lib/rubocop/cop/style/for.rb +2 -0
  44. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -1
  45. data/lib/rubocop/cop/style/hash_syntax.rb +6 -2
  46. data/lib/rubocop/cop/style/inverse_methods.rb +8 -8
  47. data/lib/rubocop/cop/style/invertible_unless_condition.rb +10 -5
  48. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +5 -8
  49. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
  50. data/lib/rubocop/cop/style/multiline_method_signature.rb +10 -1
  51. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +4 -0
  52. data/lib/rubocop/cop/style/nil_comparison.rb +2 -0
  53. data/lib/rubocop/cop/style/object_then.rb +5 -3
  54. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -3
  55. data/lib/rubocop/cop/style/raise_args.rb +4 -1
  56. data/lib/rubocop/cop/style/redundant_argument.rb +2 -2
  57. data/lib/rubocop/cop/style/redundant_assignment.rb +10 -2
  58. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +4 -3
  59. data/lib/rubocop/cop/style/redundant_line_continuation.rb +17 -6
  60. data/lib/rubocop/cop/style/redundant_return.rb +6 -0
  61. data/lib/rubocop/cop/style/sample.rb +1 -3
  62. data/lib/rubocop/cop/utils/regexp_ranges.rb +1 -1
  63. data/lib/rubocop/cops_documentation_generator.rb +4 -2
  64. data/lib/rubocop/directive_comment.rb +10 -8
  65. data/lib/rubocop/formatter/html_formatter.rb +30 -10
  66. data/lib/rubocop/formatter/offense_count_formatter.rb +12 -2
  67. data/lib/rubocop/lsp/logger.rb +1 -1
  68. data/lib/rubocop/lsp/routes.rb +1 -1
  69. data/lib/rubocop/lsp/runtime.rb +1 -1
  70. data/lib/rubocop/lsp/server.rb +5 -2
  71. data/lib/rubocop/lsp/severity.rb +1 -1
  72. data/lib/rubocop/lsp.rb +29 -0
  73. data/lib/rubocop/magic_comment.rb +1 -1
  74. data/lib/rubocop/options.rb +11 -0
  75. data/lib/rubocop/path_util.rb +6 -2
  76. data/lib/rubocop/rspec/cop_helper.rb +8 -2
  77. data/lib/rubocop/rspec/expect_offense.rb +8 -8
  78. data/lib/rubocop/rspec/shared_contexts.rb +36 -17
  79. data/lib/rubocop/rspec/support.rb +2 -1
  80. data/lib/rubocop/runner.rb +9 -2
  81. data/lib/rubocop/target_finder.rb +84 -78
  82. data/lib/rubocop/target_ruby.rb +82 -80
  83. data/lib/rubocop/version.rb +18 -3
  84. metadata +9 -6
@@ -54,12 +54,14 @@ module RuboCop
54
54
 
55
55
  private
56
56
 
57
+ # rubocop:disable Metrics/CyclomaticComplexity
57
58
  def check_branch(node)
58
59
  return unless node
59
60
 
60
61
  case node.type
61
- when :case then check_case_node(node)
62
- when :if then check_if_node(node)
62
+ when :case then check_case_node(node)
63
+ when :case_match then check_case_match_node(node)
64
+ when :if then check_if_node(node)
63
65
  when :rescue, :resbody
64
66
  check_rescue_node(node)
65
67
  when :ensure then check_ensure_node(node)
@@ -67,12 +69,18 @@ module RuboCop
67
69
  check_begin_node(node)
68
70
  end
69
71
  end
72
+ # rubocop:enable Metrics/CyclomaticComplexity
70
73
 
71
74
  def check_case_node(node)
72
75
  node.when_branches.each { |when_node| check_branch(when_node.body) }
73
76
  check_branch(node.else_branch)
74
77
  end
75
78
 
79
+ def check_case_match_node(node)
80
+ node.in_pattern_branches.each { |in_pattern_node| check_branch(in_pattern_node.body) }
81
+ check_branch(node.else_branch)
82
+ end
83
+
76
84
  def check_if_node(node)
77
85
  return if node.modifier_form? || node.ternary?
78
86
 
@@ -22,10 +22,11 @@ module RuboCop
22
22
 
23
23
  def on_send(node)
24
24
  return unless node.method?(:require_relative)
25
- return unless node.first_argument.str_content&.start_with?(CURRENT_DIRECTORY_PATH)
26
- return unless (index = node.first_argument.source.index(CURRENT_DIRECTORY_PATH))
25
+ return unless (first_argument = node.first_argument)
26
+ return unless first_argument.str_content&.start_with?(CURRENT_DIRECTORY_PATH)
27
+ return unless (index = first_argument.source.index(CURRENT_DIRECTORY_PATH))
27
28
 
28
- begin_pos = node.first_argument.source_range.begin.begin_pos + index
29
+ begin_pos = first_argument.source_range.begin.begin_pos + index
29
30
  range = range_between(begin_pos, begin_pos + 2)
30
31
 
31
32
  add_offense(range) do |corrector|
@@ -72,7 +72,7 @@ module RuboCop
72
72
  ALLOWED_STRING_TOKENS = %i[tSTRING tSTRING_CONTENT].freeze
73
73
  ARGUMENT_TYPES = %i[
74
74
  kFALSE kNIL kSELF kTRUE tCONSTANT tCVAR tFLOAT tGVAR tIDENTIFIER tINTEGER tIVAR
75
- tLBRACK tLCURLY tLPAREN_ARG tSTRING tSTRING_BEG tSYMBOL tXSTRING_BEG
75
+ tLABEL tLBRACK tLCURLY tLPAREN_ARG tSTRING tSTRING_BEG tSYMBOL tXSTRING_BEG
76
76
  ].freeze
77
77
 
78
78
  def on_new_investigation
@@ -124,8 +124,10 @@ module RuboCop
124
124
  return true unless (node = find_node_for_line(range.line))
125
125
  return false if argument_newline?(node)
126
126
 
127
- source = node.parent ? node.parent.source : node.source
128
- parse(source.gsub("\\\n", "\n")).valid_syntax?
127
+ continuation_node = node.parent || node
128
+ return false if allowed_type?(node) || allowed_type?(continuation_node)
129
+
130
+ continuation_node.source.include?("\n") || continuation_node.source.include?("\\\n")
129
131
  end
130
132
 
131
133
  def inside_string_literal?(range, token)
@@ -140,17 +142,22 @@ module RuboCop
140
142
  current_token.type == :tIDENTIFIER && ARGUMENT_TYPES.include?(next_token.type)
141
143
  end
142
144
 
145
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
143
146
  def argument_newline?(node)
147
+ node = node.to_a.last if node.assignment?
148
+ return false if node.parenthesized_call?
149
+
144
150
  node = node.children.first if node.root? && node.begin_type?
145
151
 
146
- if argument_is_method?(node)
147
- argument_newline?(node.first_argument)
152
+ if argument_is_method?(node) || node.begin_type?
153
+ argument_newline?(node.children.first)
148
154
  else
149
155
  return false unless method_call_with_arguments?(node)
150
156
 
151
- node.loc.selector.line != node.first_argument.loc.line
157
+ !same_line?(node, node.first_argument)
152
158
  end
153
159
  end
160
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
154
161
 
155
162
  def find_node_for_line(line)
156
163
  processed_source.ast.each_node do |node|
@@ -158,6 +165,10 @@ module RuboCop
158
165
  end
159
166
  end
160
167
 
168
+ def allowed_type?(node)
169
+ node.and_type? || node.or_type? || (node.if_type? && node.ternary?)
170
+ end
171
+
161
172
  def same_line?(node, line)
162
173
  return false unless (source_range = node.source_range)
163
174
 
@@ -113,6 +113,7 @@ module RuboCop
113
113
  case node.type
114
114
  when :return then check_return_node(node)
115
115
  when :case then check_case_node(node)
116
+ when :case_match then check_case_match_node(node)
116
117
  when :if then check_if_node(node)
117
118
  when :rescue then check_rescue_node(node)
118
119
  when :resbody then check_resbody_node(node)
@@ -140,6 +141,11 @@ module RuboCop
140
141
  check_branch(node.else_branch)
141
142
  end
142
143
 
144
+ def check_case_match_node(node)
145
+ node.in_pattern_branches.each { |in_pattern_node| check_branch(in_pattern_node.body) }
146
+ check_branch(node.else_branch)
147
+ end
148
+
143
149
  def check_if_node(node)
144
150
  return if node.ternary?
145
151
 
@@ -110,9 +110,7 @@ module RuboCop
110
110
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
111
111
 
112
112
  def source_range(shuffle_node, node)
113
- Parser::Source::Range.new(shuffle_node.source_range.source_buffer,
114
- shuffle_node.loc.selector.begin_pos,
115
- node.source_range.end_pos)
113
+ shuffle_node.loc.selector.join(node.source_range.end)
116
114
  end
117
115
 
118
116
  def message(shuffle_arg, method, method_args, range)
@@ -88,7 +88,7 @@ module RuboCop
88
88
  end
89
89
 
90
90
  def escaped_octal?(expr)
91
- expr.text =~ /^\\[0-7]$/
91
+ expr.text.valid_encoding? && expr.text =~ /^\\[0-7]$/
92
92
  end
93
93
 
94
94
  def octal_digit?(char)
@@ -97,7 +97,9 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
97
97
  'Version Changed'
98
98
  ]
99
99
  autocorrect = if cop.support_autocorrect?
100
- "Yes#{' (Unsafe)' unless cop.new(config).safe_autocorrect?}"
100
+ context = cop.new.always_autocorrect? ? 'Always' : 'Command-line only'
101
+
102
+ "#{context}#{' (Unsafe)' unless cop.new(config).safe_autocorrect?}"
101
103
  else
102
104
  'No'
103
105
  end
@@ -276,7 +278,7 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
276
278
  def print_cop_with_doc(cop) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
277
279
  cop_config = config.for_cop(cop)
278
280
  non_display_keys = %w[
279
- Description Enabled StyleGuide Reference Safe SafeAutoCorrect VersionAdded
281
+ AutoCorrect Description Enabled StyleGuide Reference Safe SafeAutoCorrect VersionAdded
280
282
  VersionChanged
281
283
  ]
282
284
  pars = cop_config.reject { |k| non_display_keys.include? k }
@@ -6,9 +6,11 @@ module RuboCop
6
6
  # cops it contains.
7
7
  class DirectiveComment
8
8
  # @api private
9
- REDUNDANT_DIRECTIVE_COP_DEPARTMENT = 'Lint'
9
+ LINT_DEPARTMENT = 'Lint'
10
10
  # @api private
11
- REDUNDANT_DIRECTIVE_COP = "#{REDUNDANT_DIRECTIVE_COP_DEPARTMENT}/RedundantCopDisableDirective"
11
+ LINT_REDUNDANT_DIRECTIVE_COP = "#{LINT_DEPARTMENT}/RedundantCopDisableDirective"
12
+ # @api private
13
+ LINT_SYNTAX_COP = "#{LINT_DEPARTMENT}/Syntax"
12
14
  # @api private
13
15
  COP_NAME_PATTERN = '([A-Z]\w+/)*(?:[A-Z]\w+)'
14
16
  # @api private
@@ -118,9 +120,10 @@ module RuboCop
118
120
  end
119
121
 
120
122
  def parsed_cop_names
121
- splitted_cops_string.map do |name|
123
+ cops = splitted_cops_string.map do |name|
122
124
  department?(name) ? cop_names_for_department(name) : name
123
125
  end.flatten
126
+ cops - [LINT_SYNTAX_COP]
124
127
  end
125
128
 
126
129
  def department?(name)
@@ -128,17 +131,16 @@ module RuboCop
128
131
  end
129
132
 
130
133
  def all_cop_names
131
- exclude_redundant_directive_cop(cop_registry.names)
134
+ exclude_lint_department_cops(cop_registry.names)
132
135
  end
133
136
 
134
137
  def cop_names_for_department(department)
135
138
  names = cop_registry.names_for_department(department)
136
- has_redundant_directive_cop = department == REDUNDANT_DIRECTIVE_COP_DEPARTMENT
137
- has_redundant_directive_cop ? exclude_redundant_directive_cop(names) : names
139
+ department == LINT_DEPARTMENT ? exclude_lint_department_cops(names) : names
138
140
  end
139
141
 
140
- def exclude_redundant_directive_cop(cops)
141
- cops - [REDUNDANT_DIRECTIVE_COP]
142
+ def exclude_lint_department_cops(cops)
143
+ cops - [LINT_REDUNDANT_DIRECTIVE_COP, LINT_SYNTAX_COP]
142
144
  end
143
145
  end
144
146
  end
@@ -9,6 +9,7 @@ module RuboCop
9
9
  class HTMLFormatter < BaseFormatter
10
10
  ELLIPSES = '<span class="extra-code">...</span>'
11
11
  TEMPLATE_PATH = File.expand_path('../../../assets/output.html.erb', __dir__)
12
+ CSS_PATH = File.expand_path('../../../assets/output.css.erb', __dir__)
12
13
 
13
14
  Color = Struct.new(:red, :green, :blue, :alpha) do
14
15
  def to_s
@@ -50,8 +51,8 @@ module RuboCop
50
51
  context = ERBContext.new(files, summary)
51
52
 
52
53
  template = File.read(TEMPLATE_PATH, encoding: Encoding::UTF_8)
53
- erb = ERB.new(template, trim_mode: '-')
54
- html = erb.result(context.binding)
54
+ erb = ERB.new(template)
55
+ html = erb.result(context.binding).lines.map { (_1 =~ /^\s*$/).nil? ? _1 : "\n" }.join
55
56
 
56
57
  output.write html
57
58
  end
@@ -61,14 +62,6 @@ module RuboCop
61
62
  include PathUtil
62
63
  include TextUtil
63
64
 
64
- SEVERITY_COLORS = {
65
- refactor: Color.new(0xED, 0x9C, 0x28, 1.0),
66
- convention: Color.new(0xED, 0x9C, 0x28, 1.0),
67
- warning: Color.new(0x96, 0x28, 0xEF, 1.0),
68
- error: Color.new(0xD2, 0x32, 0x2D, 1.0),
69
- fatal: Color.new(0xD2, 0x32, 0x2D, 1.0)
70
- }.freeze
71
-
72
65
  LOGO_IMAGE_PATH = File.expand_path('../../../assets/logo.png', __dir__)
73
66
 
74
67
  attr_reader :files, :summary
@@ -127,6 +120,33 @@ module RuboCop
127
120
  # https://github.com/ruby/base64/blob/v0.1.1/lib/base64.rb#L27-L40
128
121
  [image].pack('m')
129
122
  end
123
+
124
+ def render_css
125
+ context = CSSContext.new
126
+ template = File.read(CSS_PATH, encoding: Encoding::UTF_8)
127
+ erb = ERB.new(template, trim_mode: '-')
128
+ erb.result(context.binding).lines.map do |line|
129
+ line == "\n" ? line : " #{line}"
130
+ end.join
131
+ end
132
+ end
133
+
134
+ # This class provides helper methods used in the ERB CSS template.
135
+ class CSSContext
136
+ SEVERITY_COLORS = {
137
+ refactor: Color.new(0xED, 0x9C, 0x28, 1.0),
138
+ convention: Color.new(0xED, 0x9C, 0x28, 1.0),
139
+ warning: Color.new(0x96, 0x28, 0xEF, 1.0),
140
+ error: Color.new(0xD2, 0x32, 0x2D, 1.0),
141
+ fatal: Color.new(0xD2, 0x32, 0x2D, 1.0)
142
+ }.freeze
143
+
144
+ # Make Kernel#binding public.
145
+ # rubocop:disable Lint/UselessMethodDefinition
146
+ def binding
147
+ super
148
+ end
149
+ # rubocop:enable Lint/UselessMethodDefinition
130
150
  end
131
151
  end
132
152
  end
@@ -61,8 +61,7 @@ module RuboCop
61
61
 
62
62
  column_width = total_count.to_s.length + 2
63
63
  per_cop_counts.each do |cop_name, count|
64
- output.puts "#{count.to_s.ljust(column_width)}#{cop_name}" \
65
- "#{@style_guide_links[cop_name]}\n"
64
+ output.puts "#{count.to_s.ljust(column_width)}#{cop_information(cop_name)}"
66
65
  end
67
66
  output.puts '--'
68
67
  output.puts "#{total_count} Total in #{offending_files_count} files"
@@ -78,6 +77,17 @@ module RuboCop
78
77
  def total_offense_count(offense_counts)
79
78
  offense_counts.values.sum
80
79
  end
80
+
81
+ def cop_information(cop_name)
82
+ cop = RuboCop::Cop::Registry.global.find_by_cop_name(cop_name).new
83
+
84
+ if cop.correctable?
85
+ safety = cop.safe_autocorrect? ? 'Safe' : 'Unsafe'
86
+ correctable = Rainbow(" [#{safety} Correctable]").yellow
87
+ end
88
+
89
+ "#{cop_name}#{correctable}#{@style_guide_links[cop_name]}"
90
+ end
81
91
  end
82
92
  end
83
93
  end
@@ -10,7 +10,7 @@
10
10
  # https://github.com/standardrb/standard/blob/main/LICENSE.txt
11
11
  #
12
12
  module RuboCop
13
- module Lsp
13
+ module LSP
14
14
  # Log for Language Server Protocol of RuboCop.
15
15
  # @api private
16
16
  class Logger
@@ -12,7 +12,7 @@ require_relative 'severity'
12
12
  # https://github.com/standardrb/standard/blob/main/LICENSE.txt
13
13
  #
14
14
  module RuboCop
15
- module Lsp
15
+ module LSP
16
16
  # Routes for Language Server Protocol of RuboCop.
17
17
  # @api private
18
18
  class Routes
@@ -10,7 +10,7 @@
10
10
  # https://github.com/standardrb/standard/blob/main/LICENSE.txt
11
11
  #
12
12
  module RuboCop
13
- module Lsp
13
+ module LSP
14
14
  # Runtime for Language Server Protocol of RuboCop.
15
15
  # @api private
16
16
  class Runtime
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'language_server-protocol'
4
+ require_relative '../lsp'
4
5
  require_relative 'logger'
5
6
  require_relative 'routes'
6
7
  require_relative 'runtime'
@@ -15,14 +16,16 @@ require_relative 'runtime'
15
16
  # https://github.com/standardrb/standard/blob/main/LICENSE.txt
16
17
  #
17
18
  module RuboCop
18
- module Lsp
19
+ module LSP
19
20
  # Language Server Protocol of RuboCop.
20
21
  # @api private
21
22
  class Server
22
23
  def initialize(config_store)
24
+ RuboCop::LSP.enable
25
+
23
26
  @reader = LanguageServer::Protocol::Transport::Io::Reader.new($stdin)
24
27
  @writer = LanguageServer::Protocol::Transport::Io::Writer.new($stdout)
25
- @runtime = RuboCop::Lsp::Runtime.new(config_store)
28
+ @runtime = RuboCop::LSP::Runtime.new(config_store)
26
29
  @routes = Routes.new(self)
27
30
  end
28
31
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Lsp
4
+ module LSP
5
5
  # Severity for Language Server Protocol of RuboCop.
6
6
  # @api private
7
7
  class Severity
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ # The RuboCop's built-in LSP module.
5
+ module LSP
6
+ module_function
7
+
8
+ # Returns true when LSP is enabled, false when disabled.
9
+ #
10
+ # @return [Boolean]
11
+ def enabled?
12
+ @enabled ||= false
13
+ end
14
+
15
+ # Enable LSP.
16
+ #
17
+ # @return [void]
18
+ def enable
19
+ @enabled = true
20
+ end
21
+
22
+ # Disable LSP.
23
+ #
24
+ # @return [void]
25
+ def disable
26
+ @enabled = false
27
+ end
28
+ end
29
+ end
@@ -268,7 +268,7 @@ module RuboCop
268
268
 
269
269
  # Rewrite the comment without a given token type
270
270
  def without(type)
271
- if @comment.match?(/\A#\s*#{self.class::KEYWORDS[type.to_sym]}/)
271
+ if @comment.match?(/\A#\s*#{self.class::KEYWORDS[type.to_sym]}/io)
272
272
  ''
273
273
  else
274
274
  @comment
@@ -95,6 +95,7 @@ module RuboCop
95
95
  option(opts, '--ignore-unrecognized-cops')
96
96
  option(opts, '--force-default-config')
97
97
  option(opts, '-s', '--stdin FILE')
98
+ option(opts, '--editor-mode')
98
99
  option(opts, '-P', '--[no-]parallel')
99
100
  option(opts, '--raise-cop-error')
100
101
  add_severity_option(opts)
@@ -369,6 +370,7 @@ module RuboCop
369
370
  validate_display_only_failed
370
371
  validate_display_only_failed_and_display_only_correctable
371
372
  validate_display_only_correctable_and_autocorrect
373
+ validate_lsp_and_editor_mode
372
374
  disable_parallel_when_invalid_option_combo
373
375
 
374
376
  return if incompatible_options.size <= 1
@@ -416,6 +418,13 @@ module RuboCop
416
418
  format('--display-only-failed cannot be used together with other display options.')
417
419
  end
418
420
 
421
+ def validate_lsp_and_editor_mode
422
+ return if !@options.key?(:lsp) || !@options.key?(:editor_mode)
423
+
424
+ raise OptionArgumentError,
425
+ format('Do not specify `--editor-mode` as it is redundant in `--lsp`.')
426
+ end
427
+
419
428
  def validate_autocorrect
420
429
  if @options.key?(:safe_autocorrect) && @options.key?(:autocorrect_all)
421
430
  message = Rainbow(<<~MESSAGE).red
@@ -609,6 +618,8 @@ module RuboCop
609
618
  'parallel. Default is true.'],
610
619
  stdin: ['Pipe source from STDIN, using FILE in offense',
611
620
  'reports. This is useful for editor integration.'],
621
+ editor_mode: ['Optimize real-time feedback in editors,',
622
+ 'adjusting behaviors for editing experience.'],
612
623
  init: 'Generate a .rubocop.yml file in the current directory.',
613
624
  server: ['If a server process has not been started yet, start',
614
625
  'the server process and execute inspection with server.',
@@ -44,7 +44,7 @@ module RuboCop
44
44
  end
45
45
  end
46
46
 
47
- # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
47
+ # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
48
48
  def match_path?(pattern, path)
49
49
  case pattern
50
50
  when String
@@ -52,6 +52,10 @@ module RuboCop
52
52
  if pattern == path
53
53
  true
54
54
  elsif glob?(pattern)
55
+ # File name matching doesn't really work with relative patterns that start with "..". We
56
+ # get around that problem by converting the pattern to an absolute path.
57
+ pattern = File.expand_path(pattern) if pattern.start_with?('..')
58
+
55
59
  File.fnmatch?(pattern, path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
56
60
  end
57
61
 
@@ -66,7 +70,7 @@ module RuboCop
66
70
  end
67
71
  end
68
72
  end
69
- # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
73
+ # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
70
74
 
71
75
  # Returns true for an absolute Unix or Windows path.
72
76
  def absolute?(path)
@@ -6,7 +6,11 @@ require 'tempfile'
6
6
  module CopHelper
7
7
  extend RSpec::SharedContext
8
8
 
9
- let(:ruby_version) { RuboCop::TargetRuby::DEFAULT_VERSION }
9
+ let(:ruby_version) do
10
+ # The minimum version Prism can parse is 3.3.
11
+ ENV['PARSER_ENGINE'] == 'parser_prism' ? 3.3 : RuboCop::TargetRuby::DEFAULT_VERSION
12
+ end
13
+ let(:parser_engine) { ENV.fetch('PARSER_ENGINE', :parser_whitequark).to_sym }
10
14
  let(:rails_version) { false }
11
15
 
12
16
  def inspect_source(source, file = nil)
@@ -28,7 +32,9 @@ module CopHelper
28
32
  file = file.path
29
33
  end
30
34
 
31
- processed_source = RuboCop::ProcessedSource.new(source, ruby_version, file)
35
+ processed_source = RuboCop::ProcessedSource.new(
36
+ source, ruby_version, file, parser_engine: parser_engine
37
+ )
32
38
  processed_source.config = configuration
33
39
  processed_source.registry = registry
34
40
  processed_source
@@ -126,7 +126,7 @@ module RuboCop
126
126
  @offenses
127
127
  end
128
128
 
129
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
129
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
130
130
  def expect_correction(correction, loop: true, source: nil)
131
131
  if source
132
132
  expected_annotations = parse_annotations(source, raise_error: false)
@@ -148,7 +148,6 @@ module RuboCop
148
148
 
149
149
  break corrected_source unless loop
150
150
  break corrected_source if @last_corrector.empty?
151
- break corrected_source if corrected_source == @processed_source.buffer.source
152
151
 
153
152
  if iteration > RuboCop::Runner::MAX_ITERATIONS
154
153
  raise RuboCop::Runner::InfiniteCorrectionLoop.new(@processed_source.path, [@offenses])
@@ -163,19 +162,20 @@ module RuboCop
163
162
 
164
163
  expect(new_source).to eq(correction)
165
164
  end
166
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
165
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
167
166
 
168
167
  def expect_no_corrections
169
168
  raise '`expect_no_corrections` must follow `expect_offense`' unless @processed_source
170
169
 
171
170
  return if @last_corrector.empty?
172
171
 
173
- # In order to print a nice diff, e.g. what source got corrected to,
174
- # we need to run the actual corrections
175
-
172
+ # This is just here for a pretty diff if the source actually got changed
176
173
  new_source = @last_corrector.rewrite
177
-
178
174
  expect(new_source).to eq(@processed_source.buffer.source)
175
+
176
+ # There is an infinite loop if a corrector is present that did not make
177
+ # any changes. It will cause the same offense/correction on the next loop.
178
+ raise RuboCop::Runner::InfiniteCorrectionLoop.new(@processed_source.path, [@offenses])
179
179
  end
180
180
 
181
181
  def expect_no_offenses(source, file = nil)
@@ -212,7 +212,7 @@ module RuboCop
212
212
 
213
213
  # Parsed representation of code annotated with the `^^^ Message` style
214
214
  class AnnotatedSource
215
- ANNOTATION_PATTERN = /\A\s*(\^+|\^{}) /.freeze
215
+ ANNOTATION_PATTERN = /\A\s*(\^+|\^{}) ?/.freeze
216
216
  ABBREV = "[...]\n"
217
217
 
218
218
  # @param annotated_source [String] string passed to the matchers