rubocop 1.60.2 → 1.63.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) 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 +64 -15
  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 +37 -10
  10. data/lib/rubocop/config_finder.rb +12 -2
  11. data/lib/rubocop/config_obsoletion.rb +1 -1
  12. data/lib/rubocop/config_validator.rb +14 -5
  13. data/lib/rubocop/cop/autocorrect_logic.rb +6 -1
  14. data/lib/rubocop/cop/base.rb +52 -6
  15. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +4 -8
  16. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +5 -13
  17. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +5 -1
  18. data/lib/rubocop/cop/internal_affairs/example_description.rb +1 -0
  19. data/lib/rubocop/cop/internal_affairs/method_name_end_with.rb +8 -6
  20. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +122 -28
  21. data/lib/rubocop/cop/internal_affairs/redundant_expect_offense_arguments.rb +34 -0
  22. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  23. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +14 -7
  24. data/lib/rubocop/cop/layout/end_alignment.rb +3 -1
  25. data/lib/rubocop/cop/layout/redundant_line_break.rb +11 -3
  26. data/lib/rubocop/cop/layout/space_before_block_braces.rb +19 -10
  27. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +1 -1
  28. data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -4
  29. data/lib/rubocop/cop/lint/debugger.rb +27 -2
  30. data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
  31. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +14 -9
  32. data/lib/rubocop/cop/lint/redundant_with_index.rb +4 -0
  33. data/lib/rubocop/cop/lint/rescue_type.rb +1 -3
  34. data/lib/rubocop/cop/lint/script_permission.rb +3 -3
  35. data/lib/rubocop/cop/lint/syntax.rb +1 -1
  36. data/lib/rubocop/cop/lint/to_enum_arguments.rb +7 -2
  37. data/lib/rubocop/cop/lint/useless_times.rb +1 -1
  38. data/lib/rubocop/cop/lint/void.rb +6 -1
  39. data/lib/rubocop/cop/mixin/code_length.rb +12 -1
  40. data/lib/rubocop/cop/mixin/method_complexity.rb +15 -6
  41. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  42. data/lib/rubocop/cop/mixin/safe_assignment.rb +1 -1
  43. data/lib/rubocop/cop/naming/block_forwarding.rb +31 -12
  44. data/lib/rubocop/cop/naming/file_name.rb +2 -2
  45. data/lib/rubocop/cop/naming/inclusive_language.rb +1 -2
  46. data/lib/rubocop/cop/naming/predicate_name.rb +2 -2
  47. data/lib/rubocop/cop/registry.rb +1 -1
  48. data/lib/rubocop/cop/style/alias.rb +1 -0
  49. data/lib/rubocop/cop/style/arguments_forwarding.rb +29 -8
  50. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  51. data/lib/rubocop/cop/style/class_vars.rb +3 -3
  52. data/lib/rubocop/cop/style/collection_compact.rb +3 -3
  53. data/lib/rubocop/cop/style/commented_keyword.rb +5 -2
  54. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -5
  55. data/lib/rubocop/cop/style/copyright.rb +16 -11
  56. data/lib/rubocop/cop/style/eval_with_location.rb +2 -0
  57. data/lib/rubocop/cop/style/exact_regexp_match.rb +2 -1
  58. data/lib/rubocop/cop/style/for.rb +2 -0
  59. data/lib/rubocop/cop/style/format_string.rb +9 -9
  60. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -1
  61. data/lib/rubocop/cop/style/hash_syntax.rb +6 -2
  62. data/lib/rubocop/cop/style/inverse_methods.rb +8 -8
  63. data/lib/rubocop/cop/style/invertible_unless_condition.rb +10 -5
  64. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +5 -8
  65. data/lib/rubocop/cop/style/map_into_array.rb +175 -0
  66. data/lib/rubocop/cop/style/map_to_hash.rb +1 -1
  67. data/lib/rubocop/cop/style/map_to_set.rb +1 -1
  68. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
  69. data/lib/rubocop/cop/style/multiline_method_signature.rb +10 -1
  70. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +4 -0
  71. data/lib/rubocop/cop/style/nil_comparison.rb +2 -0
  72. data/lib/rubocop/cop/style/object_then.rb +5 -3
  73. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -3
  74. data/lib/rubocop/cop/style/raise_args.rb +4 -1
  75. data/lib/rubocop/cop/style/redundant_argument.rb +25 -2
  76. data/lib/rubocop/cop/style/redundant_assignment.rb +10 -2
  77. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +5 -4
  78. data/lib/rubocop/cop/style/redundant_each.rb +1 -1
  79. data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
  80. data/lib/rubocop/cop/style/redundant_line_continuation.rb +5 -0
  81. data/lib/rubocop/cop/style/redundant_percent_q.rb +1 -1
  82. data/lib/rubocop/cop/style/redundant_return.rb +6 -0
  83. data/lib/rubocop/cop/style/sample.rb +1 -3
  84. data/lib/rubocop/cop/team.rb +3 -0
  85. data/lib/rubocop/cop/utils/regexp_ranges.rb +1 -1
  86. data/lib/rubocop/cops_documentation_generator.rb +4 -2
  87. data/lib/rubocop/directive_comment.rb +10 -8
  88. data/lib/rubocop/formatter/clang_style_formatter.rb +3 -7
  89. data/lib/rubocop/formatter/html_formatter.rb +30 -10
  90. data/lib/rubocop/formatter/offense_count_formatter.rb +12 -2
  91. data/lib/rubocop/formatter/tap_formatter.rb +3 -7
  92. data/lib/rubocop/lockfile.rb +34 -4
  93. data/lib/rubocop/lsp/logger.rb +1 -1
  94. data/lib/rubocop/lsp/routes.rb +1 -1
  95. data/lib/rubocop/lsp/runtime.rb +1 -1
  96. data/lib/rubocop/lsp/server.rb +5 -2
  97. data/lib/rubocop/lsp/severity.rb +1 -1
  98. data/lib/rubocop/lsp.rb +29 -0
  99. data/lib/rubocop/magic_comment.rb +1 -1
  100. data/lib/rubocop/options.rb +11 -0
  101. data/lib/rubocop/path_util.rb +6 -2
  102. data/lib/rubocop/rspec/cop_helper.rb +8 -2
  103. data/lib/rubocop/rspec/expect_offense.rb +16 -8
  104. data/lib/rubocop/rspec/shared_contexts.rb +49 -18
  105. data/lib/rubocop/rspec/support.rb +2 -1
  106. data/lib/rubocop/runner.rb +12 -2
  107. data/lib/rubocop/target_finder.rb +84 -78
  108. data/lib/rubocop/target_ruby.rb +82 -80
  109. data/lib/rubocop/version.rb +19 -4
  110. data/lib/rubocop.rb +1 -0
  111. metadata +10 -6
