/
team.rb
266 lines (213 loc) · 8.02 KB
/
team.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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# frozen_string_literal: true
module RuboCop
module Cop
# A group of cops, ready to be called on duty to inspect files.
# Team is responsible for selecting only relevant cops to be sent on duty,
# as well as insuring that the needed forces are sent along with them.
#
# For performance reasons, Team will first dispatch cops & forces in two groups,
# first the ones needed for autocorrection (if any), then the rest
# (unless autocorrections happened).
class Team
# @return [Team]
def self.new(cop_or_classes, config, options = {})
# Support v0 api:
return mobilize(cop_or_classes, config, options) if cop_or_classes.first.is_a?(Class)
super
end
# @return [Team] with cops assembled from the given `cop_classes`
def self.mobilize(cop_classes, config, options = {})
cops = mobilize_cops(cop_classes, config, options)
new(cops, config, options)
end
# @return [Array<Cop::Base>]
def self.mobilize_cops(cop_classes, config, options = {})
cop_classes = Registry.new(cop_classes.to_a, options) unless cop_classes.is_a?(Registry)
cop_classes.map do |cop_class|
cop_class.new(config, options)
end
end
# @return [Array<Force>] needed for the given cops
def self.forces_for(cops)
needed = Hash.new { |h, k| h[k] = [] }
cops.each do |cop|
forces = cop.class.joining_forces
if forces.is_a?(Array)
forces.each { |force| needed[force] << cop }
elsif forces
needed[forces] << cop
end
end
needed.map { |force_class, joining_cops| force_class.new(joining_cops) }
end
attr_reader :errors, :warnings, :updated_source_file, :cops
alias updated_source_file? updated_source_file
def initialize(cops, config = nil, options = {})
@cops = cops
@config = config
@options = options
reset
@ready = true
@registry = Registry.new(cops, options.dup)
validate_config
end
def autocorrect?
@options[:autocorrect]
end
def debug?
@options[:debug]
end
# @deprecated. Use investigate
# @return Array<offenses>
def inspect_file(processed_source)
investigate(processed_source).offenses
end
# @return [Commissioner::InvestigationReport]
def investigate(processed_source, offset: 0, original: processed_source)
be_ready
# The autocorrection process may have to be repeated multiple times
# until there are no corrections left to perform
# To speed things up, run autocorrecting cops by themselves, and only
# run the other cops when no corrections are left
on_duty = roundup_relevant_cops(processed_source)
autocorrect_cops, other_cops = on_duty.partition(&:autocorrect?)
report = investigate_partial(autocorrect_cops, processed_source,
offset: offset, original: original)
unless autocorrect(processed_source, report, offset: offset, original: original)
# If we corrected some errors, another round of inspection will be
# done, and any other offenses will be caught then, so only need
# to check other_cops if no correction was done
report = report.merge(investigate_partial(other_cops, processed_source,
offset: offset, original: original))
end
process_errors(processed_source.path, report.errors)
report
ensure
@ready = false
end
# @deprecated
def forces
@forces ||= self.class.forces_for(cops)
end
def external_dependency_checksum
keys = cops.filter_map(&:external_dependency_checksum)
Digest::SHA1.hexdigest(keys.join)
end
private
def autocorrect(processed_source, report, original:, offset:)
@updated_source_file = false
return unless autocorrect?
return if report.processed_source.parser_error
new_source = autocorrect_report(report, original: original, offset: offset)
return unless new_source
if @options[:stdin]
# holds source read in from stdin, when --stdin option is used
@options[:stdin] = new_source
else
filename = processed_source.file_path
File.write(filename, new_source)
end
@updated_source_file = true
end
def be_ready
return if @ready
reset
@cops.map!(&:ready)
@ready = true
end
def reset
@errors = []
@warnings = []
end
# @return [Commissioner::InvestigationReport]
def investigate_partial(cops, processed_source, offset:, original:)
commissioner = Commissioner.new(cops, self.class.forces_for(cops), @options)
commissioner.investigate(processed_source, offset: offset, original: original)
end
# @return [Array<cop>]
def roundup_relevant_cops(processed_source)
cops.select do |cop|
next true if processed_source.comment_config.cop_opted_in?(cop)
next false if cop.excluded_file?(processed_source.file_path)
next false unless @registry.enabled?(cop, @config)
support_target_ruby_version?(cop) && support_target_rails_version?(cop)
end
end
def support_target_ruby_version?(cop)
return true unless cop.class.respond_to?(:support_target_ruby_version?)
cop.class.support_target_ruby_version?(cop.target_ruby_version)
end
def support_target_rails_version?(cop)
return true unless cop.class.respond_to?(:support_target_rails_version?)
cop.class.support_target_rails_version?(cop.target_rails_version)
end
def autocorrect_report(report, offset:, original:)
corrector = collate_corrections(report, offset: offset, original: original)
corrector.rewrite unless corrector.empty?
end
def collate_corrections(report, offset:, original:)
corrector = Corrector.new(original)
each_corrector(report) do |to_merge|
suppress_clobbering do
if offset.positive?
corrector.import!(to_merge, offset: offset)
else
corrector.merge!(to_merge)
end
end
end
corrector
end
def each_corrector(report)
skips = Set.new
report.cop_reports.each do |cop_report|
cop = cop_report.cop
corrector = cop_report.corrector
next if corrector.nil? || corrector.empty?
next if skips.include?(cop.class)
yield corrector
skips.merge(cop.class.autocorrect_incompatible_with)
end
end
def suppress_clobbering
yield
rescue ::Parser::ClobberingError
# ignore Clobbering errors
end
def validate_config
cops.each do |cop|
cop.validate_config if cop.respond_to?(:validate_config)
end
end
def process_errors(file, errors)
errors.each do |error|
line = ":#{error.line}" if error.line
column = ":#{error.column}" if error.column
location = "#{file}#{line}#{column}"
cause = error.cause
if cause.is_a?(Warning)
handle_warning(cause, location)
else
handle_error(cause, location, error.cop)
end
end
end
def handle_warning(error, location)
message = Rainbow("#{error.message} (from file: #{location})").yellow
@warnings << message
warn message
puts error.backtrace if debug?
end
def handle_error(error, location, cop)
message = Rainbow("An error occurred while #{cop.name} cop was inspecting #{location}.").red
@errors << message
warn message
if debug?
puts error.message, error.backtrace
else
warn 'To see the complete backtrace run rubocop -d.'
end
end
end
end
end