rubocop 0.6.1 → 0.7.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -266
  3. data/CHANGELOG.md +49 -7
  4. data/README.md +75 -2
  5. data/Rakefile +2 -2
  6. data/bin/rubocop +15 -10
  7. data/lib/rubocop.rb +19 -1
  8. data/lib/rubocop/cli.rb +113 -116
  9. data/lib/rubocop/config.rb +202 -0
  10. data/lib/rubocop/config_store.rb +37 -0
  11. data/lib/rubocop/cop/alias.rb +2 -5
  12. data/lib/rubocop/cop/align_parameters.rb +1 -1
  13. data/lib/rubocop/cop/array_literal.rb +43 -4
  14. data/lib/rubocop/cop/avoid_for.rb +2 -4
  15. data/lib/rubocop/cop/avoid_global_vars.rb +49 -0
  16. data/lib/rubocop/cop/block_comments.rb +17 -0
  17. data/lib/rubocop/cop/brace_after_percent.rb +9 -5
  18. data/lib/rubocop/cop/{indentation.rb → case_indentation.rb} +1 -1
  19. data/lib/rubocop/cop/class_methods.rb +20 -0
  20. data/lib/rubocop/cop/colon_method_call.rb +44 -0
  21. data/lib/rubocop/cop/cop.rb +30 -2
  22. data/lib/rubocop/cop/def_parentheses.rb +1 -1
  23. data/lib/rubocop/cop/empty_line_between_defs.rb +26 -0
  24. data/lib/rubocop/cop/empty_lines.rb +10 -13
  25. data/lib/rubocop/cop/eval.rb +22 -0
  26. data/lib/rubocop/cop/favor_join.rb +37 -0
  27. data/lib/rubocop/cop/grammar.rb +2 -2
  28. data/lib/rubocop/cop/hash_literal.rb +43 -4
  29. data/lib/rubocop/cop/hash_syntax.rb +2 -2
  30. data/lib/rubocop/cop/if_then_else.rb +1 -1
  31. data/lib/rubocop/cop/leading_comment_space.rb +20 -0
  32. data/lib/rubocop/cop/line_continuation.rb +18 -0
  33. data/lib/rubocop/cop/line_length.rb +1 -1
  34. data/lib/rubocop/cop/method_and_variable_snake_case.rb +7 -6
  35. data/lib/rubocop/cop/method_length.rb +4 -15
  36. data/lib/rubocop/cop/not.rb +15 -0
  37. data/lib/rubocop/cop/offence.rb +9 -0
  38. data/lib/rubocop/cop/semicolon.rb +74 -3
  39. data/lib/rubocop/cop/single_line_methods.rb +60 -0
  40. data/lib/rubocop/cop/space_after_control_keyword.rb +28 -0
  41. data/lib/rubocop/cop/surrounding_space.rb +48 -9
  42. data/lib/rubocop/cop/symbol_array.rb +29 -0
  43. data/lib/rubocop/cop/trivial_accessors.rb +103 -0
  44. data/lib/rubocop/cop/unless_else.rb +1 -1
  45. data/lib/rubocop/cop/variable_interpolation.rb +3 -2
  46. data/lib/rubocop/cop/word_array.rb +38 -0
  47. data/lib/rubocop/version.rb +1 -1
  48. data/rubocop.gemspec +11 -7
  49. data/spec/project_spec.rb +27 -0
  50. data/spec/rubocop/cli_spec.rb +549 -487
  51. data/spec/rubocop/config_spec.rb +399 -0
  52. data/spec/rubocop/config_store_spec.rb +66 -0
  53. data/spec/rubocop/cops/alias_spec.rb +7 -0
  54. data/spec/rubocop/cops/array_literal_spec.rb +8 -1
  55. data/spec/rubocop/cops/avoid_for_spec.rb +15 -1
  56. data/spec/rubocop/cops/avoid_global_vars.rb +32 -0
  57. data/spec/rubocop/cops/block_comments_spec.rb +29 -0
  58. data/spec/rubocop/cops/brace_after_percent_spec.rb +19 -13
  59. data/spec/rubocop/cops/{indentation_spec.rb → case_indentation_spec.rb} +2 -2
  60. data/spec/rubocop/cops/class_methods_spec.rb +49 -0
  61. data/spec/rubocop/cops/colon_method_call_spec.rb +47 -0
  62. data/spec/rubocop/cops/empty_line_between_defs_spec.rb +83 -0
  63. data/spec/rubocop/cops/empty_lines_spec.rb +6 -63
  64. data/spec/rubocop/cops/eval_spec.rb +36 -0
  65. data/spec/rubocop/cops/favor_join_spec.rb +39 -0
  66. data/spec/rubocop/cops/hash_literal_spec.rb +8 -1
  67. data/spec/rubocop/cops/leading_comment_space_spec.rb +60 -0
  68. data/spec/rubocop/cops/line_continuation_spec.rb +24 -0
  69. data/spec/rubocop/cops/line_length_spec.rb +1 -0
  70. data/spec/rubocop/cops/method_and_variable_snake_case_spec.rb +20 -0
  71. data/spec/rubocop/cops/method_length_spec.rb +2 -5
  72. data/spec/rubocop/cops/new_lambda_literal_spec.rb +2 -3
  73. data/spec/rubocop/cops/not_spec.rb +34 -0
  74. data/spec/rubocop/cops/offence_spec.rb +7 -0
  75. data/spec/rubocop/cops/semicolon_spec.rb +79 -4
  76. data/spec/rubocop/cops/single_line_methods_spec.rb +50 -0
  77. data/spec/rubocop/cops/space_after_control_keyword_spec.rb +28 -0
  78. data/spec/rubocop/cops/space_around_equals_in_default_parameter_spec.rb +11 -1
  79. data/spec/rubocop/cops/space_inside_hash_literal_braces_spec.rb +74 -0
  80. data/spec/rubocop/cops/symbol_array_spec.rb +25 -0
  81. data/spec/rubocop/cops/trivial_accessors_spec.rb +332 -0
  82. data/spec/rubocop/cops/variable_interpolation_spec.rb +10 -1
  83. data/spec/rubocop/cops/word_array_spec.rb +39 -0
  84. data/spec/spec_helper.rb +16 -9
  85. data/spec/support/file_helper.rb +21 -0
  86. data/spec/support/isolated_environment.rb +27 -0
  87. metadata +66 -6
