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.

Files changed (190) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +50 -0
  3. data/.rubocop.yml +5 -127
  4. data/.travis.yml +7 -1
  5. data/CHANGELOG.md +157 -0
  6. data/CONTRIBUTING.md +13 -6
  7. data/Gemfile +3 -8
  8. data/README.md +160 -9
  9. data/Rakefile +3 -17
  10. data/bin/rubocop +16 -10
  11. data/config/default.yml +46 -0
  12. data/config/disabled.yml +5 -0
  13. data/config/enabled.yml +322 -0
  14. data/lib/rubocop/cli.rb +248 -93
  15. data/lib/rubocop/config.rb +205 -0
  16. data/lib/rubocop/config_store.rb +37 -0
  17. data/lib/rubocop/cop/access_control.rb +41 -0
  18. data/lib/rubocop/cop/alias.rb +17 -0
  19. data/lib/rubocop/cop/align_parameters.rb +20 -95
  20. data/lib/rubocop/cop/and_or.rb +26 -0
  21. data/lib/rubocop/cop/ascii_comments.rb +13 -0
  22. data/lib/rubocop/cop/ascii_identifiers.rb +19 -0
  23. data/lib/rubocop/cop/avoid_class_vars.rb +15 -0
  24. data/lib/rubocop/cop/avoid_for.rb +17 -0
  25. data/lib/rubocop/cop/avoid_global_vars.rb +61 -0
  26. data/lib/rubocop/cop/avoid_perl_backrefs.rb +17 -0
  27. data/lib/rubocop/cop/avoid_perlisms.rb +47 -0
  28. data/lib/rubocop/cop/block_comments.rb +15 -0
  29. data/lib/rubocop/cop/blocks.rb +11 -47
  30. data/lib/rubocop/cop/case_indentation.rb +22 -0
  31. data/lib/rubocop/cop/class_and_module_camel_case.rb +20 -11
  32. data/lib/rubocop/cop/class_methods.rb +15 -0
  33. data/lib/rubocop/cop/collection_methods.rb +16 -16
  34. data/lib/rubocop/cop/colon_method_call.rb +20 -0
  35. data/lib/rubocop/cop/constant_name.rb +24 -0
  36. data/lib/rubocop/cop/cop.rb +34 -47
  37. data/lib/rubocop/cop/def_parentheses.rb +43 -35
  38. data/lib/rubocop/cop/empty_line_between_defs.rb +22 -0
  39. data/lib/rubocop/cop/empty_lines.rb +21 -13
  40. data/lib/rubocop/cop/empty_literal.rb +47 -0
  41. data/lib/rubocop/cop/encoding.rb +3 -3
  42. data/lib/rubocop/cop/end_of_line.rb +3 -3
  43. data/lib/rubocop/cop/ensure_return.rb +19 -0
  44. data/lib/rubocop/cop/eval.rb +19 -0
  45. data/lib/rubocop/cop/favor_join.rb +22 -0
  46. data/lib/rubocop/cop/favor_modifier.rb +38 -48
  47. data/lib/rubocop/cop/favor_percent_r.rb +19 -0
  48. data/lib/rubocop/cop/favor_sprintf.rb +21 -0
  49. data/lib/rubocop/cop/favor_unless_over_negated_if.rb +19 -17
  50. data/lib/rubocop/cop/handle_exceptions.rb +17 -0
  51. data/lib/rubocop/cop/hash_syntax.rb +29 -14
  52. data/lib/rubocop/cop/if_then_else.rb +32 -29
  53. data/lib/rubocop/cop/leading_comment_space.rb +17 -0
  54. data/lib/rubocop/cop/line_continuation.rb +15 -0
  55. data/lib/rubocop/cop/line_length.rb +4 -4
  56. data/lib/rubocop/cop/loop.rb +33 -0
  57. data/lib/rubocop/cop/method_and_variable_snake_case.rb +41 -17
  58. data/lib/rubocop/cop/method_length.rb +52 -0
  59. data/lib/rubocop/cop/new_lambda_literal.rb +8 -6
  60. data/lib/rubocop/cop/not.rb +21 -0
  61. data/lib/rubocop/cop/numeric_literals.rb +9 -7
  62. data/lib/rubocop/cop/offence.rb +12 -1
  63. data/lib/rubocop/cop/op_method.rb +26 -0
  64. data/lib/rubocop/cop/parameter_lists.rb +12 -6
  65. data/lib/rubocop/cop/parentheses_around_condition.rb +11 -11
  66. data/lib/rubocop/cop/percent_r.rb +19 -0
  67. data/lib/rubocop/cop/reduce_arguments.rb +29 -0
  68. data/lib/rubocop/cop/rescue_exception.rb +26 -0
  69. data/lib/rubocop/cop/rescue_modifier.rb +17 -0
  70. data/lib/rubocop/cop/semicolon.rb +31 -0
  71. data/lib/rubocop/cop/single_line_methods.rb +44 -0
  72. data/lib/rubocop/cop/space_after_comma_etc.rb +30 -10
  73. data/lib/rubocop/cop/space_after_control_keyword.rb +29 -0
  74. data/lib/rubocop/cop/string_literals.rb +9 -23
  75. data/lib/rubocop/cop/surrounding_space.rb +223 -83
  76. data/lib/rubocop/cop/symbol_array.rb +31 -0
  77. data/lib/rubocop/cop/symbol_name.rb +23 -0
  78. data/lib/rubocop/cop/syntax.rb +35 -5
  79. data/lib/rubocop/cop/tab.rb +3 -3
  80. data/lib/rubocop/cop/ternary_operator.rb +26 -24
  81. data/lib/rubocop/cop/trailing_whitespace.rb +3 -5
  82. data/lib/rubocop/cop/trivial_accessors.rb +26 -0
  83. data/lib/rubocop/cop/unless_else.rb +11 -7
  84. data/lib/rubocop/cop/util.rb +26 -0
  85. data/lib/rubocop/cop/variable_interpolation.rb +29 -0
  86. data/lib/rubocop/cop/when_then.rb +6 -14
  87. data/lib/rubocop/cop/word_array.rb +37 -0
  88. data/lib/rubocop/report/emacs_style.rb +2 -2
  89. data/lib/rubocop/report/plain_text.rb +1 -1
  90. data/lib/rubocop/version.rb +3 -1
  91. data/lib/rubocop.rb +48 -8
  92. data/rubocop.gemspec +32 -151
  93. data/spec/project_spec.rb +27 -0
  94. data/spec/rubocop/cli_spec.rb +573 -200
  95. data/spec/rubocop/config_spec.rb +409 -0
  96. data/spec/rubocop/config_store_spec.rb +66 -0
  97. data/spec/rubocop/cops/access_control_spec.rb +129 -0
  98. data/spec/rubocop/cops/alias_spec.rb +39 -0
  99. data/spec/rubocop/cops/align_parameters_spec.rb +66 -70
  100. data/spec/rubocop/cops/and_or_spec.rb +37 -0
  101. data/spec/rubocop/cops/ascii_comments_spec.rb +26 -0
  102. data/spec/rubocop/cops/ascii_identifiers_spec.rb +26 -0
  103. data/spec/rubocop/cops/avoid_class_vars_spec.rb +25 -0
  104. data/spec/rubocop/cops/avoid_for_spec.rb +35 -0
  105. data/spec/rubocop/cops/avoid_global_vars_spec.rb +32 -0
  106. data/spec/rubocop/cops/avoid_perl_backrefs_spec.rb +18 -0
  107. data/spec/rubocop/cops/avoid_perlisms_spec.rb +44 -0
  108. data/spec/rubocop/cops/block_comments_spec.rb +25 -0
  109. data/spec/rubocop/cops/blocks_spec.rb +33 -0
  110. data/spec/rubocop/cops/{indentation_spec.rb → case_indentation_spec.rb} +7 -7
  111. data/spec/rubocop/cops/class_and_module_camel_case_spec.rb +15 -5
  112. data/spec/rubocop/cops/class_methods_spec.rb +49 -0
  113. data/spec/rubocop/cops/collection_methods_spec.rb +9 -4
  114. data/spec/rubocop/cops/colon_method_call_spec.rb +53 -0
  115. data/spec/rubocop/cops/constant_name_spec.rb +42 -0
  116. data/spec/rubocop/cops/def_with_parentheses_spec.rb +13 -8
  117. data/spec/rubocop/cops/def_without_parentheses_spec.rb +11 -5
  118. data/spec/rubocop/cops/empty_line_between_defs_spec.rb +83 -0
  119. data/spec/rubocop/cops/empty_lines_spec.rb +14 -59
  120. data/spec/rubocop/cops/empty_literal_spec.rb +90 -0
  121. data/spec/rubocop/cops/encoding_spec.rb +11 -11
  122. data/spec/rubocop/cops/end_of_line_spec.rb +2 -2
  123. data/spec/rubocop/cops/ensure_return_spec.rb +35 -0
  124. data/spec/rubocop/cops/eval_spec.rb +39 -0
  125. data/spec/rubocop/cops/favor_join_spec.rb +35 -0
  126. data/spec/rubocop/cops/favor_modifier_spec.rb +16 -14
  127. data/spec/rubocop/cops/favor_percent_r_spec.rb +29 -0
  128. data/spec/rubocop/cops/favor_sprintf_spec.rb +51 -0
  129. data/spec/rubocop/cops/favor_unless_over_negated_if_spec.rb +4 -4
  130. data/spec/rubocop/cops/favor_until_over_negated_while_spec.rb +3 -3
  131. data/spec/rubocop/cops/handle_exceptions_spec.rb +34 -0
  132. data/spec/rubocop/cops/hash_syntax_spec.rb +11 -6
  133. data/spec/rubocop/cops/if_with_semicolon_spec.rb +7 -1
  134. data/spec/rubocop/cops/leading_comment_space_spec.rb +54 -0
  135. data/spec/rubocop/cops/line_continuation_spec.rb +24 -0
  136. data/spec/rubocop/cops/line_length_spec.rb +3 -2
  137. data/spec/rubocop/cops/loop_spec.rb +31 -0
  138. data/spec/rubocop/cops/method_and_variable_snake_case_spec.rb +55 -9
  139. data/spec/rubocop/cops/method_length_spec.rb +147 -0
  140. data/spec/rubocop/cops/multiline_if_then_spec.rb +15 -15
  141. data/spec/rubocop/cops/new_lambda_literal_spec.rb +5 -6
  142. data/spec/rubocop/cops/not_spec.rb +31 -0
  143. data/spec/rubocop/cops/numeric_literals_spec.rb +13 -13
  144. data/spec/rubocop/cops/offence_spec.rb +13 -0
  145. data/spec/rubocop/cops/one_line_conditional_spec.rb +1 -1
  146. data/spec/rubocop/cops/op_method_spec.rb +78 -0
  147. data/spec/rubocop/cops/parameter_lists_spec.rb +7 -7
  148. data/spec/rubocop/cops/parentheses_around_condition_spec.rb +41 -44
  149. data/spec/rubocop/cops/percent_r_spec.rb +29 -0
  150. data/spec/rubocop/cops/reduce_arguments_spec.rb +57 -0
  151. data/spec/rubocop/cops/rescue_exception_spec.rb +125 -0
  152. data/spec/rubocop/cops/rescue_modifier_spec.rb +37 -0
  153. data/spec/rubocop/cops/semicolon_spec.rb +88 -0
  154. data/spec/rubocop/cops/single_line_methods_spec.rb +50 -0
  155. data/spec/rubocop/cops/space_after_colon_spec.rb +3 -3
  156. data/spec/rubocop/cops/space_after_comma_spec.rb +14 -2
  157. data/spec/rubocop/cops/space_after_control_keyword_spec.rb +67 -0
  158. data/spec/rubocop/cops/space_after_semicolon_spec.rb +6 -1
  159. data/spec/rubocop/cops/space_around_braces_spec.rb +18 -3
  160. data/spec/rubocop/cops/space_around_equals_in_default_parameter_spec.rb +12 -2
  161. data/spec/rubocop/cops/space_around_operators_spec.rb +88 -26
  162. data/spec/rubocop/cops/space_inside_brackets_spec.rb +13 -7
  163. data/spec/rubocop/cops/space_inside_hash_literal_braces_spec.rb +79 -0
  164. data/spec/rubocop/cops/space_inside_parens_spec.rb +7 -3
  165. data/spec/rubocop/cops/string_literals_spec.rb +21 -6
  166. data/spec/rubocop/cops/symbol_array_spec.rb +41 -0
  167. data/spec/rubocop/cops/symbol_name_spec.rb +119 -0
  168. data/spec/rubocop/cops/syntax_spec.rb +28 -5
  169. data/spec/rubocop/cops/tab_spec.rb +2 -2
  170. data/spec/rubocop/cops/ternary_operator_spec.rb +13 -17
  171. data/spec/rubocop/cops/trailing_whitespace_spec.rb +3 -3
  172. data/spec/rubocop/cops/trivial_accessors_spec.rb +329 -0
  173. data/spec/rubocop/cops/unless_else_spec.rb +8 -8
  174. data/spec/rubocop/cops/variable_interpolation_spec.rb +49 -0
  175. data/spec/rubocop/cops/when_then_spec.rb +14 -14
  176. data/spec/rubocop/cops/word_array_spec.rb +47 -0
  177. data/spec/spec_helper.rb +30 -9
  178. data/spec/support/file_helper.rb +21 -0
  179. data/spec/support/isolated_environment.rb +27 -0
  180. metadata +235 -76
  181. data/.document +0 -5
  182. data/Gemfile.lock +0 -41
  183. data/VERSION +0 -1
  184. data/lib/rubocop/cop/ampersands_pipes_vs_and_or.rb +0 -25
  185. data/lib/rubocop/cop/grammar.rb +0 -135
  186. data/lib/rubocop/cop/indentation.rb +0 -44
  187. data/spec/rubocop/cops/ampersands_pipes_vs_and_or_spec.rb +0 -57
  188. data/spec/rubocop/cops/grammar_spec.rb +0 -71
  189. data/spec/rubocop/cops/multiline_blocks_spec.rb +0 -24
  190. 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
