rubocop 0.4.0 → 0.8.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 +7 -0
- data/.gitignore +50 -0
- data/.rubocop.yml +5 -127
- data/.travis.yml +7 -1
- data/CHANGELOG.md +157 -0
- data/CONTRIBUTING.md +13 -6
- data/Gemfile +3 -8
- data/README.md +160 -9
- data/Rakefile +3 -17
- data/bin/rubocop +16 -10
- data/config/default.yml +46 -0
- data/config/disabled.yml +5 -0
- data/config/enabled.yml +322 -0
- data/lib/rubocop/cli.rb +248 -93
- data/lib/rubocop/config.rb +205 -0
- data/lib/rubocop/config_store.rb +37 -0
- data/lib/rubocop/cop/access_control.rb +41 -0
- data/lib/rubocop/cop/alias.rb +17 -0
- data/lib/rubocop/cop/align_parameters.rb +20 -95
- data/lib/rubocop/cop/and_or.rb +26 -0
- data/lib/rubocop/cop/ascii_comments.rb +13 -0
- data/lib/rubocop/cop/ascii_identifiers.rb +19 -0
- data/lib/rubocop/cop/avoid_class_vars.rb +15 -0
- data/lib/rubocop/cop/avoid_for.rb +17 -0
- data/lib/rubocop/cop/avoid_global_vars.rb +61 -0
- data/lib/rubocop/cop/avoid_perl_backrefs.rb +17 -0
- data/lib/rubocop/cop/avoid_perlisms.rb +47 -0
- data/lib/rubocop/cop/block_comments.rb +15 -0
- data/lib/rubocop/cop/blocks.rb +11 -47
- data/lib/rubocop/cop/case_indentation.rb +22 -0
- data/lib/rubocop/cop/class_and_module_camel_case.rb +20 -11
- data/lib/rubocop/cop/class_methods.rb +15 -0
- data/lib/rubocop/cop/collection_methods.rb +16 -16
- data/lib/rubocop/cop/colon_method_call.rb +20 -0
- data/lib/rubocop/cop/constant_name.rb +24 -0
- data/lib/rubocop/cop/cop.rb +34 -47
- data/lib/rubocop/cop/def_parentheses.rb +43 -35
- data/lib/rubocop/cop/empty_line_between_defs.rb +22 -0
- data/lib/rubocop/cop/empty_lines.rb +21 -13
- data/lib/rubocop/cop/empty_literal.rb +47 -0
- data/lib/rubocop/cop/encoding.rb +3 -3
- data/lib/rubocop/cop/end_of_line.rb +3 -3
- data/lib/rubocop/cop/ensure_return.rb +19 -0
- data/lib/rubocop/cop/eval.rb +19 -0
- data/lib/rubocop/cop/favor_join.rb +22 -0
- data/lib/rubocop/cop/favor_modifier.rb +38 -48
- data/lib/rubocop/cop/favor_percent_r.rb +19 -0
- data/lib/rubocop/cop/favor_sprintf.rb +21 -0
- data/lib/rubocop/cop/favor_unless_over_negated_if.rb +19 -17
- data/lib/rubocop/cop/handle_exceptions.rb +17 -0
- data/lib/rubocop/cop/hash_syntax.rb +29 -14
- data/lib/rubocop/cop/if_then_else.rb +32 -29
- data/lib/rubocop/cop/leading_comment_space.rb +17 -0
- data/lib/rubocop/cop/line_continuation.rb +15 -0
- data/lib/rubocop/cop/line_length.rb +4 -4
- data/lib/rubocop/cop/loop.rb +33 -0
- data/lib/rubocop/cop/method_and_variable_snake_case.rb +41 -17
- data/lib/rubocop/cop/method_length.rb +52 -0
- data/lib/rubocop/cop/new_lambda_literal.rb +8 -6
- data/lib/rubocop/cop/not.rb +21 -0
- data/lib/rubocop/cop/numeric_literals.rb +9 -7
- data/lib/rubocop/cop/offence.rb +12 -1
- data/lib/rubocop/cop/op_method.rb +26 -0
- data/lib/rubocop/cop/parameter_lists.rb +12 -6
- data/lib/rubocop/cop/parentheses_around_condition.rb +11 -11
- data/lib/rubocop/cop/percent_r.rb +19 -0
- data/lib/rubocop/cop/reduce_arguments.rb +29 -0
- data/lib/rubocop/cop/rescue_exception.rb +26 -0
- data/lib/rubocop/cop/rescue_modifier.rb +17 -0
- data/lib/rubocop/cop/semicolon.rb +31 -0
- data/lib/rubocop/cop/single_line_methods.rb +44 -0
- data/lib/rubocop/cop/space_after_comma_etc.rb +30 -10
- data/lib/rubocop/cop/space_after_control_keyword.rb +29 -0
- data/lib/rubocop/cop/string_literals.rb +9 -23
- data/lib/rubocop/cop/surrounding_space.rb +223 -83
- data/lib/rubocop/cop/symbol_array.rb +31 -0
- data/lib/rubocop/cop/symbol_name.rb +23 -0
- data/lib/rubocop/cop/syntax.rb +35 -5
- data/lib/rubocop/cop/tab.rb +3 -3
- data/lib/rubocop/cop/ternary_operator.rb +26 -24
- data/lib/rubocop/cop/trailing_whitespace.rb +3 -5
- data/lib/rubocop/cop/trivial_accessors.rb +26 -0
- data/lib/rubocop/cop/unless_else.rb +11 -7
- data/lib/rubocop/cop/util.rb +26 -0
- data/lib/rubocop/cop/variable_interpolation.rb +29 -0
- data/lib/rubocop/cop/when_then.rb +6 -14
- data/lib/rubocop/cop/word_array.rb +37 -0
- data/lib/rubocop/report/emacs_style.rb +2 -2
- data/lib/rubocop/report/plain_text.rb +1 -1
- data/lib/rubocop/version.rb +3 -1
- data/lib/rubocop.rb +48 -8
- data/rubocop.gemspec +32 -151
- data/spec/project_spec.rb +27 -0
- data/spec/rubocop/cli_spec.rb +573 -200
- data/spec/rubocop/config_spec.rb +409 -0
- data/spec/rubocop/config_store_spec.rb +66 -0
- data/spec/rubocop/cops/access_control_spec.rb +129 -0
- data/spec/rubocop/cops/alias_spec.rb +39 -0
- data/spec/rubocop/cops/align_parameters_spec.rb +66 -70
- data/spec/rubocop/cops/and_or_spec.rb +37 -0
- data/spec/rubocop/cops/ascii_comments_spec.rb +26 -0
- data/spec/rubocop/cops/ascii_identifiers_spec.rb +26 -0
- data/spec/rubocop/cops/avoid_class_vars_spec.rb +25 -0
- data/spec/rubocop/cops/avoid_for_spec.rb +35 -0
- data/spec/rubocop/cops/avoid_global_vars_spec.rb +32 -0
- data/spec/rubocop/cops/avoid_perl_backrefs_spec.rb +18 -0
- data/spec/rubocop/cops/avoid_perlisms_spec.rb +44 -0
- data/spec/rubocop/cops/block_comments_spec.rb +25 -0
- data/spec/rubocop/cops/blocks_spec.rb +33 -0
- data/spec/rubocop/cops/{indentation_spec.rb → case_indentation_spec.rb} +7 -7
- data/spec/rubocop/cops/class_and_module_camel_case_spec.rb +15 -5
- data/spec/rubocop/cops/class_methods_spec.rb +49 -0
- data/spec/rubocop/cops/collection_methods_spec.rb +9 -4
- data/spec/rubocop/cops/colon_method_call_spec.rb +53 -0
- data/spec/rubocop/cops/constant_name_spec.rb +42 -0
- data/spec/rubocop/cops/def_with_parentheses_spec.rb +13 -8
- data/spec/rubocop/cops/def_without_parentheses_spec.rb +11 -5
- data/spec/rubocop/cops/empty_line_between_defs_spec.rb +83 -0
- data/spec/rubocop/cops/empty_lines_spec.rb +14 -59
- data/spec/rubocop/cops/empty_literal_spec.rb +90 -0
- data/spec/rubocop/cops/encoding_spec.rb +11 -11
- data/spec/rubocop/cops/end_of_line_spec.rb +2 -2
- data/spec/rubocop/cops/ensure_return_spec.rb +35 -0
- data/spec/rubocop/cops/eval_spec.rb +39 -0
- data/spec/rubocop/cops/favor_join_spec.rb +35 -0
- data/spec/rubocop/cops/favor_modifier_spec.rb +16 -14
- data/spec/rubocop/cops/favor_percent_r_spec.rb +29 -0
- data/spec/rubocop/cops/favor_sprintf_spec.rb +51 -0
- data/spec/rubocop/cops/favor_unless_over_negated_if_spec.rb +4 -4
- data/spec/rubocop/cops/favor_until_over_negated_while_spec.rb +3 -3
- data/spec/rubocop/cops/handle_exceptions_spec.rb +34 -0
- data/spec/rubocop/cops/hash_syntax_spec.rb +11 -6
- data/spec/rubocop/cops/if_with_semicolon_spec.rb +7 -1
- data/spec/rubocop/cops/leading_comment_space_spec.rb +54 -0
- data/spec/rubocop/cops/line_continuation_spec.rb +24 -0
- data/spec/rubocop/cops/line_length_spec.rb +3 -2
- data/spec/rubocop/cops/loop_spec.rb +31 -0
- data/spec/rubocop/cops/method_and_variable_snake_case_spec.rb +55 -9
- data/spec/rubocop/cops/method_length_spec.rb +147 -0
- data/spec/rubocop/cops/multiline_if_then_spec.rb +15 -15
- data/spec/rubocop/cops/new_lambda_literal_spec.rb +5 -6
- data/spec/rubocop/cops/not_spec.rb +31 -0
- data/spec/rubocop/cops/numeric_literals_spec.rb +13 -13
- data/spec/rubocop/cops/offence_spec.rb +13 -0
- data/spec/rubocop/cops/one_line_conditional_spec.rb +1 -1
- data/spec/rubocop/cops/op_method_spec.rb +78 -0
- data/spec/rubocop/cops/parameter_lists_spec.rb +7 -7
- data/spec/rubocop/cops/parentheses_around_condition_spec.rb +41 -44
- data/spec/rubocop/cops/percent_r_spec.rb +29 -0
- data/spec/rubocop/cops/reduce_arguments_spec.rb +57 -0
- data/spec/rubocop/cops/rescue_exception_spec.rb +125 -0
- data/spec/rubocop/cops/rescue_modifier_spec.rb +37 -0
- data/spec/rubocop/cops/semicolon_spec.rb +88 -0
- data/spec/rubocop/cops/single_line_methods_spec.rb +50 -0
- data/spec/rubocop/cops/space_after_colon_spec.rb +3 -3
- data/spec/rubocop/cops/space_after_comma_spec.rb +14 -2
- data/spec/rubocop/cops/space_after_control_keyword_spec.rb +67 -0
- data/spec/rubocop/cops/space_after_semicolon_spec.rb +6 -1
- data/spec/rubocop/cops/space_around_braces_spec.rb +18 -3
- data/spec/rubocop/cops/space_around_equals_in_default_parameter_spec.rb +12 -2
- data/spec/rubocop/cops/space_around_operators_spec.rb +88 -26
- data/spec/rubocop/cops/space_inside_brackets_spec.rb +13 -7
- data/spec/rubocop/cops/space_inside_hash_literal_braces_spec.rb +79 -0
- data/spec/rubocop/cops/space_inside_parens_spec.rb +7 -3
- data/spec/rubocop/cops/string_literals_spec.rb +21 -6
- data/spec/rubocop/cops/symbol_array_spec.rb +41 -0
- data/spec/rubocop/cops/symbol_name_spec.rb +119 -0
- data/spec/rubocop/cops/syntax_spec.rb +28 -5
- data/spec/rubocop/cops/tab_spec.rb +2 -2
- data/spec/rubocop/cops/ternary_operator_spec.rb +13 -17
- data/spec/rubocop/cops/trailing_whitespace_spec.rb +3 -3
- data/spec/rubocop/cops/trivial_accessors_spec.rb +329 -0
- data/spec/rubocop/cops/unless_else_spec.rb +8 -8
- data/spec/rubocop/cops/variable_interpolation_spec.rb +49 -0
- data/spec/rubocop/cops/when_then_spec.rb +14 -14
- data/spec/rubocop/cops/word_array_spec.rb +47 -0
- data/spec/spec_helper.rb +30 -9
- data/spec/support/file_helper.rb +21 -0
- data/spec/support/isolated_environment.rb +27 -0
- metadata +235 -76
- data/.document +0 -5
- data/Gemfile.lock +0 -41
- data/VERSION +0 -1
- data/lib/rubocop/cop/ampersands_pipes_vs_and_or.rb +0 -25
- data/lib/rubocop/cop/grammar.rb +0 -135
- data/lib/rubocop/cop/indentation.rb +0 -44
- data/spec/rubocop/cops/ampersands_pipes_vs_and_or_spec.rb +0 -57
- data/spec/rubocop/cops/grammar_spec.rb +0 -71
- data/spec/rubocop/cops/multiline_blocks_spec.rb +0 -24
- data/spec/rubocop/cops/single_line_blocks_spec.rb +0 -22
data/lib/rubocop/cli.rb
CHANGED
@@ -1,159 +1,314 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
2
|
+
require 'pathname'
|
3
3
|
require 'optparse'
|
4
|
-
require 'yaml'
|
5
|
-
require_relative 'cop/grammar'
|
6
4
|
|
7
5
|
module Rubocop
|
8
6
|
# The CLI is a class responsible of handling all the command line interface
|
9
7
|
# logic.
|
10
8
|
class CLI
|
9
|
+
# If set true while running,
|
10
|
+
# RuboCop will abort processing and exit gracefully.
|
11
|
+
attr_accessor :wants_to_quit
|
12
|
+
attr_accessor :options
|
13
|
+
|
14
|
+
alias_method :wants_to_quit?, :wants_to_quit
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@cops = Cop::Cop.all
|
18
|
+
@processed_file_count = 0
|
19
|
+
@total_offences = 0
|
20
|
+
@errors = []
|
21
|
+
@options = { mode: :default }
|
22
|
+
ConfigStore.prepare
|
23
|
+
end
|
24
|
+
|
11
25
|
# Entry point for the application logic. Here we
|
12
26
|
# do the command line arguments processing and inspect
|
13
27
|
# the target files
|
14
28
|
# @return [Fixnum] UNIX exit code
|
15
29
|
def run(args = ARGV)
|
16
|
-
|
30
|
+
trap_interrupt
|
31
|
+
|
32
|
+
parse_options(args)
|
33
|
+
|
34
|
+
begin
|
35
|
+
validate_only_option if @options[:only]
|
36
|
+
rescue ArgumentError => e
|
37
|
+
puts e.message
|
38
|
+
return 1
|
39
|
+
end
|
40
|
+
|
41
|
+
target_files(args).each do |file|
|
42
|
+
break if wants_to_quit?
|
43
|
+
|
44
|
+
config = ConfigStore.for(file)
|
45
|
+
report = Report.create(file, @options[:mode])
|
46
|
+
|
47
|
+
puts "Scanning #{file}" if @options[:debug]
|
48
|
+
|
49
|
+
syntax_cop = Rubocop::Cop::Syntax.new
|
50
|
+
syntax_cop.debug = @options[:debug]
|
51
|
+
syntax_cop.inspect_file(file)
|
52
|
+
|
53
|
+
if syntax_cop.offences.map(&:severity).include?(:error)
|
54
|
+
# In case of a syntax error we just report that error and do
|
55
|
+
# no more checking in the file.
|
56
|
+
report << syntax_cop
|
57
|
+
@total_offences += syntax_cop.offences.count
|
58
|
+
else
|
59
|
+
inspect_file(file, config, report)
|
60
|
+
end
|
61
|
+
|
62
|
+
@processed_file_count += 1
|
63
|
+
report.display unless report.empty?
|
64
|
+
end
|
65
|
+
|
66
|
+
unless @options[:silent]
|
67
|
+
display_summary(@processed_file_count, @total_offences, @errors)
|
68
|
+
end
|
69
|
+
|
70
|
+
(@total_offences == 0) && !wants_to_quit ? 0 : 1
|
71
|
+
end
|
72
|
+
|
73
|
+
def validate_only_option
|
74
|
+
if @cops.none? { |c| c.cop_name == @options[:only] }
|
75
|
+
fail ArgumentError, "Unrecognized cop name: #{@options[:only]}."
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def inspect_file(file, config, report)
|
80
|
+
begin
|
81
|
+
ast, comments, tokens, source = CLI.parse(file) do |source_buffer|
|
82
|
+
source_buffer.read
|
83
|
+
end
|
84
|
+
rescue Parser::SyntaxError, Encoding::UndefinedConversionError,
|
85
|
+
ArgumentError => e
|
86
|
+
handle_error(e, "An error occurred while parsing #{file}.".color(:red))
|
87
|
+
return
|
88
|
+
end
|
89
|
+
|
90
|
+
disabled_lines = disabled_lines_in(source)
|
17
91
|
|
92
|
+
@cops.each do |cop_class|
|
93
|
+
cop_name = cop_class.cop_name
|
94
|
+
if config.cop_enabled?(cop_name)
|
95
|
+
cop = setup_cop(cop_class,
|
96
|
+
config.for_cop(cop_name),
|
97
|
+
disabled_lines)
|
98
|
+
if !@options[:only] || @options[:only] == cop_name
|
99
|
+
begin
|
100
|
+
cop.inspect(source, tokens, ast, comments)
|
101
|
+
rescue => e
|
102
|
+
handle_error(e,
|
103
|
+
"An error occurred while #{cop.name}".color(:red) +
|
104
|
+
" cop was inspecting #{file}.".color(:red))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
@total_offences += cop.offences.count
|
108
|
+
report << cop if cop.has_report?
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def setup_cop(cop_class, cop_config, disabled_lines)
|
114
|
+
cop_class.config = cop_config
|
115
|
+
cop = cop_class.new
|
116
|
+
cop.debug = @options[:debug]
|
117
|
+
cop.disabled_lines = disabled_lines[cop_class.cop_name]
|
118
|
+
cop
|
119
|
+
end
|
120
|
+
|
121
|
+
def handle_error(e, message)
|
122
|
+
@errors << message
|
123
|
+
warn message
|
124
|
+
if @options[:debug]
|
125
|
+
puts e.message, e.backtrace
|
126
|
+
else
|
127
|
+
warn 'To see the complete backtrace run rubocop -d.'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def parse_options(args)
|
18
132
|
OptionParser.new do |opts|
|
19
133
|
opts.banner = 'Usage: rubocop [options] [file1, file2, ...]'
|
20
134
|
|
21
|
-
opts.on('-d', '--
|
22
|
-
|
135
|
+
opts.on('-d', '--debug', 'Display debug info') do |d|
|
136
|
+
@options[:debug] = d
|
23
137
|
end
|
24
138
|
opts.on('-e', '--emacs', 'Emacs style output') do
|
25
|
-
|
139
|
+
@options[:mode] = :emacs_style
|
26
140
|
end
|
27
141
|
opts.on('-c FILE', '--config FILE', 'Configuration file') do |f|
|
28
|
-
|
142
|
+
@options[:config] = f
|
143
|
+
ConfigStore.set_options_config(@options[:config])
|
144
|
+
end
|
145
|
+
opts.on('--only COP', 'Run just one cop') do |s|
|
146
|
+
@options[:only] = s
|
29
147
|
end
|
30
148
|
opts.on('-s', '--silent', 'Silence summary') do |s|
|
31
|
-
|
149
|
+
@options[:silent] = s
|
150
|
+
end
|
151
|
+
opts.on('-n', '--no-color', 'Disable color output') do |s|
|
152
|
+
Sickill::Rainbow.enabled = false
|
32
153
|
end
|
33
154
|
opts.on('-v', '--version', 'Display version') do
|
34
|
-
puts Rubocop::
|
155
|
+
puts Rubocop::Version::STRING
|
35
156
|
exit(0)
|
36
157
|
end
|
37
158
|
end.parse!(args)
|
159
|
+
end
|
38
160
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
161
|
+
def trap_interrupt
|
162
|
+
Signal.trap('INT') do
|
163
|
+
exit!(1) if wants_to_quit?
|
164
|
+
self.wants_to_quit = true
|
165
|
+
$stderr.puts
|
166
|
+
$stderr.puts 'Exiting... Interrupt again to exit immediately.'
|
167
|
+
end
|
168
|
+
end
|
44
169
|
|
45
|
-
|
46
|
-
|
170
|
+
def display_summary(num_files, total_offences, errors)
|
171
|
+
plural = num_files == 0 || num_files > 1 ? 's' : ''
|
172
|
+
print "\n#{num_files} file#{plural} inspected, "
|
173
|
+
offences_string = if total_offences.zero?
|
174
|
+
'no offences'
|
175
|
+
elsif total_offences == 1
|
176
|
+
'1 offence'
|
177
|
+
else
|
178
|
+
"#{total_offences} offences"
|
179
|
+
end
|
180
|
+
puts "#{offences_string} detected"
|
181
|
+
.color(total_offences.zero? ? :green : :red)
|
47
182
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
183
|
+
if errors.count > 0
|
184
|
+
plural = errors.count > 1 ? 's' : ''
|
185
|
+
puts "\n#{errors.count} error#{plural} occurred:".color(:red)
|
186
|
+
errors.each { |error| puts error }
|
187
|
+
puts 'Errors are usually caused by RuboCop bugs.'
|
188
|
+
puts 'Please, report your problems to RuboCop\'s issue tracker.'
|
189
|
+
end
|
190
|
+
end
|
54
191
|
|
55
|
-
|
192
|
+
def disabled_lines_in(source)
|
193
|
+
disabled_lines = Hash.new([])
|
194
|
+
disabled_section = {}
|
195
|
+
regexp = '# rubocop : (%s)\b ((?:\w+,? )+)'.gsub(' ', '\s*')
|
196
|
+
section_regexp = '^\s*' + sprintf(regexp, '(?:dis|en)able')
|
197
|
+
single_line_regexp = '\S.*' + sprintf(regexp, 'disable')
|
56
198
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
total_offences += cop.offences.count
|
64
|
-
report << cop if cop.has_report?
|
199
|
+
source.each_with_index do |line, ix|
|
200
|
+
each_mentioned_cop(/#{section_regexp}/, line) do |cop_name, kind|
|
201
|
+
disabled_section[cop_name] = (kind == 'disable')
|
202
|
+
end
|
203
|
+
disabled_section.keys.each do |cop_name|
|
204
|
+
disabled_lines[cop_name] += [ix + 1] if disabled_section[cop_name]
|
65
205
|
end
|
66
206
|
|
67
|
-
|
207
|
+
each_mentioned_cop(/#{single_line_regexp}/, line) do |cop_name, kind|
|
208
|
+
disabled_lines[cop_name] += [ix + 1] if kind == 'disable'
|
209
|
+
end
|
68
210
|
end
|
211
|
+
disabled_lines
|
212
|
+
end
|
69
213
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
214
|
+
def each_mentioned_cop(regexp, line)
|
215
|
+
match = line.match(regexp)
|
216
|
+
if match
|
217
|
+
kind, cops = match.captures
|
218
|
+
cops = Cop::Cop.all.map(&:cop_name).join(',') if cops.include?('all')
|
219
|
+
cops.split(/,\s*/).each { |cop_name| yield cop_name, kind }
|
74
220
|
end
|
75
|
-
|
76
|
-
return total_offences == 0 ? 0 : 1
|
77
221
|
end
|
78
222
|
|
79
|
-
def
|
80
|
-
|
81
|
-
# UTF-16 works better in this algorithm but is not supported in 1.9.2.
|
82
|
-
temporary_encoding = (RUBY_VERSION == '1.9.2') ? 'UTF-8' : 'UTF-16'
|
83
|
-
line.encode!(temporary_encoding, enc, invalid: :replace, replace: '')
|
84
|
-
line.encode!(enc, temporary_encoding)
|
85
|
-
end
|
223
|
+
def self.parse(file)
|
224
|
+
parser = Parser::CurrentRuby.new
|
86
225
|
|
87
|
-
|
88
|
-
|
89
|
-
sexp = Ripper.sexp(source.join("\n"))
|
90
|
-
Cop::Position.make_position_objects(sexp)
|
91
|
-
correlations = Cop::Grammar.new(tokens).correlate(sexp)
|
92
|
-
[tokens, sexp, correlations]
|
93
|
-
end
|
226
|
+
parser.diagnostics.all_errors_are_fatal = true
|
227
|
+
parser.diagnostics.ignore_warnings = true
|
94
228
|
|
95
|
-
|
96
|
-
|
97
|
-
# directory where the inspected file is. If no .rubocop.yml is
|
98
|
-
# found there, the user's home directory is checked.
|
99
|
-
def config_from_dotfile(target_file_dir)
|
100
|
-
return unless target_file_dir
|
101
|
-
# @configs is a cache that maps directories to
|
102
|
-
# configurations. We search for .rubocop.yml only if we haven't
|
103
|
-
# already found it for the given directory.
|
104
|
-
unless @configs[target_file_dir]
|
105
|
-
dir = target_file_dir
|
106
|
-
while dir != '/'
|
107
|
-
path = File.join(dir, '.rubocop.yml')
|
108
|
-
if File.exist?(path)
|
109
|
-
@configs[target_file_dir] = YAML.load_file(path)
|
110
|
-
break
|
111
|
-
end
|
112
|
-
dir = File.expand_path('..', dir)
|
113
|
-
end
|
114
|
-
path = File.join(Dir.home, '.rubocop.yml')
|
115
|
-
@configs[target_file_dir] = YAML.load_file(path) if File.exist?(path)
|
229
|
+
parser.diagnostics.consumer = lambda do |diagnostic|
|
230
|
+
$stderr.puts(diagnostic.render)
|
116
231
|
end
|
117
|
-
@configs[target_file_dir]
|
118
|
-
end
|
119
232
|
|
120
|
-
|
121
|
-
|
233
|
+
source_buffer = Parser::Source::Buffer.new(file, 1)
|
234
|
+
yield source_buffer
|
122
235
|
|
123
|
-
|
124
|
-
cop_config = config[cop_klass.name.split('::').last] if config
|
125
|
-
cops_on_duty << cop_klass if cop_config.nil? || cop_config['Enabled']
|
126
|
-
end
|
236
|
+
ast, comments, tokens = parser.tokenize(source_buffer)
|
127
237
|
|
128
|
-
|
129
|
-
|
238
|
+
tokens = tokens.map do |t|
|
239
|
+
type, details = *t
|
240
|
+
text, range = *details
|
241
|
+
Rubocop::Cop::Token.new(range, type, text)
|
242
|
+
end
|
130
243
|
|
131
|
-
|
132
|
-
puts '== Reporting for duty =='
|
133
|
-
cops.each { |c| puts ' * '.yellow + c.to_s.green }
|
134
|
-
puts '========================'
|
244
|
+
[ast, comments, tokens, source_buffer.source.split($RS)]
|
135
245
|
end
|
136
246
|
|
137
247
|
# Generate a list of target files by expanding globing patterns
|
138
248
|
# (if any). If args is empty recursively finds all Ruby source
|
139
|
-
# files
|
249
|
+
# files under the current directory
|
140
250
|
# @return [Array] array of filenames
|
141
251
|
def target_files(args)
|
142
|
-
return
|
252
|
+
return ruby_files if args.empty?
|
143
253
|
|
144
254
|
files = []
|
145
255
|
|
146
256
|
args.each do |target|
|
147
257
|
if File.directory?(target)
|
148
|
-
files
|
258
|
+
files += ruby_files(target.chomp(File::SEPARATOR))
|
149
259
|
elsif target =~ /\*/
|
150
|
-
files
|
260
|
+
files += Dir[target]
|
151
261
|
else
|
152
262
|
files << target
|
153
263
|
end
|
154
264
|
end
|
155
265
|
|
156
|
-
files.
|
266
|
+
files.uniq
|
267
|
+
end
|
268
|
+
|
269
|
+
# Finds all Ruby source files under the current or other supplied
|
270
|
+
# directory. A Ruby source file is defined as a file with the `.rb`
|
271
|
+
# extension or a file with no extension that has a ruby shebang line
|
272
|
+
# as its first line.
|
273
|
+
# It is possible to specify includes and excludes using the config file,
|
274
|
+
# so you can include other Ruby files like Rakefiles and gemspecs.
|
275
|
+
# @param root Root directory under which to search for ruby source files
|
276
|
+
# @return [Array] Array of filenames
|
277
|
+
def ruby_files(root = Dir.pwd)
|
278
|
+
files = Dir["#{root}/**/*"].reject { |file| FileTest.directory? file }
|
279
|
+
|
280
|
+
rb = []
|
281
|
+
|
282
|
+
rb += files.select { |file| File.extname(file) == '.rb' }
|
283
|
+
rb += files.select do |file|
|
284
|
+
if File.extname(file) == ''
|
285
|
+
begin
|
286
|
+
File.open(file) { |f| f.readline } =~ /#!.*ruby/
|
287
|
+
rescue EOFError, ArgumentError => e
|
288
|
+
log_error(e, "Unprocessable file #{file.inspect}: ")
|
289
|
+
false
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
rb += files.select do |file|
|
295
|
+
config = ConfigStore.for(file)
|
296
|
+
config.file_to_include?(file)
|
297
|
+
end
|
298
|
+
|
299
|
+
rb.reject do |file|
|
300
|
+
config = ConfigStore.for(file)
|
301
|
+
config.file_to_exclude?(file)
|
302
|
+
end.uniq
|
303
|
+
end
|
304
|
+
|
305
|
+
private
|
306
|
+
|
307
|
+
def log_error(e, msg = '')
|
308
|
+
if @options[:debug]
|
309
|
+
error_message = "#{e.class}, #{e.message}"
|
310
|
+
STDERR.puts "#{msg}\t#{error_message}"
|
311
|
+
end
|
157
312
|
end
|
158
313
|
end
|
159
314
|
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'delegate'
|
4
|
+
require 'yaml'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
module Rubocop
|
8
|
+
class Config < DelegateClass(Hash)
|
9
|
+
class ValidationError < StandardError; end
|
10
|
+
|
11
|
+
DOTFILE = '.rubocop.yml'
|
12
|
+
RUBOCOP_HOME = File.realpath(File.join(File.dirname(__FILE__), '..', '..'))
|
13
|
+
DEFAULT_FILE = File.join(RUBOCOP_HOME, 'config', 'default.yml')
|
14
|
+
|
15
|
+
attr_reader :loaded_path
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def load_file(path)
|
19
|
+
hash = YAML.load_file(path)
|
20
|
+
|
21
|
+
base_configs(path, hash['inherit_from']).reverse.each do |base_config|
|
22
|
+
base_config.each do |key, value|
|
23
|
+
if value.is_a?(Hash)
|
24
|
+
hash[key] = hash.has_key?(key) ? merge(value, hash[key]) : value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
hash.delete('inherit_from')
|
30
|
+
config = new(hash, File.realpath(path))
|
31
|
+
config.warn_unless_valid
|
32
|
+
config
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return a recursive merge of two hashes. That is, a normal hash
|
36
|
+
# merge, with the addition that any value that is a hash, and
|
37
|
+
# occurs in both arguments, will also be merged. And so on.
|
38
|
+
def merge(base_hash, derived_hash)
|
39
|
+
result = {}
|
40
|
+
base_hash.each do |key, value|
|
41
|
+
result[key] = if derived_hash.has_key?(key)
|
42
|
+
if value.is_a?(Hash)
|
43
|
+
merge(value, derived_hash[key])
|
44
|
+
else
|
45
|
+
derived_hash[key]
|
46
|
+
end
|
47
|
+
else
|
48
|
+
base_hash[key]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
derived_hash.each do |key, value|
|
52
|
+
result[key] = value unless base_hash.has_key?(key)
|
53
|
+
end
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
def base_configs(path, inherit_from)
|
58
|
+
base_files = case inherit_from
|
59
|
+
when nil then []
|
60
|
+
when String then [inherit_from]
|
61
|
+
when Array then inherit_from
|
62
|
+
end
|
63
|
+
base_files.map do |f|
|
64
|
+
f = File.join(File.dirname(path), f) unless f.start_with?('/')
|
65
|
+
load_file(f)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the path of .rubocop.yml searching upwards in the
|
70
|
+
# directory structure starting at the given directory where the
|
71
|
+
# inspected file is. If no .rubocop.yml is found there, the
|
72
|
+
# user's home directory is checked. If there's no .rubocop.yml
|
73
|
+
# there either, the path to the default file is returned.
|
74
|
+
def configuration_file_for(target_dir)
|
75
|
+
possible_config_files = dirs_to_search(target_dir).map do |dir|
|
76
|
+
File.join(dir, DOTFILE)
|
77
|
+
end
|
78
|
+
|
79
|
+
found_file = possible_config_files.find do |config_file|
|
80
|
+
File.exist?(config_file)
|
81
|
+
end
|
82
|
+
found_file || DEFAULT_FILE
|
83
|
+
end
|
84
|
+
|
85
|
+
def configuration_from_file(config_file)
|
86
|
+
config = load_file(config_file)
|
87
|
+
merge_with_default(config, config_file)
|
88
|
+
end
|
89
|
+
|
90
|
+
def default_configuration
|
91
|
+
@default_configuration ||= load_file(DEFAULT_FILE)
|
92
|
+
end
|
93
|
+
|
94
|
+
def merge_with_default(config, config_file)
|
95
|
+
new(merge(default_configuration, config), config_file)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def dirs_to_search(target_dir)
|
101
|
+
dirs_to_search = []
|
102
|
+
target_dir_pathname = Pathname.new(File.expand_path(target_dir))
|
103
|
+
target_dir_pathname.ascend do |dir_pathname|
|
104
|
+
dirs_to_search << dir_pathname.to_s
|
105
|
+
end
|
106
|
+
dirs_to_search << Dir.home
|
107
|
+
dirs_to_search
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def initialize(hash = {}, loaded_path = nil)
|
112
|
+
@hash = hash
|
113
|
+
@loaded_path = loaded_path
|
114
|
+
super(@hash)
|
115
|
+
end
|
116
|
+
|
117
|
+
def for_cop(cop)
|
118
|
+
self[cop]
|
119
|
+
end
|
120
|
+
|
121
|
+
def cop_enabled?(cop)
|
122
|
+
self[cop].nil? || self[cop]['Enabled']
|
123
|
+
end
|
124
|
+
|
125
|
+
def warn_unless_valid
|
126
|
+
validate
|
127
|
+
rescue Config::ValidationError => e
|
128
|
+
puts "Warning: #{e.message}".color(:red)
|
129
|
+
end
|
130
|
+
|
131
|
+
# TODO: This should be a private method
|
132
|
+
def validate
|
133
|
+
# Don't validate RuboCop's own files. Avoids inifinite recursion.
|
134
|
+
return if @loaded_path.start_with?(RUBOCOP_HOME)
|
135
|
+
|
136
|
+
default_config = self.class.default_configuration
|
137
|
+
|
138
|
+
valid_cop_names, invalid_cop_names = @hash.keys.partition do |key|
|
139
|
+
default_config.has_key?(key)
|
140
|
+
end
|
141
|
+
|
142
|
+
invalid_cop_names.each do |name|
|
143
|
+
fail ValidationError,
|
144
|
+
"unrecognized cop #{name} found in #{loaded_path || self}"
|
145
|
+
end
|
146
|
+
|
147
|
+
valid_cop_names.each do |name|
|
148
|
+
@hash[name].each_key do |param|
|
149
|
+
unless default_config[name].has_key?(param)
|
150
|
+
fail ValidationError,
|
151
|
+
"unrecognized parameter #{name}:#{param} found " +
|
152
|
+
"in #{loaded_path || self}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def file_to_include?(file)
|
159
|
+
relative_file_path = relative_path_to_loaded_dir(file)
|
160
|
+
patterns_to_include.any? do |pattern|
|
161
|
+
match_path?(pattern, relative_file_path)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def file_to_exclude?(file)
|
166
|
+
relative_file_path = relative_path_to_loaded_dir(file)
|
167
|
+
patterns_to_exclude.any? do |pattern|
|
168
|
+
match_path?(pattern, relative_file_path)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def patterns_to_include
|
173
|
+
@hash['AllCops']['Includes']
|
174
|
+
end
|
175
|
+
|
176
|
+
def patterns_to_exclude
|
177
|
+
@hash['AllCops']['Excludes']
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
def relative_path_to_loaded_dir(file)
|
183
|
+
return file unless loaded_path
|
184
|
+
file_pathname = Pathname.new(File.expand_path(file))
|
185
|
+
file_pathname.relative_path_from(loaded_dir_pathname).to_s
|
186
|
+
end
|
187
|
+
|
188
|
+
def loaded_dir_pathname
|
189
|
+
return nil unless loaded_path
|
190
|
+
@loaded_dir ||= begin
|
191
|
+
loaded_dir = File.expand_path(File.dirname(loaded_path))
|
192
|
+
Pathname.new(loaded_dir)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def match_path?(pattern, path)
|
197
|
+
case pattern
|
198
|
+
when String
|
199
|
+
File.basename(path) == pattern || File.fnmatch(pattern, path)
|
200
|
+
when Regexp
|
201
|
+
path =~ pattern
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Rubocop
|
4
|
+
module ConfigStore
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def prepare
|
8
|
+
# @options_config stores a config that is specified in the command line.
|
9
|
+
# This takes precedence over configs located in any directories
|
10
|
+
@options_config = nil
|
11
|
+
|
12
|
+
# @path_cache maps directories to configuration paths. We search
|
13
|
+
# for .rubocop.yml only if we haven't already found it for the
|
14
|
+
# given directory.
|
15
|
+
@path_cache = {}
|
16
|
+
|
17
|
+
# @object_cache maps configuration file paths to
|
18
|
+
# configuration objects so we only need to load them once.
|
19
|
+
@object_cache = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_options_config(options_config)
|
23
|
+
loaded_config = Config.load_file(options_config)
|
24
|
+
@options_config = Config.merge_with_default(loaded_config,
|
25
|
+
options_config)
|
26
|
+
end
|
27
|
+
|
28
|
+
def for(file)
|
29
|
+
return @options_config if @options_config
|
30
|
+
|
31
|
+
dir = File.dirname(file)
|
32
|
+
@path_cache[dir] ||= Config.configuration_file_for(dir)
|
33
|
+
path = @path_cache[dir]
|
34
|
+
@object_cache[path] ||= Config.configuration_from_file(path)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Rubocop
|
4
|
+
module Cop
|
5
|
+
class AccessControl < Cop
|
6
|
+
INDENT_MSG = 'Indent private and protected as deep as method defs.'
|
7
|
+
BLANK_MSG = 'Keep a blank line before and after private/protected.'
|
8
|
+
|
9
|
+
PRIVATE_NODE = s(:send, nil, :private)
|
10
|
+
PROTECTED_NODE = s(:send, nil, :protected)
|
11
|
+
|
12
|
+
def inspect(source, tokens, ast, comments)
|
13
|
+
on_node([:class, :module, :sclass], ast) do |class_node|
|
14
|
+
class_start_col = class_node.loc.expression.column
|
15
|
+
|
16
|
+
# we'll have to walk all class children nodes
|
17
|
+
# except other class/module nodes
|
18
|
+
class_node.children.compact.each do |node|
|
19
|
+
on_node(:send, node, [:class, :module, :sclass]) do |send_node|
|
20
|
+
if [PRIVATE_NODE, PROTECTED_NODE].include?(send_node)
|
21
|
+
send_start_col = send_node.loc.expression.column
|
22
|
+
|
23
|
+
if send_start_col - 2 != class_start_col
|
24
|
+
add_offence(:convention,
|
25
|
+
send_node.loc.line,
|
26
|
+
INDENT_MSG)
|
27
|
+
end
|
28
|
+
|
29
|
+
send_line = send_node.loc.line
|
30
|
+
|
31
|
+
unless source[send_line].empty? && source[send_line - 2].empty?
|
32
|
+
add_offence(:convention, send_line, BLANK_MSG)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|