@@ -0,0 +1,202 @@
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 merge_with_default(config, config_file)
91
+ default_config = load_file(DEFAULT_FILE)
92
+ new(merge(default_config, config), config_file)
93
+ end
94
+
95
+ private
96
+
97
+ def dirs_to_search(target_dir)
98
+ dirs_to_search = []
99
+ target_dir_pathname = Pathname.new(File.expand_path(target_dir))
100
+ target_dir_pathname.ascend do |dir_pathname|
101
+ dirs_to_search << dir_pathname.to_s
102
+ end
103
+ dirs_to_search << Dir.home
104
+ dirs_to_search
105
+ end
106
+ end
107
+
108
+ def initialize(hash = {}, loaded_path = nil)
109
+ @hash = hash
110
+ @loaded_path = loaded_path
111
+ super(@hash)
112
+ end
113
+
114
+ def for_cop(cop)
115
+ self[cop]
116
+ end
117
+
118
+ def cop_enabled?(cop)
119
+ self[cop].nil? || self[cop]['Enabled']
120
+ end
121
+
122
+ def warn_unless_valid
123
+ validate!
124
+ rescue Config::ValidationError => e
125
+ puts "Warning: #{e.message}".color(:red)
126
+ end
127
+
128
+ # TODO: This should be a private method
129
+ def validate!
130
+ # Don't validate RuboCop's own files. Avoids inifinite recursion.
131
+ return if @loaded_path.start_with?(RUBOCOP_HOME)
132
+
133
+ default_config = Config.load_file(DEFAULT_FILE)
134
+
135
+ valid_cop_names, invalid_cop_names = @hash.keys.partition do |key|
136
+ default_config.has_key?(key)
137
+ end
138
+
139
+ invalid_cop_names.each do |name|
140
+ fail ValidationError,
141
+ "unrecognized cop #{name} found in #{loaded_path || self}"
142
+ end
143
+
144
+ valid_cop_names.each do |name|
145
+ @hash[name].each_key do |param|
146
+ unless default_config[name].has_key?(param)
147
+ fail ValidationError,
148
+ "unrecognized parameter #{name}:#{param} found " +
149
+ "in #{loaded_path || self}"
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ def file_to_include?(file)
156
+ relative_file_path = relative_path_to_loaded_dir(file)
157
+ patterns_to_include.any? do |pattern|
158
+ match_path?(pattern, relative_file_path)
159
+ end
160
+ end
161
+
162
+ def file_to_exclude?(file)
163
+ relative_file_path = relative_path_to_loaded_dir(file)
164
+ patterns_to_exclude.any? do |pattern|
165
+ match_path?(pattern, relative_file_path)
166
+ end
167
+ end
168
+
169
+ def patterns_to_include
170
+ @hash['AllCops']['Includes']
171
+ end
172
+
173
+ def patterns_to_exclude
174
+ @hash['AllCops']['Excludes']
175
+ end
176
+
177
+ private
178
+
179
+ def relative_path_to_loaded_dir(file)
180
+ return file unless loaded_path
181
+ file_pathname = Pathname.new(File.expand_path(file))
182
+ file_pathname.relative_path_from(loaded_dir_pathname).to_s
183
+ end
184
+
185
+ def loaded_dir_pathname
186
+ return nil unless loaded_path
187
+ @loaded_dir ||= begin
188
+ loaded_dir = File.expand_path(File.dirname(loaded_path))
189
+ Pathname.new(loaded_dir)
190
+ end
191
+ end
192
+
193
+ def match_path?(pattern, path)
194
+ case pattern
195
+ when String
196
+ File.basename(path) == pattern || File.fnmatch(pattern, path)
197
+ when Regexp
198
+ path =~ pattern
199
+ end
200
+ end
201
+ end
202
+ 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
@@ -6,11 +6,8 @@ module Rubocop
6
6
  ERROR_MESSAGE = 'Use alias_method instead of alias.'
