rubocop 1.60.2 → 1.62.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) 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/lsp.rb +2 -2
  7. data/lib/rubocop/cli.rb +6 -1
  8. data/lib/rubocop/config.rb +4 -0
  9. data/lib/rubocop/config_finder.rb +12 -2
  10. data/lib/rubocop/config_validator.rb +14 -5
  11. data/lib/rubocop/cop/autocorrect_logic.rb +6 -1
  12. data/lib/rubocop/cop/base.rb +17 -8
  13. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +4 -8
  14. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +5 -13
  15. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +2 -0
  16. data/lib/rubocop/cop/internal_affairs/method_name_end_with.rb +8 -6
  17. data/lib/rubocop/cop/internal_affairs/redundant_expect_offense_arguments.rb +34 -0
  18. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  19. data/lib/rubocop/cop/layout/end_alignment.rb +3 -1
  20. data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -1
  21. data/lib/rubocop/cop/layout/space_before_block_braces.rb +19 -10
  22. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +1 -1
  23. data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
  24. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +14 -9
  25. data/lib/rubocop/cop/lint/rescue_type.rb +1 -3
  26. data/lib/rubocop/cop/lint/script_permission.rb +3 -3
  27. data/lib/rubocop/cop/lint/syntax.rb +1 -1
  28. data/lib/rubocop/cop/lint/to_enum_arguments.rb +7 -2
  29. data/lib/rubocop/cop/lint/void.rb +6 -1
  30. data/lib/rubocop/cop/naming/predicate_name.rb +2 -2
  31. data/lib/rubocop/cop/registry.rb +1 -1
  32. data/lib/rubocop/cop/style/arguments_forwarding.rb +29 -8
  33. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  34. data/lib/rubocop/cop/style/commented_keyword.rb +5 -2
  35. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -5
  36. data/lib/rubocop/cop/style/hash_syntax.rb +6 -2
  37. data/lib/rubocop/cop/style/inverse_methods.rb +8 -8
  38. data/lib/rubocop/cop/style/invertible_unless_condition.rb +10 -5
  39. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +5 -8
  40. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
  41. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +4 -0
  42. data/lib/rubocop/cop/style/object_then.rb +5 -3
  43. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -3
  44. data/lib/rubocop/cop/style/raise_args.rb +3 -0
  45. data/lib/rubocop/cop/style/redundant_argument.rb +2 -2
  46. data/lib/rubocop/cop/style/redundant_assignment.rb +10 -2
  47. data/lib/rubocop/cop/style/redundant_line_continuation.rb +17 -6
  48. data/lib/rubocop/cop/style/redundant_return.rb +6 -0
  49. data/lib/rubocop/cop/style/sample.rb +1 -3
  50. data/lib/rubocop/cops_documentation_generator.rb +4 -2
  51. data/lib/rubocop/formatter/html_formatter.rb +30 -10
  52. data/lib/rubocop/formatter/offense_count_formatter.rb +12 -2
  53. data/lib/rubocop/lsp/logger.rb +1 -1
  54. data/lib/rubocop/lsp/routes.rb +1 -1
  55. data/lib/rubocop/lsp/runtime.rb +1 -1
  56. data/lib/rubocop/lsp/server.rb +5 -2
  57. data/lib/rubocop/lsp/severity.rb +1 -1
  58. data/lib/rubocop/lsp.rb +29 -0
  59. data/lib/rubocop/magic_comment.rb +1 -1
  60. data/lib/rubocop/options.rb +11 -0
  61. data/lib/rubocop/path_util.rb +6 -2
  62. data/lib/rubocop/rspec/cop_helper.rb +8 -2
  63. data/lib/rubocop/rspec/expect_offense.rb +1 -1
  64. data/lib/rubocop/rspec/shared_contexts.rb +36 -17
  65. data/lib/rubocop/rspec/support.rb +2 -1
  66. data/lib/rubocop/runner.rb +9 -2
  67. data/lib/rubocop/target_finder.rb +84 -78
  68. data/lib/rubocop/target_ruby.rb +82 -80
  69. data/lib/rubocop/version.rb +18 -3
  70. metadata +9 -6
