reek 1.3.4 → 1.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +5 -0
  3. data/README.md +27 -2
  4. data/features/command_line_interface/options.feature +5 -2
  5. data/features/command_line_interface/smells_count.feature +43 -45
  6. data/features/command_line_interface/stdin.feature +9 -15
  7. data/features/configuration_files/masking_smells.feature +9 -17
  8. data/features/rake_task/rake_task.feature +4 -4
  9. data/features/reports/reports.feature +80 -21
  10. data/features/samples.feature +8 -18
  11. data/features/step_definitions/reek_steps.rb +4 -0
  12. data/lib/reek/cli/application.rb +3 -6
  13. data/lib/reek/cli/command_line.rb +16 -6
  14. data/lib/reek/cli/reek_command.rb +4 -12
  15. data/lib/reek/cli/report.rb +61 -19
  16. data/lib/reek/config_file_exception.rb +5 -0
  17. data/lib/reek/smells/control_parameter.rb +45 -14
  18. data/lib/reek/smells/data_clump.rb +15 -39
  19. data/lib/reek/smells/duplicate_method_call.rb +76 -26
  20. data/lib/reek/source/config_file.rb +30 -19
  21. data/lib/reek/source/sexp_extensions.rb +139 -0
  22. data/lib/reek/source/sexp_node.rb +64 -0
  23. data/lib/reek/source/source_code.rb +1 -1
  24. data/lib/reek/source/tree_dresser.rb +30 -175
  25. data/lib/reek/spec/should_reek.rb +2 -5
  26. data/lib/reek/version.rb +1 -1
  27. data/reek.gemspec +1 -1
  28. data/spec/matchers/smell_of_matcher.rb +12 -15
  29. data/spec/reek/cli/report_spec.rb +10 -6
  30. data/spec/reek/core/code_parser_spec.rb +0 -6
  31. data/spec/reek/smells/control_parameter_spec.rb +195 -8
  32. data/spec/reek/smells/data_clump_spec.rb +28 -3
  33. data/spec/reek/smells/uncommunicative_method_name_spec.rb +7 -7
  34. data/spec/reek/source/sexp_extensions_spec.rb +290 -0
  35. data/spec/reek/source/sexp_node_spec.rb +28 -0
  36. data/spec/reek/source/source_code_spec.rb +59 -19
  37. data/spec/reek/source/tree_dresser_spec.rb +7 -314
  38. data/spec/reek/spec/should_reek_spec.rb +51 -64
  39. data/spec/samples/all_but_one_masked/dirty.rb +2 -2
  40. data/spec/samples/corrupt_config_file/dirty.rb +1 -0
  41. data/spec/samples/masked/dirty.rb +1 -1
  42. data/spec/samples/masked_by_dotfile/dirty.rb +2 -2
  43. data/spec/samples/no_config_file/dirty.rb +8 -0
  44. data/spec/samples/not_quite_masked/dirty.rb +0 -3
  45. data/spec/samples/three_smelly_files/dirty_one.rb +3 -0
  46. data/spec/samples/three_smelly_files/dirty_three.rb +5 -0
  47. data/spec/samples/three_smelly_files/dirty_two.rb +4 -0
  48. data/spec/spec_helper.rb +5 -0
  49. metadata +145 -137
  50. data/spec/reek/cli/reek_command_spec.rb +0 -46
@@ -1,4 +1,5 @@
1
1
  require 'yaml'
2
+ require 'reek/config_file_exception'
2
3
 
3
4
  module Reek
4
5
  module Source
@@ -43,26 +44,25 @@ module Reek
43
44
  end
44
45
 
45
46
  #
46
- # Load the file path with which this was initialized,
47
- # unless it is already known to be a bad configuration file.
48
- # If it won't load, then it is considered a bad file.
47
+ # Load the file path with which this was initialized.
48
+ # Empty files are ignored with a warning. All other errors are to be
49
+ # handled farther up the stack.
49
50
  #
50
51
  def load
51
- unless @@bad_config_files.include?(@file_path)
52
- begin
53
- result = YAML.load_file(@file_path) || {}
54
- if Hash === result
55
- return result
56
- else
57
- @@bad_config_files << @file_path # poop
58
- problem('Not a hash')
59
- end
60
- rescue Exception => err
61
- @@bad_config_files << @file_path # poop
62
- problem(err.to_s)
63
- end
52
+ if File.size(@file_path) == 0
53
+ problem('Empty file')
54
+ return {}
64
55
  end