@@ -38,6 +38,7 @@ module RuboCop
38
38
 
39
39
  private
40
40
 
41
+ # rubocop:disable Metrics/AbcSize
41
42
  def autocorrect(corrector, node, begin_of_arguments)
42
43
  arguments = node.arguments
43
44
  joined_arguments = arguments.map(&:source).join(', ')
@@ -49,9 +50,17 @@ module RuboCop
49
50
  corrector.remove(range_by_whole_lines(arguments.loc.end, include_final_newline: true))
50
51
  end
51
52
 
52
- corrector.remove(arguments_range(node))
53
+ arguments_range = arguments_range(node)
54
+ # If the method name isn't on the same line as def, move it directly after def
55
+ if arguments_range.first_line != opening_line(node)
56
+ corrector.remove(node.loc.name)
57
+ corrector.insert_after(node.loc.keyword, " #{node.loc.name.source}")
58
+ end
59
+
60
+ corrector.remove(arguments_range)
53
61
  corrector.insert_after(begin_of_arguments, joined_arguments)
54
62
  end
63
+ # rubocop:enable Metrics/AbcSize
55
64
 
56
65
  def last_line_source_of_arguments(arguments)
57
66
  processed_source[arguments.last_line - 1].strip
@@ -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
 
@@ -44,6 +44,8 @@ module RuboCop
44
44
  def_node_matcher :nil_check?, '(send _ :nil?)'
45
45
 
46
46
  def on_send(node)
47
+ return unless node.receiver
48
+
47
49
  style_check?(node) do
48
50
  add_offense(node.loc.selector) do |corrector|
49
51
  new_code = if prefer_comparison?
@@ -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')
@@ -77,7 +80,7 @@ module RuboCop
77
80
 
78
81
  def correction_exploded_to_compact(node)
79
82
  exception_node, *message_nodes = *node.arguments
80
- return node.source if message_nodes.size > 1
83
+ return if message_nodes.size > 1
81
84
 
82
85
  argument = message_nodes.first.source
83
86
  exception_class = exception_node.receiver&.source || exception_node.source
