rubocop 0.33.0 → 0.34.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rubocop might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +62 -1
- data/README.md +65 -6
- data/config/default.yml +30 -0
- data/config/disabled.yml +5 -0
- data/config/enabled.yml +19 -3
- data/lib/rubocop.rb +7 -2
- data/lib/rubocop/cli.rb +1 -1
- data/lib/rubocop/config.rb +11 -6
- data/lib/rubocop/config_loader.rb +7 -3
- data/lib/rubocop/cop/commissioner.rb +6 -11
- data/lib/rubocop/cop/cop.rb +7 -3
- data/lib/rubocop/cop/lint/deprecated_class_methods.rb +10 -8
- data/lib/rubocop/cop/lint/duplicated_key.rb +37 -0
- data/lib/rubocop/cop/lint/end_alignment.rb +6 -6
- data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +57 -14
- data/lib/rubocop/cop/metrics/method_length.rb +1 -3
- data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -2
- data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +11 -1
- data/lib/rubocop/cop/mixin/configurable_naming.rb +14 -3
- data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +1 -3
- data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +9 -0
- data/lib/rubocop/cop/mixin/method_preference.rb +28 -0
- data/lib/rubocop/cop/mixin/safe_assignment.rb +2 -2
- data/lib/rubocop/cop/performance/case_when_splat.rb +132 -0
- data/lib/rubocop/cop/performance/string_replacement.rb +45 -28
- data/lib/rubocop/cop/rails/action_filter.rb +31 -5
- data/lib/rubocop/cop/rails/date.rb +6 -5
- data/lib/rubocop/cop/rails/read_write_attribute.rb +1 -1
- data/lib/rubocop/cop/rails/time_zone.rb +12 -1
- data/lib/rubocop/cop/style/alias.rb +1 -0
- data/lib/rubocop/cop/style/block_delimiters.rb +6 -5
- data/lib/rubocop/cop/style/collection_methods.rb +2 -20
- data/lib/rubocop/cop/style/else_alignment.rb +2 -1
- data/lib/rubocop/cop/style/empty_line_between_defs.rb +42 -13
- data/lib/rubocop/cop/style/extra_spacing.rb +17 -3
- data/lib/rubocop/cop/style/first_parameter_indentation.rb +1 -1
- data/lib/rubocop/cop/style/hash_syntax.rb +5 -0
- data/lib/rubocop/cop/style/indentation_width.rb +7 -1
- data/lib/rubocop/cop/style/initial_indentation.rb +5 -0
- data/lib/rubocop/cop/style/multiline_operation_indentation.rb +1 -1
- data/lib/rubocop/cop/style/mutable_constant.rb +35 -0
- data/lib/rubocop/cop/style/next.rb +31 -14
- data/lib/rubocop/cop/style/option_hash.rb +9 -1
- data/lib/rubocop/cop/style/parallel_assignment.rb +21 -44
- data/lib/rubocop/cop/style/redundant_freeze.rb +37 -0
- data/lib/rubocop/cop/style/rescue_ensure_alignment.rb +2 -1
- data/lib/rubocop/cop/style/rescue_modifier.rb +35 -3
- data/lib/rubocop/cop/style/space_inside_string_interpolation.rb +1 -0
- data/lib/rubocop/cop/style/string_methods.rb +32 -0
- data/lib/rubocop/cop/style/symbol_proc.rb +54 -12
- data/lib/rubocop/cop/style/trailing_blank_lines.rb +1 -1
- data/lib/rubocop/cop/team.rb +2 -2
- data/lib/rubocop/cop/util.rb +2 -2
- data/lib/rubocop/cop/variable_force.rb +10 -10
- data/lib/rubocop/cop/variable_force/locatable.rb +5 -5
- data/lib/rubocop/formatter/formatter_set.rb +1 -0
- data/lib/rubocop/options.rb +24 -1
- data/lib/rubocop/result_cache.rb +121 -0
- data/lib/rubocop/runner.rb +55 -15
- data/lib/rubocop/target_finder.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- data/relnotes/v0.34.0.md +182 -0
- data/rubocop.gemspec +1 -1
- metadata +12 -4
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# This cop check for uses of Object#freeze on immutable objects.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# CONST = 1.freeze
|
11
|
+
#
|
12
|
+
# # good
|
13
|
+
# CONST = 1
|
14
|
+
class RedundantFreeze < Cop
|
15
|
+
MSG = 'Freezing immutable objects is pointless.'.freeze
|
16
|
+
|
17
|
+
TARGET_NODES = [:int, :float, :sym].freeze
|
18
|
+
|
19
|
+
def on_send(node)
|
20
|
+
receiver, method_name, *args = *node
|
21
|
+
|
22
|
+
return unless receiver && TARGET_NODES.include?(receiver.type)
|
23
|
+
return unless method_name == :freeze && args.empty?
|
24
|
+
|
25
|
+
add_offense(node, :expression)
|
26
|
+
end
|
27
|
+
|
28
|
+
def autocorrect(node)
|
29
|
+
lambda do |corrector|
|
30
|
+
corrector.remove(node.loc.dot)
|
31
|
+
corrector.remove(node.loc.selector)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -5,14 +5,46 @@ module RuboCop
|
|
5
5
|
module Style
|
6
6
|
# This cop checks for uses of rescue in its modifier form.
|
7
7
|
class RescueModifier < Cop
|
8
|
+
include AutocorrectAlignment
|
9
|
+
|
8
10
|
MSG = 'Avoid using `rescue` in its modifier form.'
|
9
11
|
|
10
12
|
def investigate(processed_source)
|
11
|
-
processed_source.tokens
|
12
|
-
|
13
|
-
|
13
|
+
@modifier_locations = processed_source.tokens
|
14
|
+
.select { |t| t.type == :kRESCUE_MOD }
|
15
|
+
.map(&:pos)
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_resbody(node)
|
19
|
+
return unless modifier?(node)
|
20
|
+
add_offense(node.parent, :expression)
|
21
|
+
end
|
22
|
+
|
23
|
+
def autocorrect(node)
|
24
|
+
operation, rescue_modifier, = *node
|
25
|
+
*_, rescue_args = *rescue_modifier
|
26
|
+
|
27
|
+
correction =
|
28
|
+
"begin\n" <<
|
29
|
+
indentation(node) << operation.loc.expression.source <<
|
30
|
+
"\n#{offset(node)}rescue\n" <<
|
31
|
+
indentation(node) << rescue_args.loc.expression.source <<
|
32
|
+
"\n#{offset(node)}end"
|
33
|
+
range = Parser::Source::Range.new(node.loc.expression.source_buffer,
|
34
|
+
node.loc.expression.begin_pos,
|
35
|
+
node.loc.expression.end_pos)
|
36
|
+
|
37
|
+
lambda do |corrector|
|
38
|
+
corrector.replace(range, correction)
|
14
39
|
end
|
15
40
|
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def modifier?(node)
|
45
|
+
return false unless @modifier_locations.respond_to?(:include?)
|
46
|
+
@modifier_locations.include?(node.loc.keyword)
|
47
|
+
end
|
16
48
|
end
|
17
49
|
end
|
18
50
|
end
|
@@ -20,6 +20,7 @@ module RuboCop
|
|
20
20
|
def on_dstr(node)
|
21
21
|
node.children.select { |n| n.type == :begin }.each do |begin_node|
|
22
22
|
final_node = begin_node.children.last
|
23
|
+
next unless final_node
|
23
24
|
|
24
25
|
interp = final_node.loc.expression
|
25
26
|
interp_with_surrounding_space = range_with_surrounding_space(interp)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# This cop enforces the use of consistent method names
|
7
|
+
# from the String class.
|
8
|
+
class StringMethods < Cop
|
9
|
+
include MethodPreference
|
10
|
+
|
11
|
+
MSG = 'Prefer `%s` over `%s`.'
|
12
|
+
|
13
|
+
def on_send(node)
|
14
|
+
_receiver, method_name, *_args = *node
|
15
|
+
return unless preferred_methods[method_name]
|
16
|
+
add_offense(node, :selector,
|
17
|
+
format(MSG,
|
18
|
+
preferred_method(method_name),
|
19
|
+
method_name)
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def autocorrect(node)
|
24
|
+
lambda do |corrector|
|
25
|
+
corrector.replace(node.loc.selector,
|
26
|
+
preferred_method(node.loc.selector.source))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -17,18 +17,20 @@ module RuboCop
|
|
17
17
|
PROC_NODE = s(:send, s(:const, nil, :Proc), :new)
|
18
18
|
|
19
19
|
def on_block(node)
|
20
|
-
|
20
|
+
block_send_or_super, block_args, block_body = *node
|
21
21
|
|
22
|
-
|
22
|
+
if super?(block_send_or_super)
|
23
|
+
bmethod_name = :super
|
24
|
+
else
|
25
|
+
_breceiver, bmethod_name, _bargs = *block_send_or_super
|
26
|
+
end
|
23
27
|
|
24
28
|
# TODO: Rails-specific handling that we should probably make
|
25
29
|
# configurable - https://github.com/bbatsov/rubocop/issues/1485
|
26
30
|
# we should ignore lambdas & procs
|
27
|
-
return if
|
31
|
+
return if block_send_or_super == PROC_NODE
|
28
32
|
return if [:lambda, :proc].include?(bmethod_name)
|
29
33
|
return if ignored_method?(bmethod_name)
|
30
|
-
# File.open(file) { |f| f.readlines }
|
31
|
-
return if bargs
|
32
34
|
return unless can_shorten?(block_args, block_body)
|
33
35
|
|
34
36
|
_receiver, method_name, _args = *block_body
|
@@ -41,16 +43,52 @@ module RuboCop
|
|
41
43
|
|
42
44
|
def autocorrect(node)
|
43
45
|
lambda do |corrector|
|
44
|
-
|
46
|
+
block_send_or_super, _block_args, block_body = *node
|
45
47
|
_receiver, method_name, _args = *block_body
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
if super?(block_send_or_super)
|
50
|
+
args = *block_send_or_super
|
51
|
+
autocorrect_method(corrector, node, args, method_name)
|
52
|
+
else
|
53
|
+
_breceiver, _bmethod_name, *args = *block_send_or_super
|
54
|
+
autocorrect_method(corrector, node, args, method_name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
51
58
|
|
52
|
-
|
53
|
-
|
59
|
+
def autocorrect_method(corrector, node, args, method_name)
|
60
|
+
if args.empty?
|
61
|
+
autocorrect_no_args(corrector, node, method_name)
|
62
|
+
else
|
63
|
+
autocorrect_with_args(corrector, node, args, method_name)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def autocorrect_no_args(corrector, node, method_name)
|
68
|
+
corrector.replace(block_range_with_space(node), "(&:#{method_name})")
|
69
|
+
end
|
70
|
+
|
71
|
+
def autocorrect_with_args(corrector, node, args, method_name)
|
72
|
+
corrector.insert_after(args.last.loc.expression, ", &:#{method_name}")
|
73
|
+
corrector.remove(block_range_with_space(node))
|
74
|
+
end
|
75
|
+
|
76
|
+
def block_range_with_space(node)
|
77
|
+
block_range =
|
78
|
+
Parser::Source::Range.new(node.loc.expression.source_buffer,
|
79
|
+
begin_pos_for_replacement(node),
|
80
|
+
node.loc.end.end_pos)
|
81
|
+
range_with_surrounding_space(block_range, :left)
|
82
|
+
end
|
83
|
+
|
84
|
+
def begin_pos_for_replacement(node)
|
85
|
+
block_send_or_super, _block_args, _block_body = *node
|
86
|
+
expr = block_send_or_super.loc.expression
|
87
|
+
|
88
|
+
if (paren_pos = (expr.source =~ /\(\s*\)$/))
|
89
|
+
expr.begin_pos + paren_pos
|
90
|
+
else
|
91
|
+
node.loc.begin.begin_pos
|
54
92
|
end
|
55
93
|
end
|
56
94
|
|
@@ -78,6 +116,10 @@ module RuboCop
|
|
78
116
|
|
79
117
|
block_arg_name == receiver_name
|
80
118
|
end
|
119
|
+
|
120
|
+
def super?(node)
|
121
|
+
[:super, :zsuper].include?(node.type)
|
122
|
+
end
|
81
123
|
end
|
82
124
|
end
|
83
125
|
end
|
@@ -35,7 +35,7 @@ module RuboCop
|
|
35
35
|
begin_pos = sb.source.length - whitespace_at_end.length
|
36
36
|
autocorrect_range = Parser::Source::Range.new(sb, begin_pos,
|
37
37
|
sb.source.length)
|
38
|
-
begin_pos +=
|
38
|
+
begin_pos += 1 unless whitespace_at_end.length == 0
|
39
39
|
report_range = Parser::Source::Range.new(sb, begin_pos,
|
40
40
|
sb.source.length)
|
41
41
|
add_offense(autocorrect_range, report_range,
|
data/lib/rubocop/cop/team.rb
CHANGED
@@ -97,8 +97,8 @@ module RuboCop
|
|
97
97
|
file_errors.each do |cop, errors|
|
98
98
|
errors.each do |e|
|
99
99
|
handle_error(e,
|
100
|
-
"An error occurred while #{cop.name}"
|
101
|
-
" cop was inspecting #{file}.".
|
100
|
+
Rainbow("An error occurred while #{cop.name}" /
|
101
|
+
" cop was inspecting #{file}.".red))
|
102
102
|
end
|
103
103
|
end
|
104
104
|
end
|
data/lib/rubocop/cop/util.rb
CHANGED
@@ -186,8 +186,8 @@ module RuboCop
|
|
186
186
|
end
|
187
187
|
|
188
188
|
def begins_its_line?(range)
|
189
|
-
|
190
|
-
|
189
|
+
source_before_range = range.source_buffer.source[0...range.begin_pos]
|
190
|
+
source_before_range.rpartition("\n").last.strip.empty?
|
191
191
|
end
|
192
192
|
|
193
193
|
def within_node?(inner, outer)
|
@@ -106,28 +106,28 @@ module RuboCop
|
|
106
106
|
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
107
107
|
def dispatch_node(node)
|
108
108
|
case node.type
|
109
|
-
when *ARGUMENT_DECLARATION_TYPES
|
110
|
-
process_variable_declaration(node)
|
111
109
|
when VARIABLE_ASSIGNMENT_TYPE
|
112
110
|
process_variable_assignment(node)
|
113
111
|
when REGEXP_NAMED_CAPTURE_TYPE
|
114
112
|
process_regexp_named_captures(node)
|
115
|
-
when *OPERATOR_ASSIGNMENT_TYPES
|
116
|
-
process_variable_operator_assignment(node)
|
117
113
|
when MULTIPLE_ASSIGNMENT_TYPE
|
118
114
|
process_variable_multiple_assignment(node)
|
119
115
|
when VARIABLE_REFERENCE_TYPE
|
120
116
|
process_variable_referencing(node)
|
121
|
-
when *LOOP_TYPES
|
122
|
-
process_loop(node)
|
123
117
|
when RESCUE_TYPE
|
124
118
|
process_rescue(node)
|
125
119
|
when ZERO_ARITY_SUPER_TYPE
|
126
120
|
process_zero_arity_super(node)
|
127
|
-
when *SCOPE_TYPES
|
128
|
-
process_scope(node)
|
129
121
|
when SEND_TYPE
|
130
122
|
process_send(node)
|
123
|
+
when *ARGUMENT_DECLARATION_TYPES
|
124
|
+
process_variable_declaration(node)
|
125
|
+
when *OPERATOR_ASSIGNMENT_TYPES
|
126
|
+
process_variable_operator_assignment(node)
|
127
|
+
when *LOOP_TYPES
|
128
|
+
process_loop(node)
|
129
|
+
when *SCOPE_TYPES
|
130
|
+
process_scope(node)
|
131
131
|
end
|
132
132
|
end
|
133
133
|
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
@@ -326,13 +326,13 @@ module RuboCop
|
|
326
326
|
case node.type
|
327
327
|
when :lvar
|
328
328
|
referenced_variable_names_in_loop << node.children.first
|
329
|
+
when :lvasgn
|
330
|
+
assignment_nodes_in_loop << node
|
329
331
|
when *OPERATOR_ASSIGNMENT_TYPES
|
330
332
|
asgn_node = node.children.first
|
331
333
|
if asgn_node.type == :lvasgn
|
332
334
|
referenced_variable_names_in_loop << asgn_node.children.first
|
333
335
|
end
|
334
|
-
when :lvasgn
|
335
|
-
assignment_nodes_in_loop << node
|
336
336
|
end
|
337
337
|
end
|
338
338
|
|
@@ -80,9 +80,9 @@ module RuboCop
|
|
80
80
|
case branch_point_node.type
|
81
81
|
when :if then if_body_name
|
82
82
|
when :case then case_body_name
|
83
|
-
when *LOGICAL_OPERATOR_TYPES then logical_operator_body_name
|
84
83
|
when RESCUE_TYPE then rescue_body_name
|
85
84
|
when ENSURE_TYPE then ensure_body_name
|
85
|
+
when *LOGICAL_OPERATOR_TYPES then logical_operator_body_name
|
86
86
|
else fail InvalidBranchBodyError
|
87
87
|
end
|
88
88
|
rescue InvalidBranchBodyError
|
@@ -157,14 +157,14 @@ module RuboCop
|
|
157
157
|
child_index = parent_node.children.index(child_node)
|
158
158
|
|
159
159
|
case parent_node.type
|
160
|
-
when *BRANCH_TYPES
|
161
|
-
child_index != CONDITION_INDEX_OF_BRANCH_NODE
|
162
|
-
when *LOGICAL_OPERATOR_TYPES
|
163
|
-
child_index != LEFT_SIDE_INDEX_OF_LOGICAL_OPERATOR_NODE
|
164
160
|
when RESCUE_TYPE
|
165
161
|
true
|
166
162
|
when ENSURE_TYPE
|
167
163
|
child_index != ENSURE_INDEX_OF_ENSURE_NODE
|
164
|
+
when *BRANCH_TYPES
|
165
|
+
child_index != CONDITION_INDEX_OF_BRANCH_NODE
|
166
|
+
when *LOGICAL_OPERATOR_TYPES
|
167
|
+
child_index != LEFT_SIDE_INDEX_OF_LOGICAL_OPERATOR_NODE
|
168
168
|
else
|
169
169
|
false
|
170
170
|
end
|
data/lib/rubocop/options.rb
CHANGED
@@ -15,9 +15,16 @@ module RuboCop
|
|
15
15
|
|
16
16
|
def parse(args)
|
17
17
|
define_options(args).parse!(args)
|
18
|
+
# The --no-color CLI option sets `color: false` so we don't want the
|
19
|
+
# `no_color` key, which is created automatically.
|
20
|
+
@options.delete(:no_color)
|
18
21
|
|
19
22
|
validate_compatibility
|
20
23
|
|
24
|
+
if @options[:stdin] && !args.one?
|
25
|
+
fail ArgumentError, '-s/--stdin requires exactly one path.'
|
26
|
+
end
|
27
|
+
|
21
28
|
[@options, args]
|
22
29
|
end
|
23
30
|
|
@@ -40,6 +47,7 @@ module RuboCop
|
|
40
47
|
OptionParser.new do |opts|
|
41
48
|
opts.banner = 'Usage: rubocop [options] [file1, file2, ...]'
|
42
49
|
|
50
|
+
add_list_options(opts)
|
43
51
|
add_only_options(opts)
|
44
52
|
add_configuration_options(opts, args)
|
45
53
|
add_formatting_options(opts)
|
@@ -57,6 +65,9 @@ module RuboCop
|
|
57
65
|
(@options[:only] & %w(Lint/UnneededDisable UnneededDisable)).any?
|
58
66
|
fail ArgumentError, 'Lint/UnneededDisable can not be used with --only.'
|
59
67
|
end
|
68
|
+
if @options.key?(:cache) && !%w(true false).include?(@options[:cache])
|
69
|
+
fail ArgumentError, '-C/--cache argument must be true or false'
|
70
|
+
end
|
60
71
|
return unless (incompat = @options.keys & EXITING_OPTIONS).size > 1
|
61
72
|
fail ArgumentError, "Incompatible cli options: #{incompat.inspect}"
|
62
73
|
end
|
@@ -121,6 +132,7 @@ module RuboCop
|
|
121
132
|
|
122
133
|
def add_boolean_flags(opts)
|
123
134
|
option(opts, '-F', '--fail-fast')
|
135
|
+
option(opts, '-C', '--cache FLAG')
|
124
136
|
option(opts, '-d', '--debug')
|
125
137
|
option(opts, '-D', '--display-cop-names')
|
126
138
|
option(opts, '-S', '--display-style-guide')
|
@@ -133,6 +145,11 @@ module RuboCop
|
|
133
145
|
|
134
146
|
option(opts, '-v', '--version')
|
135
147
|
option(opts, '-V', '--verbose-version')
|
148
|
+
option(opts, '-s', '--stdin') { @options[:stdin] = $stdin.read }
|
149
|
+
end
|
150
|
+
|
151
|
+
def add_list_options(opts)
|
152
|
+
option(opts, '-L', '--list-target-files')
|
136
153
|
end
|
137
154
|
|
138
155
|
# Sets a value in the @options hash, based on the given long option and its
|
@@ -222,15 +239,21 @@ module RuboCop
|
|
222
239
|
fail_fast: ['Inspect files in order of modification',
|
223
240
|
'time and stop after the first file',
|
224
241
|
'containing offenses.'],
|
242
|
+
cache: ["Use result caching (FLAG=true) or don't",
|
243
|
+
'(FLAG=false), default determined by',
|
244
|
+
'configuration parameter AllCops: UseCache.'],
|
225
245
|
debug: 'Display debug info.',
|
226
246
|
display_cop_names: 'Display cop names in offense messages.',
|
227
247
|
display_style_guide: 'Display style guide URLs in offense messages.',
|
228
248
|
rails: 'Run extra Rails cops.',
|
229
249
|
lint: 'Run only lint cops.',
|
250
|
+
list_target_files: 'List all files RuboCop will inspect.',
|
230
251
|
auto_correct: 'Auto-correct offenses.',
|
231
252
|
no_color: 'Disable color output.',
|
232
253
|
version: 'Display version.',
|
233
|
-
verbose_version: 'Display verbose version.'
|
254
|
+
verbose_version: 'Display verbose version.',
|
255
|
+
stdin: ['Pipe source from STDIN.',
|
256
|
+
'This is useful for editor integration.']
|
234
257
|
}
|
235
258
|
end
|
236
259
|
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'digest/md5'
|
4
|
+
require 'find'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
module RuboCop
|
8
|
+
# Provides functionality for caching rubocop runs.
|
9
|
+
class ResultCache
|
10
|
+
# Include the user name in the path as a simple means of avoiding write
|
11
|
+
# collisions.
|
12
|
+
def initialize(file, options, config_store, cache_root = nil)
|
13
|
+
cache_root ||= ResultCache.cache_root(config_store)
|
14
|
+
@path = File.join(cache_root, rubocop_checksum, relevant_options(options),
|
15
|
+
file_checksum(file, config_store))
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
File.exist?(@path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def load
|
23
|
+
Marshal.load(IO.read(@path))
|
24
|
+
end
|
25
|
+
|
26
|
+
def save(offenses, disabled_line_ranges, comments)
|
27
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
28
|
+
preliminary_path = "#{@path}_#{rand(1_000_000_000)}"
|
29
|
+
File.open(preliminary_path, 'w') do |f|
|
30
|
+
# The Hash[x.sort] call is a trick that converts a Hash with a default
|
31
|
+
# block to a Hash without a default block. Thus making it possible to
|
32
|
+
# dump.
|
33
|
+
f.write(Marshal.dump([offenses, Hash[disabled_line_ranges.sort],
|
34
|
+
comments]))
|
35
|
+
end
|
36
|
+
# The preliminary path is used so that if there are multiple RuboCop
|
37
|
+
# processes trying to save data for the same inspected file
|
38
|
+
# simultaneously, the only problem we run in to is a competition who gets
|
39
|
+
# to write to the final file. The contents are the same, so no corruption
|
40
|
+
# of data should occur.
|
41
|
+
FileUtils.mv(preliminary_path, @path)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Remove old files so that the cache doesn't grow too big. When the
|
45
|
+
# threshold MaxFilesInCache has been exceeded, the oldest 50% all the files
|
46
|
+
# in the cache are removed. The reason for removing so much is that
|
47
|
+
# cleaning should be done relatively seldom, since there is a slight risk
|
48
|
+
# that some other RuboCop process was just about to read the file, when
|
49
|
+
# there's parallel execution and the cache is shared.
|
50
|
+
def self.cleanup(config_store, verbose, cache_root = nil)
|
51
|
+
return if inhibit_cleanup # OPTIMIZE: For faster testing
|
52
|
+
cache_root ||= cache_root(config_store)
|
53
|
+
return unless File.exist?(cache_root)
|
54
|
+
|
55
|
+
files, dirs = Find.find(cache_root).partition { |path| File.file?(path) }
|
56
|
+
if files.length > config_store.for('.')['AllCops']['MaxFilesInCache'] &&
|
57
|
+
files.length > 1
|
58
|
+
# Add 1 to half the number of files, so that we remove the file if
|
59
|
+
# there's only 1 left.
|
60
|
+
remove_count = 1 + files.length / 2
|
61
|
+
if verbose
|
62
|
+
puts "Removing the #{remove_count} oldest files from #{cache_root}"
|
63
|
+
end
|
64
|
+
sorted = files.sort_by { |path| File.mtime(path) }
|
65
|
+
begin
|
66
|
+
sorted[0, remove_count].each { |path| File.delete(path) }
|
67
|
+
dirs.each { |dir| Dir.rmdir(dir) if Dir["#{dir}/*"].empty? }
|
68
|
+
rescue Errno::ENOENT
|
69
|
+
# This can happen if parallel RuboCop invocations try to remove the
|
70
|
+
# same files. No problem.
|
71
|
+
puts $ERROR_INFO if verbose
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def self.cache_root(config_store)
|
79
|
+
root = config_store.for('.')['AllCops']['CacheRootDirectory']
|
80
|
+
root = Dir.tmpdir if root == '/tmp'
|
81
|
+
File.join(root, 'rubocop_cache')
|
82
|
+
end
|
83
|
+
|
84
|
+
def file_checksum(file, config_store)
|
85
|
+
Digest::MD5.hexdigest(Dir.pwd + file + IO.read(file) +
|
86
|
+
config_store.for(file).to_s)
|
87
|
+
rescue Errno::ENOENT
|
88
|
+
# Spurious files that come and go should not cause a crash, at least not
|
89
|
+
# here.
|
90
|
+
'_'
|
91
|
+
end
|
92
|
+
|
93
|
+
class << self
|
94
|
+
attr_accessor :source_checksum, :inhibit_cleanup
|
95
|
+
end
|
96
|
+
|
97
|
+
# The checksum of the rubocop program running the inspection.
|
98
|
+
def rubocop_checksum
|
99
|
+
ResultCache.source_checksum ||=
|
100
|
+
begin
|
101
|
+
lib_root = File.join(File.dirname(__FILE__), '..')
|
102
|
+
bin_root = File.join(lib_root, '..', 'bin')
|
103
|
+
source = Find.find(lib_root, bin_root).sort.map do |path|
|
104
|
+
IO.read(path) if File.file?(path)
|
105
|
+
end
|
106
|
+
Digest::MD5.hexdigest(source.join)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
NON_CHANGING = [:color, :format, :formatters, :out, :debug, :fail_level,
|
111
|
+
:cache, :fail_fast]
|
112
|
+
|
113
|
+
# Return the options given at invocation, minus the ones that have no
|
114
|
+
# effect on which offenses and disabled line ranges are found, and thus
|
115
|
+
# don't affect caching.
|
116
|
+
def relevant_options(options)
|
117
|
+
options = options.reject { |key, _| NON_CHANGING.include?(key) }
|
118
|
+
options.to_s.gsub(/[^a-z]+/i, '_')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|