65
- return {}
56
+
57
+ begin
58
+ result = YAML.load_file(@file_path) || {}
59
+ rescue => e
60
+ error(e.to_s)
61
+ end
62
+
63
+ error('Not a hash') unless Hash === result
64
+
65
+ result
66
66
  end
67
67
 
68
68
  #
@@ -70,8 +70,19 @@ module Reek
70
70
  # Error.
71
71
  #
72
72
  def problem(reason)
73
- $stderr.puts "Error: Invalid configuration file \"#{File.basename(@file_path)}\" -- #{reason}"
74
- # SMELL: Duplication of 'Error:'
73
+ $stderr.puts "Warning: #{message(reason)}"
74
+ end
75
+
76
+ #
77
+ # Report invalid configuration file to standard
78
+ # Error.
79
+ #
80
+ def error(reason)
81
+ raise ConfigFileException.new message(reason)
82
+ end
83
+
84
+ def message(reason)
85
+ "Invalid configuration file \"#{File.basename(@file_path)}\" -- #{reason}"
75
86
  end
76
87
  end
77
88
  end
@@ -0,0 +1,139 @@
1
+ require 'reek/source/sexp_node'
2
+
3
+ module Reek
4
+ module Source
5
+ module SexpExtensions
6
+ module AndNode
7
+ def condition() self[1..2].tap {|b| b.extend SexpNode } end
8
+ end
9
+
10
+ module OrNode
11
+ def condition() self[1..2].tap {|b| b.extend SexpNode } end
12
+ end
13
+
14
+ module AttrasgnNode
15
+ def args() self[3] end
16
+ end
17
+
18
+ module CaseNode
19
+ def condition() self[1] end
20
+ end
21
+
22
+ module CallNode
23
+ def receiver() self[1] end
24
+ def method_name() self[2] end
25
+ def args() self[3..-1] end
26
+ def arg_names
27
+ args.map {|arg| arg[1]}
28
+ end
29
+ end
30
+
31
+ module CvarNode
32
+ def name() self[1] end
33
+ end
34
+
35
+ CvasgnNode = CvarNode
36
+ CvdeclNode = CvarNode
37
+
38
+ module LvarNode
39
+ def value() self[1] end
40
+ end
41
+
42
+ module MethodNode
43
+ def arg_names
44
+ @args ||= parameter_names.reject {|param| param.to_s =~ /^&/}
45
+ end
46
+ def parameter_names
47
+ @param_names ||= argslist[1..-1].map { |param| Sexp === param ? param[1] : param }
48
+ end
49
+ end
50
+
51
+ module DefnNode
52
+ def name() self[1] end
53
+ def argslist() self[2] end
54
+ def body()
55
+ self[3..-1].extend SexpNode
56
+ end
57
+ include MethodNode
58
+ def full_name(outer)
59
+ prefix = outer == '' ? '' : "#{outer}#"
60
+ "#{prefix}#{name}"
61
+ end
62
+ end
63
+
64
+ module DefsNode
65
+ def receiver() self[1] end
66
+ def name() self[2] end
67
+ def argslist() self[3] end
68
+ def body()
69
+ self[4..-1].extend SexpNode
70
+ end
71
+ include MethodNode
72
+ def full_name(outer)
73
+ prefix = outer == '' ? '' : "#{outer}#"
74
+ "#{prefix}#{SexpNode.format(receiver)}.#{name}"
75
+ end
76
+ end
77
+
78
+ module IfNode
79
+ def condition() self[1] end
80
+ end
81
+
82
+ module IterNode
83
+ def call() self[1] end
84
+ def args() self[2] end
85
+ def block() self[3] end
86
+ def parameters() self[2] || [] end
87
+ def parameter_names
88
+ parameters[1..-1].to_a
89
+ end
90
+ end
91
+
92
+ module LitNode
93
+ def value() self[1] end
94
+ end
95
+
96
+ module Colon2Node
97
+ def name
98
+ self[2]
99
+ end
100
+
101
+ def simple_name
102
+ if name.is_a? Colon2Node
103
+ name.simple_name
104
+ else
105
+ name
106
+ end
107
+ end
108
+ end
109
+
110
+ module ModuleNode
111
+ def name() self[1] end
112
+
113
+ def simple_name
114
+ Sexp === name ? name.simple_name : name
115
+ end
116
+
117
+ def full_name(outer)
118
+ prefix = outer == '' ? '' : "#{outer}::"
119
+ "#{prefix}#{text_name}"
120
+ end
121
+ def text_name
122
+ SexpNode.format(name)
123
+ end
124
+ end
125
+
126
+ module ClassNode
127
+ include ModuleNode
128
+ def superclass() self[2] end
129
+ end
130
+
131
+ module YieldNode
132
+ def args() self[1..-1] end
133
+ def arg_names
134
+ args.map {|arg| arg[1]}
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,64 @@
1
+ module Reek
2
+ module Source
3
+ #
4
+ # Extensions to +Sexp+ to allow +CodeParser+ to navigate the abstract
5
+ # syntax tree more easily.
6
+ #
7
+ module SexpNode
8
+ def self.format(expr)
9
+ case expr
10
+ when Sexp then expr.format_ruby
11
+ else expr.to_s
12
+ end
13
+ end
14
+
15
+ def hash
16
+ self.inspect.hash
17
+ end
18
+
19
+ def is_language_node?
20
+ Symbol === first
21
+ end
22
+
23
+ def has_type?(type)
24
+ is_language_node? and first == type
25
+ end
26
+
27
+ def each_node(type, ignoring, &blk)
28
+ if block_given?
29
+ look_for(type, ignoring, &blk)
30
+ else
31
+ result = []
32
+ look_for(type, ignoring) {|exp| result << exp}
33
+ result
34
+ end
35
+ end
36
+
37
+ def each_sexp
38
+ each { |elem| yield elem if Sexp === elem }
39
+ end
40
+
41
+ #
42
+ # Carries out a depth-first traversal of this syntax tree, yielding
43
+ # every Sexp of type +target_type+. The traversal ignores any node
44
+ # whose type is listed in the Array +ignoring+.
45
+ #
46
+ def look_for(target_type, ignoring, &blk)
47
+ each do |elem|
48
+ if Sexp === elem then
49
+ elem.look_for(target_type, ignoring, &blk) unless ignoring.include?(elem.first)
50
+ end
51
+ end
52
+ blk.call(self) if first == target_type
53
+ end
54
+
55
+ def format_ruby
56
+ Ruby2Ruby.new.process(deep_copy)
57
+ end
58
+
59
+ def deep_copy
60
+ Sexp.new(*map { |elem| Sexp === elem ? elem.deep_copy : elem })
61
+ end
62
+ end
63
+ end
64
+ end
@@ -33,7 +33,7 @@ module Reek
33
33
  def syntax_tree