@@ -81,14 +81,20 @@ 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
+ target_argument = if node.first_argument.respond_to?(:value)
85
+ node.first_argument.value
86
+ else
87
+ node.first_argument
88
+ end
89
+
90
+ argument_matched?(target_argument, redundant_argument)
85
91
  end
86
92
 
87
93
  def redundant_arg_for_method(method_name)
88
94
  arg = cop_config['Methods'].fetch(method_name) { return }
89
95
 
90
96
  @mem ||= {}
91
- @mem[method_name] ||= parse(arg.inspect).ast
97
+ @mem[method_name] ||= arg.inspect
92
98
  end
93
99
 
94
100
  def argument_range(node)
@@ -98,6 +104,23 @@ module RuboCop
98
104
  range_with_surrounding_space(node.first_argument.source_range, newlines: false)
99
105
  end
100
106
  end
107
+
108
+ def argument_matched?(target_argument, redundant_argument)
109
+ argument = if target_argument.is_a?(AST::Node)
110
+ target_argument.source
111
+ elsif exclude_cntrl_character?(target_argument, redundant_argument)
112
+ target_argument.inspect
113
+ else
114
+ target_argument.to_s
115
+ end
116
+
117
+ argument == redundant_argument
118
+ end
119
+
120
+ def exclude_cntrl_character?(target_argument, redundant_argument)
121
+ !target_argument.to_s.sub(/\A'/, '"').sub(/'\z/, '"').match?(/[[:cntrl:]]/) ||
122
+ !redundant_argument.match?(/[[:cntrl:]]/)
123
+ end
101
124
  end
102
125
  end
103
126
  end
@@ -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
 
@@ -18,14 +18,15 @@ module RuboCop
18
18
  extend AutoCorrector
19
19
 
20
20
  MSG = 'Remove the redundant current directory path.'
21
+ RESTRICT_ON_SEND = %i[require_relative].freeze
21
22
  CURRENT_DIRECTORY_PATH = './'
22
23
 
23
24
  def on_send(node)
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|
@@ -86,7 +86,7 @@ module RuboCop
86
86
  def range(node)
87
87
  return node.selector unless node.method?(:each)
88
88
 
89
- if node.parent.call_type?
89
+ if node.parent&.call_type?
90
90
  node.selector.join(node.parent.loc.dot)
91
91
  else
92
92
  node.loc.dot.join(node.selector)
@@ -79,7 +79,7 @@ module RuboCop
79
79
  private_constant :REPLACEMENT_METHODS
80
80
 
81
81
  def on_send(node)
82
- return if node.arguments? || node.block_node
82
+ return if node.arguments? || node.block_literal?
83
83
 
84
84
  select_predicate?(node) do |select_node, filter_method|
85
85
  return if RAILS_METHODS.include?(filter_method) && !active_support_extensions_enabled?
@@ -140,7 +140,11 @@ module RuboCop
140
140
  current_token.type == :tIDENTIFIER && ARGUMENT_TYPES.include?(next_token.type)
141
141
  end
142
142
 
143
+ # rubocop:disable Metrics/AbcSize
143
144
  def argument_newline?(node)
145
+ node = node.to_a.last if node.assignment?
146
+ return false if node.parenthesized_call?
147
+
144
148
  node = node.children.first if node.root? && node.begin_type?
145
149
 
146
150
  if argument_is_method?(node)
@@ -151,6 +155,7 @@ module RuboCop
151
155
  node.loc.selector.line != node.first_argument.loc.line
152
156
  end
153
157
  end
158
+ # rubocop:enable Metrics/AbcSize
154
159
 
155
160
  def find_node_for_line(line)
156
161
  processed_source.ast.each_node do |node|
@@ -53,7 +53,7 @@ module RuboCop
53
53
  return if interpolated_quotes?(node) || allowed_percent_q?(node)
54
54
 
55
55
  add_offense(node) do |corrector|
56
- delimiter = /^%Q[^"]+$|'/.match?(node.source) ? QUOTE : SINGLE_QUOTE
56
+ delimiter = /\A%Q[^"]+\z|'/.match?(node.source) ? QUOTE : SINGLE_QUOTE
57
57
 
58
58
  corrector.replace(node.loc.begin, delimiter)
59
59
  corrector.replace(node.loc.end, delimiter)
@@ -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)
@@ -174,6 +174,9 @@ module RuboCop
174
174
  end
175
175
 
176
176
  def support_target_rails_version?(cop)
177
+ # In this case, the rails version was already checked by `#excluded_file?`
178
+ return true if defined?(RuboCop::Rails::TargetRailsVersion::USES_REQUIRES_GEM_API)
179
+
177
180
  return true unless cop.class.respond_to?(:support_target_rails_version?)
178
181
 
179
182
  cop.class.support_target_rails_version?(cop.target_rails_version)
@@ -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
@@ -24,14 +24,10 @@ module RuboCop
24
24
  message: message(offense)
25
25
  )
26
26
 
27
- begin
28
- return unless valid_line?(offense)
27
+ return unless valid_line?(offense)
29
28
 
30
- report_line(offense.location)
31
- report_highlighted_area(offense.highlighted_area)
32
- rescue IndexError
33
- # range is not on a valid line; perhaps the source file is empty
34
- end
29
+ report_line(offense.location)
30
+ report_highlighted_area(offense.highlighted_area)
35
31
  end
36
32
 
37
33
  def valid_line?(offense)
@@ -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
@@ -53,14 +53,10 @@ module RuboCop
53
53
  message: message(offense)
54
54
  )
