reek 1.3.4 → 1.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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