34
34
  begin
35
35
  ast = @parser.parse(@source, @desc)
36
- rescue Exception => error
36
+ rescue Racc::ParseError, RubyParser::SyntaxError => error
37
37
  @@err_io.puts "#{desc}: #{error.class.name}: #{error}"
38
38
  end
39
39
  ast ||= s()
@@ -1,191 +1,46 @@
1
+ require 'reek/source/sexp_node'
2
+ require 'reek/source/sexp_extensions'
3
+
1
4
  module Reek
2
5
  module Source
3
-
4
6
  #
5
- # Extensions to +Sexp+ to allow +CodeParser+ to navigate the abstract
6
- # syntax tree more easily.
7
+ # Adorns an abstract syntax tree with mix-in modules to make accessing
8
+ # the tree more understandable and less implementation-dependent.
7
9
  #
8
- module SexpNode
9
- def self.format(expr)
10
- case expr
11
- when Sexp then expr.format_ruby
12
- else expr.to_s
13
- end
14
- end
15
-
16
- def hash
17
- self.inspect.hash
18
- end
19
-
20
- def is_language_node?
21
- Symbol === first
22
- end
23
-
24
- def has_type?(type)
25
- is_language_node? and first == type
26
- end
27
-
28
- def each_node(type, ignoring, &blk)
29
- if block_given?
30
- look_for(type, ignoring, &blk)
31
- else
32
- result = []
33
- look_for(type, ignoring) {|exp| result << exp}
34
- result
35
- end
36
- end
37
-
38
- #
39
- # Carries out a depth-first traversal of this syntax tree, yielding
40
- # every Sexp of type +target_type+. The traversal ignores any node
41
- # whose type is listed in the Array +ignoring+.
42
- #
43
- def look_for(target_type, ignoring, &blk)
44
- each do |elem|
45
- if Sexp === elem then
46
- elem.look_for(target_type, ignoring, &blk) unless ignoring.include?(elem.first)
47
- end
48
- end
49
- blk.call(self) if first == target_type
50
- end
51
-
52
- def format_ruby
53
- Ruby2Ruby.new.process(deep_copy)
54
- end
55
-
56
- def deep_copy
57
- Sexp.new(*map { |elem| Sexp === elem ? elem.deep_copy : elem })
58
- end
59
- end
60
-
61
- module SexpExtensions
62
- module AttrasgnNode
63
- def args() self[3] end
64
- end
65
-
66
- module CaseNode
67
- def condition() self[1] end
68
- end
69
-
70
- module CallNode
71
- def receiver() self[1] end
72
- def method_name() self[2] end
73
- def args() self[3..-1] end
74
- def arg_names
75
- args.map {|arg| arg[1]}
76
- end
77
- end
78
-
79
- module CvarNode
80
- def name() self[1] end
81
- end
82
-
83
- CvasgnNode = CvarNode
84
- CvdeclNode = CvarNode
85
-
86
- module MethodNode
87
- def arg_names
88
- @args ||= parameter_names.reject {|param| param.to_s =~ /^&/}
89
- end
90
- def parameter_names
91
- @param_names ||= argslist[1..-1].map { |param| Sexp === param ? param[1] : param }
92
- end
93
- end
94
-
95
- module DefnNode
96
- def name() self[1] end
97
- def argslist() self[2] end
98
- def body()
99
- self[3..-1].tap {|b| b.extend SexpNode }
100
- end
101
- include MethodNode
102
- def full_name(outer)
103
- prefix = outer == '' ? '' : "#{outer}#"
104
- "#{prefix}#{name}"
105
- end
106
- end
107
-
108
- module DefsNode
109
- def receiver() self[1] end
110
- def name() self[2] end
111
- def argslist() self[3] end
112
- def body()
113
- self[4..-1].tap {|b| b.extend SexpNode }
114
- end
115
- include MethodNode
116
- def full_name(outer)
117
- prefix = outer == '' ? '' : "#{outer}#"
118
- "#{prefix}#{SexpNode.format(receiver)}.#{name}"
119
- end
120
- end
121
-
122
- module IfNode
123
- def condition() self[1] end
124
- end
125
-
126
- module IterNode
127
- def call() self[1] end
128
- def args() self[2] end
129
- def block() self[3] end
130
- def parameters() self[2] || [] end
131
- def parameter_names
132
- parameters[1..-1].to_a
133
- end
134
- end
135
-
136
- module LitNode
137
- def value() self[1] end
10
+ class TreeDresser
11
+ def initialize(extensions_module = SexpExtensions, node_module = SexpNode)
12
+ @extensions_module = extensions_module
13
+ @node_module = node_module
138
14
  end