@@ -76,9 +76,9 @@ module RuboCop
76
76
  PATTERN
77
77
 
78
78
  def on_send(node)
79
- inverse_candidate?(node) do |_method_call, lhs, method, rhs|
79
+ inverse_candidate?(node) do |method_call, lhs, method, rhs|
80
80
  return unless inverse_methods.key?(method)
81
- return if negated?(node)
81
+ return if negated?(node) || relational_comparison_with_safe_navigation?(method_call)
82
82
  return if part_of_ignored_node?(node)
83
83
  return if possible_class_hierarchy_check?(lhs, rhs, method)
84
84
 
@@ -155,16 +155,16 @@ module RuboCop
155
155
  node.parent.respond_to?(:method?) && node.parent.method?(:!)
156
156
  end
157
157
 
158
+ def relational_comparison_with_safe_navigation?(node)
159
+ node.csend_type? && CLASS_COMPARISON_METHODS.include?(node.method_name)
160
+ end
161
+
158
162
  def not_to_receiver(node, method_call)
159
- Parser::Source::Range.new(node.source_range.source_buffer,
160
- node.loc.selector.begin_pos,
161
- method_call.source_range.begin_pos)
163
+ node.loc.selector.begin.join(method_call.source_range.begin)
162
164
  end
163
165
 
164
166
  def end_parentheses(node, method_call)
165
- Parser::Source::Range.new(node.source_range.source_buffer,
166
- method_call.source_range.end_pos,
167
- node.source_range.end_pos)
167
+ method_call.source_range.end.join(node.source_range.end)
168
168
  end
169
169
 
170
170
  # When comparing classes, `!(Integer < Numeric)` is not the same as
@@ -32,12 +32,14 @@ module RuboCop
32
32
  # foo unless x != y
33
33
  # foo unless x >= 10
34
34
  # foo unless x.even?
35
+ # foo unless odd?
35
36
  #
36
37
  # # good
37
38
  # foo if bar
38
39
  # foo if x == y
39
40
  # foo if x < 10
40
41
  # foo if x.odd?
42
+ # foo if even?
41
43
  #
42
44
  # # bad (complex condition)
43
45
  # foo unless x != y || x.even?
@@ -99,12 +101,15 @@ module RuboCop
99
101
  end
100
102
  end
101
103
 
102
- def preferred_send_condition(node)
103
- receiver_source = node.receiver.source
104
+ def preferred_send_condition(node) # rubocop:disable Metrics/CyclomaticComplexity
105
+ receiver_source = node.receiver&.source
104
106
  return receiver_source if node.method?(:!)
105
107
 
108
+ # receiver may be implicit (self)
109
+ dotted_receiver_source = receiver_source ? "#{receiver_source}." : ''
110
+
106
111
  inverse_method_name = inverse_methods[node.method_name]
107
- return "#{receiver_source}.#{inverse_method_name}" unless node.arguments?
112
+ return "#{dotted_receiver_source}#{inverse_method_name}" unless node.arguments?
108
113
 
109
114
  argument_list = node.arguments.map(&:source).join(', ')
110
115
  if node.operator_method?
@@ -112,10 +117,10 @@ module RuboCop
112
117
  end
113
118
 
114
119
  if node.parenthesized?
115
- return "#{receiver_source}.#{inverse_method_name}(#{argument_list})"
120
+ return "#{dotted_receiver_source}#{inverse_method_name}(#{argument_list})"
116
121
  end
117
122
 
118
- "#{receiver_source}.#{inverse_method_name} #{argument_list}"
123
+ "#{dotted_receiver_source}#{inverse_method_name} #{argument_list}"
119
124
  end
120
125
 
121
126
  def preferred_logical_condition(node)
@@ -116,20 +116,17 @@ module RuboCop
116
116
  def truthy_branch_for_guard?(node)
117
117
  if_node = node.left_sibling
118
118
 