7
7
 
8
8
  def inspect(file, source, tokens, sexp)
9
- tokens.each_index do |ix|
10
- t = tokens[ix]
11
- if t.type == :on_kw && t.text == 'alias'
12
- add_offence(:convention, t.pos.lineno, ERROR_MESSAGE)
13
- end
9
+ each_keyword('alias', tokens) do |t|
10
+ add_offence(:convention, t.pos.lineno, ERROR_MESSAGE)
14
11
  end
15
12
  end
16
13
  end
@@ -105,7 +105,7 @@ module Rubocop
105
105
  end
106
106
  end
107
107
  end
108
- offset = @tokens[start_ix..-1].index { |t| not whitespace?(t) }
108
+ offset = @tokens[start_ix..-1].index { |t| !whitespace?(t) }
109
109
  start_ix + offset
110
110
  end
111
111
  end
@@ -6,16 +6,55 @@ module Rubocop
6
6
  ERROR_MESSAGE = 'Use array literal [] instead of Array.new.'
7
7
 
8
8
  def inspect(file, source, tokens, sexp)
9
+ offences = preliminary_scan(sexp)
10
+
11
+ # find Array.new()
9
12
  each(:method_add_arg, sexp) do |s|
10
- potential_class = s[1][1][1]
13
+ next if s[1][0] != :call
14
+
15
+ receiver = s[1][1][1]
16
+ method_name = s[1][3][1]
11
17
 
12
- if potential_class && potential_class[1] == 'Array' &&
13
- s[1][3][1] == 'new' && s[2] == [:arg_paren, nil]
18
+ if receiver && receiver[1] == 'Array' &&
19
+ method_name == 'new' && s[2] == [:arg_paren, nil]
20
+ offences.delete(Offence.new(:convention,
21
+ receiver[2].lineno,
22
+ ERROR_MESSAGE))
14
23
  add_offence(:convention,
15
- potential_class[2].lineno,
24
+ receiver[2].lineno,
16
25
  ERROR_MESSAGE)
17
26
  end
27
+
28
+ # check for false positives
29
+ if receiver && receiver[1] == 'Array' &&
30
+ method_name == 'new' && s[2] != [:arg_paren, nil]
31
+ offences.delete(Offence.new(:convention,
32
+ receiver[2].lineno,
33
+ ERROR_MESSAGE))
34
+ end
35
+ end
36
+
37
+ offences.each { |o| add_offence(*o.explode) }
38
+ end
39
+
40
+ def preliminary_scan(sexp)
41
+ offences = []
42
+
43
+ # find Array.new
44
+ # there will be some false positives here, which
45
+ # we'll eliminate later on
46
+ each(:call, sexp) do |s|
47
+ receiver = s[1][1]
48
+
49
+ if receiver && receiver[1] == 'Array' &&
50
+ s[3][1] == 'new'
51
+ offences << Offence.new(:convention,
52
+ receiver[2].lineno,
53
+ ERROR_MESSAGE)
54
+ end
18
55
  end