139
15
 
140
- module ModuleNode
141
- def name() self[1] end
142
- def simple_name
143
- expr = name
144
- while Sexp === expr and expr[0] == :colon2
145
- expr = expr[2]
146
- end
147
- expr
148
- end
149
- def full_name(outer)
150
- prefix = outer == '' ? '' : "#{outer}::"
151
- "#{prefix}#{text_name}"
152
- end
153
- def text_name
154
- SexpNode.format(name)
155
- end
16
+ def dress(sexp)
17
+ extend_sexp(sexp)
18
+ sexp.each_sexp { |sub| dress(sub) }
19
+ sexp
156
20
  end
157
21
 
158
- module ClassNode
159
- include ModuleNode
160
- def superclass() self[2] end
161
- end
22
+ private
162
23
 
163
- module YieldNode
164
- def args() self[1..-1] end
165
- def arg_names
166
- args.map {|arg| arg[1]}
167
- end
24
+ def extend_sexp(sexp)
25
+ sexp.extend(@node_module)
26
+ extension_module = extension_for(sexp)
27
+ sexp.extend(extension_module) if extension_module
168
28
  end
169
- end
170
29
 
171
- #
172
- # Adorns an abstract syntax tree with mix-in modules to make accessing
173
- # the tree more understandable and less implementation-dependent.
174
- #
175
- class TreeDresser
176
-
177
- def dress(sexp)
178
- sexp.extend(SexpNode)
179
- module_name = extensions_for(sexp.sexp_type)
180
- if SexpExtensions.const_defined?(module_name)
181
- sexp.extend(SexpExtensions.const_get(module_name))
182
- end
183
- sexp[0..-1].each { |sub| dress(sub) if Array === sub }
184
- sexp
30
+ def extension_for(sexp)
31
+ extension_map[sexp.sexp_type]
185
32
  end
186
33
 
187
- def extensions_for(node_type)
188
- "#{node_type.to_s.capitalize}Node"
34
+ def extension_map
35
+ @extension_map ||= begin
36
+ assoc = @extensions_module.constants.map { |const|
37
+ [
38
+ const.to_s.sub(/Node$/, '').downcase.to_sym,
39
+ @extensions_module.const_get(const)
40
+ ]
41
+ }
42
+ Hash[assoc]
43
+ end
189
44
  end
190
45
  end
191
46
  end