forked from rubocop/rubocop
/
disabled_config_formatter.rb
250 lines (199 loc) · 8.57 KB
/
disabled_config_formatter.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# frozen_string_literal: true
module RuboCop
module Formatter
# This formatter displays a YAML configuration file where all cops that
# detected any offenses are configured to not detect the offense.
class DisabledConfigFormatter < BaseFormatter
include PathUtil
HEADING = <<~COMMENTS
# This configuration was generated by
# `%<command>s`
# %<timestamp>susing RuboCop version #{Version.version}.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
COMMENTS
@config_to_allow_offenses = {}
@detected_styles = {}
class << self
attr_accessor :config_to_allow_offenses, :detected_styles
end
def initialize(output, options = {})
super
@cops_with_offenses ||= Hash.new(0)
@files_with_offenses ||= {}
end
def file_started(_file, _file_info)
@exclude_limit_option = @options[:exclude_limit]
@exclude_limit = Integer(@exclude_limit_option ||
RuboCop::Options::DEFAULT_MAXIMUM_EXCLUSION_ITEMS)
end
def file_finished(file, offenses)
offenses.each do |o|
@cops_with_offenses[o.cop_name] += 1
@files_with_offenses[o.cop_name] ||= Set.new
@files_with_offenses[o.cop_name] << file
end
end
def finished(_inspected_files)
output.puts format(HEADING, command: command, timestamp: timestamp)
# Syntax isn't a real cop and it can't be disabled.
@cops_with_offenses.delete('Lint/Syntax')
output_offenses
puts "Created #{output.path}."
end
private
def show_timestamp?
@options.fetch(:auto_gen_timestamp, true)
end
def show_offense_counts?
@options.fetch(:offense_counts, true)
end
def command
command = 'rubocop --auto-gen-config'
command += ' --auto-gen-only-exclude' if @options[:auto_gen_only_exclude]
if @exclude_limit_option
command += format(' --exclude-limit %<limit>d', limit: Integer(@exclude_limit_option))
end
command += ' --no-offense-counts' unless show_offense_counts?
command += ' --no-auto-gen-timestamp' unless show_timestamp?
command
end
def timestamp
show_timestamp? ? "on #{Time.now.utc} " : ''
end
def output_offenses
@cops_with_offenses.sort.each do |cop_name, offense_count|
output_cop(cop_name, offense_count)
end
end
def output_cop(cop_name, offense_count)
output.puts
cfg = self.class.config_to_allow_offenses[cop_name] || {}
set_max(cfg, cop_name)
# To avoid malformed YAML when potentially reading the config in
# #excludes, we use an output buffer and append it to the actual output
# only when it results in valid YAML.
output_buffer = StringIO.new
output_cop_comments(output_buffer, cfg, cop_name, offense_count)
output_cop_config(output_buffer, cfg, cop_name)
output.puts(output_buffer.string)
end
def set_max(cfg, cop_name)
return unless cfg[:exclude_limit]
# In case auto_gen_only_exclude is set, only modify the maximum if the
# files are not excluded one by one.
if !@options[:auto_gen_only_exclude] || @files_with_offenses[cop_name].size > @exclude_limit
cfg.merge!(cfg[:exclude_limit])
end
# Remove already used exclude_limit.
cfg.reject! { |key| key == :exclude_limit }
end
def output_cop_comments(output_buffer, cfg, cop_name, offense_count)
output_buffer.puts "# Offense count: #{offense_count}" if show_offense_counts?
cop_class = Cop::Registry.global.find_by_cop_name(cop_name)
default_cfg = default_config(cop_name)
if supports_safe_autocorrect?(cop_class, default_cfg)
output_buffer.puts '# This cop supports safe autocorrection (--autocorrect).'
elsif supports_unsafe_autocorrect?(cop_class, default_cfg)
output_buffer.puts '# This cop supports unsafe autocorrection (--autocorrect-all).'
end
return unless default_cfg
params = cop_config_params(default_cfg, cfg)
return if params.empty?
output_cop_param_comments(output_buffer, params, default_cfg)
end
def supports_safe_autocorrect?(cop_class, default_cfg)
cop_class&.support_autocorrect? && (default_cfg.nil? || safe_autocorrect?(default_cfg))
end
def supports_unsafe_autocorrect?(cop_class, default_cfg)
cop_class&.support_autocorrect? && !safe_autocorrect?(default_cfg)
end
def cop_config_params(default_cfg, cfg)
default_cfg.keys -
%w[Description StyleGuide Reference Enabled Exclude Safe
SafeAutoCorrect VersionAdded VersionChanged VersionRemoved] -
cfg.keys
end
def output_cop_param_comments(output_buffer, params, default_cfg)
config_params = params.reject { |p| p.start_with?('Supported') }
output_buffer.puts("# Configuration parameters: #{config_params.join(', ')}.")
params.each do |param|
value = default_cfg[param]
next unless value.is_a?(Array)
next if value.empty?
output_buffer.puts "# #{param}: #{value.uniq.join(', ')}"
end
end
def default_config(cop_name)
RuboCop::ConfigLoader.default_configuration[cop_name]
end
def output_cop_config(output_buffer, cfg, cop_name)
# 'Enabled' option will be put into file only if exclude
# limit is exceeded.
cfg_without_enabled = cfg.reject { |key| key == 'Enabled' }
output_buffer.puts "#{cop_name}:"
cfg_without_enabled.each do |key, value|
value = value[0] if value.is_a?(Array)
output_buffer.puts " #{key}: #{value}"
end
output_offending_files(output_buffer, cfg_without_enabled, cop_name)
end
def output_offending_files(output_buffer, cfg, cop_name)
return unless cfg.empty?
offending_files = @files_with_offenses[cop_name].sort
if offending_files.count > @exclude_limit
output_buffer.puts ' Enabled: false'
else
output_exclude_list(output_buffer, offending_files, cop_name)
end
end
def output_exclude_list(output_buffer, offending_files, cop_name)
require 'pathname'
parent = Pathname.new(Dir.pwd)
output_buffer.puts ' Exclude:'
excludes(offending_files, cop_name, parent).each do |exclude_path|
output_exclude_path(output_buffer, exclude_path, parent)
end
end
def excludes(offending_files, cop_name, parent)
# Exclude properties in .rubocop_todo.yml override default ones, as well as any custom
# excludes in .rubocop.yml, so in order to retain those excludes we must copy them.
# There can be multiple .rubocop.yml files in subdirectories, but we just look at the
# current working directory.
config = ConfigStore.new.for(parent)
cfg = config[cop_name] || {}
if merge_mode_for_exclude?(config) || merge_mode_for_exclude?(cfg)
offending_files
else
cop_exclude = cfg['Exclude']
if cop_exclude && cop_exclude != default_config(cop_name)['Exclude']
warn "`#{cop_name}: Exclude` in `#{smart_path(config.loaded_path)}` overrides a " \
'generated `Exclude` in `.rubocop_todo.yml`. Either remove ' \
"`#{cop_name}: Exclude` or add `inherit_mode: merge: - Exclude`."
end
((cop_exclude || []) + offending_files).uniq
end
end
def merge_mode_for_exclude?(cfg)
Array(cfg.to_h.dig('inherit_mode', 'merge')).include?('Exclude')
end
def output_exclude_path(output_buffer, exclude_path, parent)
# exclude_path is either relative path, an absolute path, or a regexp.
file_path = Pathname.new(exclude_path)
relative = file_path.relative_path_from(parent)
output_buffer.puts " - '#{relative}'"
rescue ArgumentError
file = exclude_path
output_buffer.puts " - '#{file}'"
rescue TypeError
regexp = exclude_path
output_buffer.puts " - !ruby/regexp /#{regexp.source}/"
end
def safe_autocorrect?(config)
config.fetch('Safe', true) && config.fetch('SafeAutoCorrect', true)
end
end
end
end