56
+
57
+ offences
19
58
  end
20
59
  end
21
60
  end
@@ -6,10 +6,8 @@ module Rubocop
6
6
  ERROR_MESSAGE = 'Prefer *each* over *for*.'
7
7
 
8
8
  def inspect(file, source, tokens, sexp)
9
- each(:for, sexp) do |s|
10
- add_offence(:convention,
11
- s[1][1][2].lineno,
12
- ERROR_MESSAGE)
9
+ each_keyword('for', tokens) do |t|
10
+ add_offence(:convention, t.pos.lineno, ERROR_MESSAGE)
13
11
  end
14
12
  end
15
13
  end
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class AvoidGlobalVars < Cop
6
+ ERROR_MESSAGE = 'Do not introduce global variables.'
7
+
8
+ # predefined global variables their English aliases
9
+ # http://www.zenspider.com/Languages/Ruby/QuickRef.html
10
+ BUILT_IN_VARS = %w(
11
+ $: $LOAD_PATH
12
+ $" $LOADED_FEATURES
13
+ $0 $PROGRAM_NAME
14
+ $! $ERROR_INFO
15
+ $@ $ERROR_POSITION
16
+ $; $FS $FIELD_SEPARATOR
17
+ $, $OFS $OUTPUT_FIELD_SEPARATOR
18
+ $/ $RS $INPUT_RECORD_SEPARATOR
19
+ $\\ $ORS $OUTPUT_RECORD_SEPARATOR
20
+ $. $NR $INPUT_LINE_NUMBER
21
+ $_ $LAST_READ_LINE
22
+ $> $DEFAULT_OUTPUT
23
+ $< $DEFAULT_INPUT
24
+ $$ $PID $PROCESS_ID
25
+ $? $CHILD_STATUS
26
+ $~ $LAST_MATCH_INFO
27
+ $= $IGNORECASE
28
+ $* $ARGV
29
+ $& $MATCH
30
+ $` $PREMATCH
31
+ $' $POSTMATCH
32
+ $+ $LAST_PAREN_MATCH
33
+ $stdin $stdout $stderr
34
+ $DEBUG $FILENAME $VERBOSE
35
+ $-0 $-a $-d $-F $-i $-I $-l $-p $-v $-w
36
+ )
37
+
38
+ def inspect(file, source, tokens, sexp)
39
+ each(:@gvar, sexp) do |s|
40
+ global_var = s[1]
41
+
42
+ unless BUILT_IN_VARS.include?(global_var)
43
+ add_offence(:convention, s[2].lineno, ERROR_MESSAGE)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class BlockComments < Cop
6
+ ERROR_MESSAGE = 'Do not use block comments.'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ tokens.each do |t|
10
+ if t.type == :on_embdoc_beg
11
+ add_offence(:convention, t.pos.lineno, ERROR_MESSAGE)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -5,9 +5,11 @@ module Rubocop
5
5
  class BraceAfterPercent < Cop
6
6
  ERROR_MESSAGE = 'Prefer () as delimiters for all % literals.'
7
7
  LITERALS = {
8
- on_tstring_beg: '%q',
8
+ on_tstring_beg: ['%q', '%Q'],
9
9
  on_words_beg: '%W',
10
10
  on_qwords_beg: '%w',
11
+ on_qsymbols_beg: '%i',
12
+ on_symbols_beg: '%I',
11
13
  on_regexp_beg: '%r',
12
14
  on_symbeg: '%s',
13
15
  on_backtick: '%x'
@@ -16,10 +18,12 @@ module Rubocop
16
18
  def inspect(file, source, tokens, sexp)
17
19
  tokens.each_index do |ix|
18
20
  t = tokens[ix]
19
- token = LITERALS[t.type]
20
- if token && t.text.downcase.start_with?(token) && t.text[2] != '('
21
- add_offence(:convention, t.pos.lineno,
22
- ERROR_MESSAGE)
21
+ literals = Array(LITERALS[t.type])
22
+ literals.each do |literal|
23
+ if literal && t.text.start_with?(literal) && t.text[2] != '('
24
+ add_offence(:convention, t.pos.lineno,
25
+ ERROR_MESSAGE)
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rubocop
4
4
  module Cop
5
- class Indentation < Cop
5
+ class CaseIndentation < Cop
6
6
  ERROR_MESSAGE = 'Indent when as deep as case.'
7
7
 
8
8
  def inspect(file, source, tokens, sexp)