55
55
 
56
- begin
57
- return unless valid_line?(offense)
56
+ return unless valid_line?(offense)
58
57
 
59
- report_line(offense.location)
60
- report_highlighted_area(offense.highlighted_area)
61
- rescue IndexError
62
- # range is not on a valid line; perhaps the source file is empty
63
- end
58
+ report_line(offense.location)
59
+ report_highlighted_area(offense.highlighted_area)
64
60
  end
65
61
 
66
62
  def annotate_message(msg)
@@ -5,14 +5,23 @@ module RuboCop
5
5
  # Does not actually resolve gems, just parses the lockfile.
6
6
  # @api private
7
7
  class Lockfile
8
- # Gems that the bundle depends on
8
+ # @param [String, Pathname, nil] lockfile_path
9
+ def initialize(lockfile_path = nil)
10
+ lockfile_path ||= defined?(Bundler) ? Bundler.default_lockfile : nil
11
+
12
+ @lockfile_path = lockfile_path
13
+ end
14
+
15
+ # Gems that the bundle directly depends on.
16
+ # @return [Array<Bundler::Dependency>, nil]
9
17
  def dependencies
10
18
  return [] unless parser
11
19
 
12
20
  parser.dependencies.values
13
21
  end
14
22
 
15
- # All activated gems, including transitive dependencies
23
+ # All activated gems, including transitive dependencies.
24
+ # @return [Array<Bundler::Dependency>, nil]
16
25
  def gems
17
26
  return [] unless parser
18
27
 
@@ -21,17 +30,38 @@ module RuboCop
21
30
  parser.dependencies.values.concat(parser.specs.flat_map(&:dependencies))
22
31
  end
23
32
 
33
+ # Returns the locked versions of gems from this lockfile.
34
+ # @param [Boolean] include_transitive_dependencies: When false, only direct dependencies
35
+ # are returned, i.e. those listed explicitly in the `Gemfile`.
36
+ # @returns [Hash{String => Gem::Version}] The locked gem versions, keyed by the gems' names.
37
+ def gem_versions(include_transitive_dependencies: true)
38
+ return {} unless parser
39
+
40
+ all_gem_versions = parser.specs.to_h { |spec| [spec.name, spec.version] }
41
+
42
+ if include_transitive_dependencies
43
+ all_gem_versions
44
+ else
45
+ direct_dep_names = parser.dependencies.keys
46
+ all_gem_versions.slice(*direct_dep_names)
47
+ end
48
+ end
49
+
50
+ # Whether this lockfile includes the named gem, directly or indirectly.
51
+ # @param [String] name
52
+ # @return [Boolean]
24
53
  def includes_gem?(name)
25
54
  gems.any? { |gem| gem.name == name }
26
55
  end
27
56
 
28
57
  private
29
58
 
59
+ # @return [Bundler::LockfileParser, nil]
30
60
  def parser
31
- return unless defined?(Bundler) && Bundler.default_lockfile
32
61
  return @parser if defined?(@parser)
62
+ return unless @lockfile_path
33
63
 
34
- lockfile = Bundler.read_file(Bundler.default_lockfile)
64
+ lockfile = Bundler.read_file(@lockfile_path)
35
65
  @parser = lockfile ? Bundler::LockfileParser.new(lockfile) : nil
36
66
  rescue Bundler::BundlerError
37
67
  nil
@@ -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