119
- if if_node.if? || if_node.ternary?
120
- if_node.else_branch.nil?
121
- elsif if_node.unless?
122
- if_node.if_branch.nil?
119
+ if if_node.if?
120
+ if_node.if_branch.arguments.any?
121
+ else
122
+ if_node.if_branch.arguments.none?
123
123
  end
124
124
  end
125
125
 
126
126
  def range(node)
127
- buffer = node.source_range.source_buffer
128
127
  map_node = node.receiver.send_node
129
- begin_pos = map_node.loc.selector.begin_pos
130
- end_pos = node.source_range.end_pos
131
128
 
132
- Parser::Source::Range.new(buffer, begin_pos, end_pos)
129
+ map_node.loc.selector.join(node.source_range.end)
133
130
  end
134
131
  end
135
132
  end
@@ -132,7 +132,7 @@ module RuboCop
132
132
  call_in_match_pattern?(node) ||
133
133
  hash_literal_in_arguments?(node) ||
134
134
  node.descendants.any? do |n|
135
- n.forwarded_args_type? || n.block_type? ||
135
+ n.forwarded_args_type? || n.block_type? || n.numblock_type? ||
136
136
  ambiguous_literal?(n) || logical_operator?(n)
137
137
  end
138
138
  end
@@ -47,7 +47,11 @@ module RuboCop
47
47
  message = enforce_single_line_ternary_operator?(node) ? MSG_SINGLE_LINE : MSG_IF
48
48
 
49
49
  add_offense(node, message: message) do |corrector|
50
+ next if part_of_ignored_node?(node)
51
+
50
52
  autocorrect(corrector, node)
53
+
54
+ ignore_node(node)
51
55
  end
52
56
  end
53
57
 
@@ -46,15 +46,17 @@ module RuboCop
46
46
  private
47
47
 
48
48
  def check_method_node(node)
49
- return unless preferred_method(node)
49
+ return unless preferred_method?(node)
50
50
 
51
51
  message = message(node)
52
52
  add_offense(node.loc.selector, message: message) do |corrector|
53
- corrector.replace(node.loc.selector, style.to_s)
53
+ prefer = style == :then && node.receiver.nil? ? 'self.then' : style
54
+
55
+ corrector.replace(node.loc.selector, prefer)
54
56
  end
55
57
  end
56
58
 
57
- def preferred_method(node)
59
+ def preferred_method?(node)
58
60
  case style
59
61
  when :then
60
62
  node.method?(:yield_self)
@@ -289,9 +289,7 @@ module RuboCop
289
289
  private
290
290
 
291
291
  def modifier_range(node)
292
- Parser::Source::Range.new(node.source_range.source_buffer,
293
- node.loc.keyword.begin_pos,
294
- node.source_range.end_pos)
292
+ node.loc.keyword.join(node.source_range.end)
295
293
  end
296
294
  end
297
295
  end
@@ -16,6 +16,9 @@ module RuboCop
16
16
  # The exploded style has an `AllowedCompactTypes` configuration
17
17
  # option that takes an Array of exception name Strings.
18
18
  #
19
+ # @safety
20
+ # This cop is unsafe because `raise Foo` calls `Foo.exception`, not `Foo.new`.
21
+ #
19
22
  # @example EnforcedStyle: exploded (default)
20
23
  # # bad
21
24
  # raise StandardError.new('message')
@@ -81,14 +81,14 @@ module RuboCop
81
81
  redundant_argument = redundant_arg_for_method(node.method_name.to_s)
82
82
  return false if redundant_argument.nil?
83
83
 
84
- node.first_argument == redundant_argument
84
+ node.first_argument.source.sub(/\A'/, '"').sub(/'\z/, '"') == redundant_argument
85
85
  end
86
86
 
87
87
  def redundant_arg_for_method(method_name)
88
88
  arg = cop_config['Methods'].fetch(method_name) { return }
89
89
 
90
90
  @mem ||= {}
91
- @mem[method_name] ||= parse(arg.inspect).ast
91
+ @mem[method_name] ||= arg.inspect
92
92
  end
93
93
 
94
94
  def argument_range(node)
@@ -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
 
@@ -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)
@@ -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 }
@@ -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
@@ -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