- $options = { mode: :default }
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', '--[no-]debug', 'Display debug info') do |d|
22
- $options[:debug] = d
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
- $options[:mode] = :emacs_style
139
+ @options[:mode] = :emacs_style
26
140
  end
27
141
  opts.on('-c FILE', '--config FILE', 'Configuration file') do |f|
28
- $options[:config] = YAML.load_file(f)
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
- $options[:silent] = s
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::VERSION
155
+ puts Rubocop::Version::STRING
35
156
  exit(0)
36
157
  end
37
158
  end.parse!(args)
159
+ end
38
160
 
39
- total_offences = 0
40
- @configs = {}
41
-
42
- target_files = target_files(args)
43
- config = $options[:config] || config_from_dotfile(target_files[0])
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
- cops = cops_on_duty(config)
46
- show_cops_on_duty(cops) if $options[:debug]
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
- target_files(args).each do |file|
49
- report = Report.create(file, $options[:mode])
50
- source = File.readlines(file).map do |line|
51
- get_rid_of_invalid_byte_sequences(line)
52
- line.chomp
53
- end
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
- tokens, sexp, correlations = CLI.rip_source(source)
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
- cops.each do |cop_klass|
58
- cop_config = config[cop_klass.name.split('::').last] if config
59
- cop_klass.config = cop_config
60
- cop = cop_klass.new
61
- cop.correlations = correlations
62
- cop.inspect(file, source, tokens, sexp)
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
- report.display unless report.empty?
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
- unless $options[:silent]
71
- print "\n#{target_files(args).count} files inspected, "
72
- puts "#{total_offences} offences detected"
73
- .send(total_offences.zero? ? :green : :red)
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 get_rid_of_invalid_byte_sequences(line)
80
- enc = line.encoding.name
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
- def self.rip_source(source)
88
- tokens = Ripper.lex(source.join("\n")).map { |t| Cop::Token.new(*t) }
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
- # Returns the configuration hash from .rubocop.yml searching
96
- # upwards in the directory structure starting at the given
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
- def cops_on_duty(config)
121
- cops_on_duty = []
233
+ source_buffer = Parser::Source::Buffer.new(file, 1)
234
+ yield source_buffer
122
235
 
123
- Cop::Cop.all.each do |cop_klass|
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
- cops_on_duty
129
- end
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
- def show_cops_on_duty(cops)
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 in the current directory
249
+ # files under the current directory
140
250
  # @return [Array] array of filenames
141
251
  def target_files(args)
142
- return Dir['**/*.rb'] if args.empty?
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 << Dir["#{target}/**/*.rb"]
258
+ files += ruby_files(target.chomp(File::SEPARATOR))
149
259
  elsif target =~ /\*/
150
- files << Dir[target]
260
+ files += Dir[target]
151
261
  else
152
262
  files << target
153
263
  end
154
264
  end
155
265
 
156
- files.flatten
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