reek 1.2.5 → 1.2.6

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.
@@ -1,7 +1,12 @@
1
+ == 1.2.6 (2009-11-28)
2
+
3
+ === Minor Changes
4
+ * Corrected display of module methods to use # (#56)
5
+
1
6
  == 1.2.5 (2009-11-19)
2
7
 
3
8
  === Minor Changes
4
- * Ignores ruby_parser errors and pretneds the offending file was empty
9
+ * Ignores ruby_parser errors and pretends the offending file was empty
5
10
 
6
11
  == 1.2.4 (2009-11-17)
7
12
 
@@ -13,6 +13,8 @@ Feature: Basic smell detection
13
13
  spec/samples/inline.rb -- 39 warnings (+1 masked):
14
14
  Inline declares the class variable @@directory (Class Variable)
15
15
  Inline declares the class variable @@rootdir (Class Variable)
16
+ Inline#self.rootdir calls env.nil? twice (Duplication)
17
+ Inline#self.rootdir has approx 8 statements (Long Method)
16
18
  Inline::C declares the class variable @@type_map (Class Variable)
17
19
  Inline::C has at least 13 instance variables (Large Class)
18
20
  Inline::C takes parameters [options, src] to 5 methods (Data Clump)
@@ -45,8 +47,6 @@ Feature: Basic smell detection
45
47
  Inline::C#parse_signature/block has the variable name 'x' (Uncommunicative Name)
46
48
  Inline::C#strip_comments doesn't depend on instance state (Utility Function)
47
49
  Inline::C#strip_comments refers to src more than self (Feature Envy)
48
- Inline::self.rootdir calls env.nil? twice (Duplication)
49
- Inline::self.rootdir has approx 8 statements (Long Method)
50
50
  Module#inline calls Inline.const_get(lang) twice (Duplication)
51
51
  Module#inline has approx 11 statements (Long Method)
52
52
  Module#inline is controlled by argument options (Control Couple)
@@ -65,70 +65,6 @@ Feature: Basic smell detection
65
65
  OptionParser tests default_pattern at least 7 times (Simulated Polymorphism)
66
66
  OptionParser tests not_style at least 3 times (Simulated Polymorphism)
67
67
  OptionParser tests s at least 7 times (Simulated Polymorphism)
68
- OptionParser#CompletingHash#match/block/block is nested (Nested Iterators)
69
- OptionParser#Completion::complete calls candidates.size twice (Duplication)
70
- OptionParser#Completion::complete calls k.id2name twice (Duplication)
71
- OptionParser#Completion::complete has approx 22 statements (Long Method)
72
- OptionParser#Completion::complete has the variable name 'k' (Uncommunicative Name)
73
- OptionParser#Completion::complete has the variable name 'v' (Uncommunicative Name)
74
- OptionParser#Completion::complete is controlled by argument icase (Control Couple)
75
- OptionParser#Completion::complete refers to candidates more than self (Feature Envy)
76
- OptionParser#Completion::complete/block has the variable name 'k' (Uncommunicative Name)
77
- OptionParser#Completion::complete/block has the variable name 'v' (Uncommunicative Name)
78
- OptionParser#List#accept has the variable name 't' (Uncommunicative Name)
79
- OptionParser#List#accept is controlled by argument pat (Control Couple)
80
- OptionParser#List#accept refers to pat more than self (Feature Envy)
81
- OptionParser#List#add_banner refers to opt more than self (Feature Envy)
82
- OptionParser#List#complete has 4 parameters (Long Parameter List)
83
- OptionParser#List#complete is controlled by argument icase (Control Couple)
84
- OptionParser#List#reject has the variable name 't' (Uncommunicative Name)
85
- OptionParser#List#summarize refers to opt more than self (Feature Envy)
86
- OptionParser#List#update has 5 parameters (Long Parameter List)
87
- OptionParser#List#update has approx 6 statements (Long Method)
88
- OptionParser#List#update has the variable name 'o' (Uncommunicative Name)
89
- OptionParser#List#update is controlled by argument lopts (Control Couple)
90
- OptionParser#List#update is controlled by argument sopts (Control Couple)
91
- OptionParser#List#update/block has the variable name 'o' (Uncommunicative Name)
92
- OptionParser#ParseError#set_option is controlled by argument eq (Control Couple)
93
- OptionParser#Switch#NoArgument#parse is controlled by argument arg (Control Couple)
94
- OptionParser#Switch#OptionalArgument#parse is controlled by argument arg (Control Couple)
95
- OptionParser#Switch#PlacedArgument#parse has approx 6 statements (Long Method)
96
- OptionParser#Switch#RequiredArgument#parse is controlled by argument arg (Control Couple)
97
- OptionParser#Switch#add_banner has the variable name 's' (Uncommunicative Name)
98
- OptionParser#Switch#conv_arg calls conv twice (Duplication)
99
- OptionParser#Switch#initialize has 7 parameters (Long Parameter List)
100
- OptionParser#Switch#parse_arg calls pattern twice (Duplication)
101
- OptionParser#Switch#parse_arg calls s.length twice (Duplication)
102
- OptionParser#Switch#parse_arg has approx 11 statements (Long Method)
103
- OptionParser#Switch#parse_arg has the variable name 'm' (Uncommunicative Name)
104
- OptionParser#Switch#parse_arg has the variable name 's' (Uncommunicative Name)
105
- OptionParser#Switch#self.guess has the variable name 't' (Uncommunicative Name)
106
- OptionParser#Switch#self.incompatible_argument_styles has the variable name 't' (Uncommunicative Name)
107
- OptionParser#Switch#summarize calls (indent + l) twice (Duplication)
108
- OptionParser#Switch#summarize calls arg 4 times (Duplication)
109
- OptionParser#Switch#summarize calls left.collect twice (Duplication)
110
- OptionParser#Switch#summarize calls left.collect { |s| s.length }.max twice (Duplication)
111
- OptionParser#Switch#summarize calls left.collect { |s| s.length }.max.to_i twice (Duplication)
112
- OptionParser#Switch#summarize calls left.shift twice (Duplication)
113
- OptionParser#Switch#summarize calls left[-1] 3 times (Duplication)
114
- OptionParser#Switch#summarize calls s.length 3 times (Duplication)
115
- OptionParser#Switch#summarize has 5 parameters (Long Parameter List)
116
- OptionParser#Switch#summarize has approx 25 statements (Long Method)
117
- OptionParser#Switch#summarize has the variable name 'l' (Uncommunicative Name)
118
- OptionParser#Switch#summarize has the variable name 'r' (Uncommunicative Name)
119
- OptionParser#Switch#summarize has the variable name 's' (Uncommunicative Name)
120
- OptionParser#Switch#summarize/block has the variable name 's' (Uncommunicative Name)
121
- OptionParser#Switch#summarize/block/block is nested (Nested Iterators)
122
- OptionParser#block has the variable name 'f' (Uncommunicative Name)
123
- OptionParser#block has the variable name 'k' (Uncommunicative Name)
124
- OptionParser#block has the variable name 'o' (Uncommunicative Name)
125
- OptionParser#block has the variable name 's' (Uncommunicative Name)
126
- OptionParser#block is controlled by argument o (Control Couple)
127
- OptionParser#block is controlled by argument s (Control Couple)
128
- OptionParser#block/block has the variable name 's' (Uncommunicative Name)
129
- OptionParser#block/block has the variable name 'v' (Uncommunicative Name)
130
- OptionParser#block/block is controlled by argument pkg (Control Couple)
131
- OptionParser#block/block is nested (Nested Iterators)
132
68
  OptionParser#complete has 4 parameters (Long Parameter List)
133
69
  OptionParser#complete is controlled by argument icase (Control Couple)
134
70
  OptionParser#complete/block/block is nested (Nested Iterators)
@@ -182,6 +118,70 @@ Feature: Basic smell detection
182
118
  OptionParser#summarize has 4 parameters (Long Parameter List)
183
119
  OptionParser#summarize/block has the variable name 'l' (Uncommunicative Name)
184
120
  OptionParser#ver has the variable name 'v' (Uncommunicative Name)
121
+ OptionParser/block has the variable name 'f' (Uncommunicative Name)
122
+ OptionParser/block has the variable name 'k' (Uncommunicative Name)
123
+ OptionParser/block has the variable name 'o' (Uncommunicative Name)
124
+ OptionParser/block has the variable name 's' (Uncommunicative Name)
125
+ OptionParser/block is controlled by argument o (Control Couple)
126
+ OptionParser/block is controlled by argument s (Control Couple)
127
+ OptionParser/block/block has the variable name 's' (Uncommunicative Name)
128
+ OptionParser/block/block has the variable name 'v' (Uncommunicative Name)
129
+ OptionParser/block/block is controlled by argument pkg (Control Couple)
130
+ OptionParser/block/block is nested (Nested Iterators)
131
+ OptionParser::CompletingHash#match/block/block is nested (Nested Iterators)
132
+ OptionParser::Completion#complete calls candidates.size twice (Duplication)
133
+ OptionParser::Completion#complete calls k.id2name twice (Duplication)
134
+ OptionParser::Completion#complete has approx 22 statements (Long Method)
135
+ OptionParser::Completion#complete has the variable name 'k' (Uncommunicative Name)
136
+ OptionParser::Completion#complete has the variable name 'v' (Uncommunicative Name)
137
+ OptionParser::Completion#complete is controlled by argument icase (Control Couple)
138
+ OptionParser::Completion#complete refers to candidates more than self (Feature Envy)
139
+ OptionParser::Completion#complete/block has the variable name 'k' (Uncommunicative Name)
140
+ OptionParser::Completion#complete/block has the variable name 'v' (Uncommunicative Name)
141
+ OptionParser::List#accept has the variable name 't' (Uncommunicative Name)
142
+ OptionParser::List#accept is controlled by argument pat (Control Couple)
143
+ OptionParser::List#accept refers to pat more than self (Feature Envy)
144
+ OptionParser::List#add_banner refers to opt more than self (Feature Envy)
145
+ OptionParser::List#complete has 4 parameters (Long Parameter List)
146
+ OptionParser::List#complete is controlled by argument icase (Control Couple)
147
+ OptionParser::List#reject has the variable name 't' (Uncommunicative Name)
148
+ OptionParser::List#summarize refers to opt more than self (Feature Envy)
149
+ OptionParser::List#update has 5 parameters (Long Parameter List)
150
+ OptionParser::List#update has approx 6 statements (Long Method)
151
+ OptionParser::List#update has the variable name 'o' (Uncommunicative Name)
152
+ OptionParser::List#update is controlled by argument lopts (Control Couple)
153
+ OptionParser::List#update is controlled by argument sopts (Control Couple)
154
+ OptionParser::List#update/block has the variable name 'o' (Uncommunicative Name)
155
+ OptionParser::ParseError#set_option is controlled by argument eq (Control Couple)
156
+ OptionParser::Switch#add_banner has the variable name 's' (Uncommunicative Name)
157
+ OptionParser::Switch#conv_arg calls conv twice (Duplication)
158
+ OptionParser::Switch#initialize has 7 parameters (Long Parameter List)
159
+ OptionParser::Switch#parse_arg calls pattern twice (Duplication)
160
+ OptionParser::Switch#parse_arg calls s.length twice (Duplication)
161
+ OptionParser::Switch#parse_arg has approx 11 statements (Long Method)
162
+ OptionParser::Switch#parse_arg has the variable name 'm' (Uncommunicative Name)
163
+ OptionParser::Switch#parse_arg has the variable name 's' (Uncommunicative Name)
164
+ OptionParser::Switch#self.guess has the variable name 't' (Uncommunicative Name)
165
+ OptionParser::Switch#self.incompatible_argument_styles has the variable name 't' (Uncommunicative Name)
166
+ OptionParser::Switch#summarize calls (indent + l) twice (Duplication)
167
+ OptionParser::Switch#summarize calls arg 4 times (Duplication)
168
+ OptionParser::Switch#summarize calls left.collect twice (Duplication)
169
+ OptionParser::Switch#summarize calls left.collect { |s| s.length }.max twice (Duplication)
170
+ OptionParser::Switch#summarize calls left.collect { |s| s.length }.max.to_i twice (Duplication)
171
+ OptionParser::Switch#summarize calls left.shift twice (Duplication)
172
+ OptionParser::Switch#summarize calls left[-1] 3 times (Duplication)
173
+ OptionParser::Switch#summarize calls s.length 3 times (Duplication)
174
+ OptionParser::Switch#summarize has 5 parameters (Long Parameter List)
175
+ OptionParser::Switch#summarize has approx 25 statements (Long Method)
176
+ OptionParser::Switch#summarize has the variable name 'l' (Uncommunicative Name)
177
+ OptionParser::Switch#summarize has the variable name 'r' (Uncommunicative Name)
178
+ OptionParser::Switch#summarize has the variable name 's' (Uncommunicative Name)
179
+ OptionParser::Switch#summarize/block has the variable name 's' (Uncommunicative Name)
180
+ OptionParser::Switch#summarize/block/block is nested (Nested Iterators)
181
+ OptionParser::Switch::NoArgument#parse is controlled by argument arg (Control Couple)
182
+ OptionParser::Switch::OptionalArgument#parse is controlled by argument arg (Control Couple)
183
+ OptionParser::Switch::PlacedArgument#parse has approx 6 statements (Long Method)
184
+ OptionParser::Switch::RequiredArgument#parse is controlled by argument arg (Control Couple)
185
185
  block has the variable name 'q' (Uncommunicative Name)
186
186
 
187
187
  """
@@ -198,8 +198,6 @@ Feature: Basic smell detection
198
198
  RedCloth tests codepre.zero? at least 3 times (Simulated Polymorphism)
199
199
  RedCloth tests href at least 3 times (Simulated Polymorphism)
200
200
  RedCloth tests title at least 4 times (Simulated Polymorphism)
201
- RedCloth#block has the variable name 'a' (Uncommunicative Name)
202
- RedCloth#block has the variable name 'b' (Uncommunicative Name)
203
201
  RedCloth#block_markdown_atx refers to text more than self (Feature Envy)
204
202
  RedCloth#block_markdown_bq has approx 6 statements (Long Method)
205
203
  RedCloth#block_markdown_rule refers to text more than self (Feature Envy)
@@ -288,5 +286,7 @@ Feature: Basic smell detection
288
286
  RedCloth#textile_p has 4 parameters (Long Parameter List)
289
287
  RedCloth#textile_p is controlled by argument atts (Control Couple)
290
288
  RedCloth#to_html has approx 24 statements (Long Method)
289
+ RedCloth/block has the variable name 'a' (Uncommunicative Name)
290
+ RedCloth/block has the variable name 'b' (Uncommunicative Name)
291
291
 
292
292
  """
@@ -41,3 +41,19 @@ Feature: Reek reads from $stdin when no files are given
41
41
  Turn#y has the name 'y' (Uncommunicative Name)
42
42
 
43
43
  """
44
+
45
+ @stderr
46
+ Scenario: syntax error causes the source to be ignored
47
+ When I pass "def incomplete" to reek
48
+ Then it succeeds
49
+ And it reports:
50
+ """
51
+ $stdin -- 0 warnings
52
+
53
+ """
54
+ And stderr reports:
55
+ """
56
+ $stdin: Racc::ParseError:
57
+ parse error on value "$end" ($end)
58
+
59
+ """
@@ -30,6 +30,10 @@ Then /^it reports:$/ do |report|
30
30
  @last_stdout.should == report
31
31
  end
32
32
 
33
+ Then /^stderr reports:$/ do |report|
34
+ @last_stderr.should == report
35
+ end
36
+
33
37
  Then /^it reports the error ['"](.*)['"]$/ do |string|
34
38
  @last_stderr.chomp.should == string
35
39
  end
@@ -1,5 +1,5 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
2
 
3
3
  module Reek # :doc:
4
- VERSION = '1.2.5'
4
+ VERSION = '1.2.6'
5
5
  end
@@ -9,6 +9,16 @@ module Reek
9
9
  #
10
10
  class Source
11
11
 
12
+ @@err_io = $stderr
13
+
14
+ class << self
15
+ def err_io=(io)
16
+ original = @@err_io
17
+ @@err_io = io
18
+ original
19
+ end
20
+ end
21
+
12
22
  attr_reader :desc
13
23
 
14
24
  def initialize(code, desc, parser = RubyParser.new)
@@ -23,7 +33,7 @@ module Reek
23
33
  begin
24
34
  ast = @parser.parse(@source, @desc)
25
35
  rescue Exception => error
26
- $stderr.puts "#{desc}: #{error.class.name}: #{error}"
36
+ @@err_io.puts "#{desc}: #{error.class.name}: #{error}"
27
37
  end
28
38
  ast ||= s()
29
39
  TreeDresser.new.dress(ast)
@@ -38,6 +38,7 @@ module Reek
38
38
  def initialize(outer, exp)
39
39
  super
40
40
  @name = Name.new('block')
41
+ @scope_connector = '/'
41
42
  @parameters = exp[2] || []
42
43
  @parameters.extend(ParameterSet)
43
44
  end
@@ -53,10 +54,6 @@ module Reek
53
54
  def nested_block?
54
55
  @outer.inside_a_block?
55
56
  end
56
-
57
- def outer_name
58
- "#{@outer.outer_name}#{@name}/"
59
- end
60
57
 
61
58
  def variable_names
62
59
  @parameters.names + @local_variables.to_a
@@ -34,10 +34,6 @@ module Reek
34
34
  @instance_variables << Name.new(sym)
35
35
  end
36
36
 
37
- def outer_name
38
- "#{@outer.outer_name}#{@name}#"
39
- end
40
-
41
37
  def variable_names
42
38
  @instance_variables
43
39
  end
@@ -20,6 +20,7 @@ module Reek
20
20
  def initialize(outer, exp)
21
21
  @outer = outer
22
22
  @exp = exp
23
+ @scope_connector = ''
23
24
  @myself = nil
24
25
  end
25
26
 
@@ -42,7 +43,7 @@ module Reek
42
43
  me = @name.to_s
43
44
  strings.any? do |str|
44
45
  re = /#{str}/
45
- re === me or re === self.to_s
46
+ re === me or re === self.full_name
46
47
  end
47
48
  end
48
49
 
@@ -58,12 +59,10 @@ module Reek
58
59
  0
59
60
  end
60
61
 
61
- def outer_name
62
- "#{@name}/"
63
- end
64
-
65
- def to_s
66
- "#{@outer.outer_name}#{@name}"
62
+ def full_name
63
+ outer = @outer ? @outer.full_name : ''
64
+ prefix = outer == '' ? '' : "#{outer}#{@scope_connector}"
65
+ "#{prefix}#{@name}"
67
66
  end
68
67
  end
69
68
  end
@@ -6,19 +6,13 @@ module Reek
6
6
 
7
7
  def initialize(outer, exp)
8
8
  super
9
+ @name = ''
10
+ @scope_connector = ''
9
11
  @if_expr = exp[1]
10
12
  end
11
13
 
12
14
  def tests_a_parameter?
13
15
  @if_expr[0] == :lvar and has_parameter(@if_expr[1])
14
16
  end
15
-
16
- def outer_name
17
- @outer.outer_name
18
- end
19
-
20
- def to_s # SMELL: should be unnecessary :(
21
- @outer.to_s
22
- end
23
17
  end
24
18
  end
@@ -76,6 +76,7 @@ module Reek
76
76
  @parameters ||= []
77
77
  @parameters.extend(MethodParameters)
78
78
  @name = Name.new(exp[1])
79
+ @scope_connector = '#'
79
80
  @num_statements = 0
80
81
  @calls = Hash.new(0)
81
82
  @depends_on_self = false
@@ -125,10 +126,6 @@ module Reek
125
126
  @depends_on_self = true
126
127
  end
127
128
 
128
- def outer_name
129
- "#{@outer.outer_name}#{@name}/"
130
- end
131
-
132
129
  def envious_receivers
133
130
  return [] if @refs.self_is_max?
134
131
  @refs.max_keys
@@ -21,6 +21,7 @@ module Reek
21
21
  def initialize(outer, name, exp)
22
22
  super(outer, exp)
23
23
  @name = name
24
+ @scope_connector = '::'
24
25
  @parsed_methods = []
25
26
  end
26
27
 
@@ -41,10 +42,6 @@ module Reek
41
42
  @parsed_methods << meth
42
43
  end
43
44
 
44
- def outer_name
45
- "#{@outer.outer_name}#{@name}::"
46
- end
47
-
48
45
  def variable_names
49
46
  []
50
47
  end
@@ -9,19 +9,18 @@ module Reek
9
9
  super(outer, exp)
10
10
  @name = Name.new(exp[2])
11
11
  @receiver = SexpFormatter.format(exp[1])
12
+ @scope_connector = ""
12
13
  record_depends_on_self
13
14
  end
14
15
 
15
16
  def envious_receivers
16
17
  []
17
18
  end
18
-
19
- def outer_name
20
- "#{@outer.outer_name}#{@receiver}.#{@name}/"
21
- end
22
19
 
23
- def to_s
24
- "#{@outer.outer_name}#{@receiver}.#{@name}"
20
+ def full_name
21
+ outer = @outer ? @outer.full_name : ''
22
+ prefix = outer == '' ? '' : "#{outer}#"
23
+ "#{prefix}#{@receiver}.#{@name}"
25
24
  end
26
25
  end
27
26
  end
@@ -6,10 +6,11 @@ module Reek
6
6
  class SmellWarning
7
7
  include Comparable
8
8
 
9
- def initialize(smell, context, warning, masked)
10
- @detector = smell
9
+ def initialize(detector_class, context, line, message, masked)
10
+ @smell = detector_class.class.name.split(/::/)[-1]
11
11
  @context = context
12
- @warning = warning
12
+ @line = line
13
+ @message = message
13
14
  @is_masked = masked
14
15
  end
15
16
 
@@ -25,28 +26,19 @@ module Reek
25
26
  (self <=> other) == 0
26
27
  end
27
28
 
28
- #
29
- # Returns +true+ only if this is a warning about an instance of
30
- # +smell_class+ and its report string matches all of the +patterns+.
31
- #
32
- def matches?(smell_class, patterns)
33
- return false unless smell_class.to_s == @detector.class.class_name
34
- contains_all?(patterns)
35
- end
36
-
37
29
  def contains_all?(patterns)
38
30
  rpt = sort_key.to_s
39
31
  return patterns.all? {|exp| exp === rpt}
40
32
  end
41
33
 
42
34
  def sort_key
43
- [@context.to_s, @warning, @detector.smell_name]
35
+ [@context, @message, smell_name]
44
36
  end
45
37
 
46
38
  protected :sort_key
47
39
 
48
40
  def report(format)
49
- format.gsub(/\%s/, @detector.smell_name).gsub(/\%c/, @context.to_s).gsub(/\%w/, @warning).gsub(/\%m/, @is_masked ? '(masked) ' : '')
41
+ format.gsub(/\%s/, smell_name).gsub(/\%c/, @context).gsub(/\%w/, @message).gsub(/\%m/, @is_masked ? '(masked) ' : '')
50
42
  end
51
43
 
52
44
  def report_on(report)
@@ -56,5 +48,9 @@ module Reek
56
48
  report.found_smell(self)
57
49
  end
58
50
  end
51
+
52
+ def smell_name
53
+ @smell.gsub(/([a-z])([A-Z])/) { |sub| "#{$1} #{$2}"}.split.join(' ')
54
+ end
59
55
  end
60
56
  end
@@ -1,12 +1,5 @@
1
1
  require 'reek/configuration'
2
2
 
3
- class Class
4
- def name_words
5
- class_name = name.split(/::/)[-1]
6
- class_name.gsub(/([a-z])([A-Z])/) { |sub| "#{$1} #{$2}"}.split
7
- end
8
- end
9
-
10
3
  module Reek
11
4
  module Smells
12
5
 
@@ -92,8 +85,8 @@ module Reek
92
85
  context.matches?(value(EXCLUDE_KEY, context, DEFAULT_EXCLUDE_SET))
93
86
  end
94
87
 
95
- def found(scope, warning)
96
- smell = SmellWarning.new(self, scope, warning, @masked)
88
+ def found(context, message)
89
+ smell = SmellWarning.new(self, context.full_name, context.exp.line, message, @masked)
97
90
  @smells_found << smell
98
91
  smell
99
92
  end
@@ -116,10 +109,6 @@ module Reek
116
109
  (not @masked) and (@smells_found.length > 0)
117
110
  end
118
111
 
119
- def smell_name
120
- self.class.name_words.join(' ')
121
- end
122
-
123
112
  def value(key, ctx, fall_back)
124
113
  @config.value(key, ctx, fall_back)
125
114
  end
@@ -65,11 +65,15 @@ module Reek
65
65
 
66
66
  def consider_name(context) # :nodoc:
67
67
  name = context.name
68
- return false if value(ACCEPT_KEY, context, DEFAULT_ACCEPT_SET).include?(context.to_s) # TODO: fq_name() ?
68
+ return false if accept?(context)
69
69
  return false unless is_bad_name?(name, context)
70
70
  found(context, "has the name '#{name}'")
71
71
  end
72
72
 
73
+ def accept?(context)
74
+ value(ACCEPT_KEY, context, DEFAULT_ACCEPT_SET).include?(context.full_name)
75
+ end
76
+
73
77
  def is_bad_name?(name, context) # :nodoc:
74
78
  var = name.effective_name
75
79
  return false if var == '*' or value(ACCEPT_KEY, context, DEFAULT_ACCEPT_SET).include?(var)
@@ -3,6 +3,7 @@ module Reek
3
3
 
4
4
  def initialize
5
5
  @refs = ObjectRefs.new
6
+ @name = ''
6
7
  @myself = Object
7
8
  end
8
9
 
@@ -18,6 +19,10 @@ module Reek
18
19
  sym = name.to_s
19
20
  @myself.const_defined?(sym) ? @myself.const_get(sym) : nil
20
21
  end
22
+
23
+ def full_name
24
+ ''
25
+ end
21
26
 
22
27
  def inside_a_block?
23
28
  false
@@ -26,9 +31,5 @@ module Reek
26
31
  def is_overriding_method?(name)
27
32
  false
28
33
  end
29
-
30
- def outer_name
31
- ''
32
- end
33
34
  end
34
35
  end
@@ -2,13 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{reek}
5
- s.version = "1.2.5"
5
+ s.version = "1.2.6"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Kevin Rutherford"]
9
- s.date = %q{2009-11-19}
9
+ s.date = %q{2009-11-28}
10
10
  s.default_executable = %q{reek}
11
- s.description = %q{Code smell detector for Ruby}
11
+ s.description = %q{Reek is a tool that examines Ruby classes, modules and methods
12
+ and reports any code smells it finds.
13
+ }
12
14
  s.email = ["kevin@rutherford-software.com"]
13
15
  s.executables = ["reek"]
14
16
  s.extra_rdoc_files = ["History.txt", "License.txt", "README.rdoc"]
@@ -1,16 +1,41 @@
1
1
  require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
2
 
3
3
  require 'reek/adapters/source'
4
+ require 'stringio'
4
5
 
5
6
  include Reek
6
7
 
7
8
  describe Source do
8
9
  context 'when the parser fails' do
9
- it 'returns an empty syntax tree' do
10
+ before :each do
11
+ @catcher = StringIO.new
12
+ @old_err_io = (Source.err_io = @catcher)
10
13
  parser = mock('parser')
11
- parser.should_receive(:parse).and_raise(SyntaxError)
12
- src = Source.new('', '', parser)
13
- src.syntax_tree.should == s()
14
+ @error_message = 'Error message'
15
+ parser.should_receive(:parse).and_raise(SyntaxError.new(@error_message))
16
+ @source_name = 'Test source'
17
+ @src = Source.new('', @source_name, parser)
18
+ end
19
+ it 'raises a SyntaxError' do
20
+ @src.syntax_tree
21
+ end
22
+ it 'returns an empty syntax tree' do
23
+ @src.syntax_tree.should == s()
24
+ end
25
+ it 'records the syntax error' do
26
+ @src.syntax_tree
27
+ @catcher.string.should match(SyntaxError.name)
28
+ end
29
+ it 'records the source name' do
30
+ @src.syntax_tree
31
+ @catcher.string.should match(@source_name)
32
+ end
33
+ it 'records the error message' do
34
+ @src.syntax_tree
35
+ @catcher.string.should match(@error_message)
36
+ end
37
+ after :each do
38
+ Source.err_io = @old_err_io
14
39
  end
15
40
  end
16
41
  end
@@ -43,4 +43,23 @@ describe BlockContext do
43
43
  scope.record_instance_variable(:@list)
44
44
  scope.variable_names.should == [:@list]
45
45
  end
46
+
47
+ context 'full_name' do
48
+ it "reports full context" do
49
+ bctx = BlockContext.new(StopContext.new, s(nil, nil))
50
+ bctx.full_name.should == 'block'
51
+ end
52
+ it 'uses / to connect to the class name' do
53
+ element = StopContext.new
54
+ element = ClassContext.new(element, :Fred, s(:class, :Fred))
55
+ element = BlockContext.new(element, s(:iter, nil, s(:lasgn, :x), nil))
56
+ element.full_name.should == 'Fred/block'
57
+ end
58
+ it 'uses / to connect to the module name' do
59
+ element = StopContext.new
60
+ element = ModuleContext.new(element, :Fred, s(:module, :Fred))
61
+ element = BlockContext.new(element, s(:iter, nil, s(:lasgn, :x), nil))
62
+ element.full_name.should == 'Fred/block'
63
+ end
64
+ end
46
65
  end
@@ -10,30 +10,37 @@ require 'reek/stop_context'
10
10
  include Reek
11
11
 
12
12
  describe CodeContext do
13
- context 'to_s' do
13
+ context 'full_name' do
14
14
  it "reports the full context" do
15
15
  element = StopContext.new
16
16
  element = ModuleContext.new(element, Name.new(:mod), s(:module, :mod, nil))
17
17
  element = ClassContext.new(element, [0, :klass], s())
18
18
  element = MethodContext.new(element, [0, :bad])
19
19
  element = BlockContext.new(element, s(nil, nil))
20
- element.to_s.should match(/bad/)
21
- element.to_s.should match(/klass/)
22
- element.to_s.should match(/mod/)
20
+ element.full_name.should match(/bad/)
21
+ element.full_name.should match(/klass/)
22
+ element.full_name.should match(/mod/)
23
23
  end
24
24
 
25
25
  it 'reports the method name via if context' do
26
26
  element1 = StopContext.new
27
27
  element2 = MethodContext.new(element1, [0, :bad])
28
28
  element3 = IfContext.new(element2, [0,1])
29
- BlockContext.new(element3, s(nil, nil)).to_s.should match(/bad/)
29
+ BlockContext.new(element3, s(nil, nil)).full_name.should match(/bad/)
30
30
  end
31
31
 
32
32
  it 'reports the method name via nested blocks' do
33
33
  element1 = StopContext.new
34
34
  element2 = MethodContext.new(element1, [0, :bad])
35
35
  element3 = BlockContext.new(element2, s(nil, nil))
36
- BlockContext.new(element3, s(nil, nil)).to_s.should match(/bad/)
36
+ BlockContext.new(element3, s(nil, nil)).full_name.should match(/bad/)
37
+ end
38
+ it 'includes the enclosing context name' do
39
+ outer_name = 'randomstring'
40
+ outer = mock('outer')
41
+ outer.should_receive(:full_name).and_return(outer_name)
42
+ ifc = IfContext.new(outer, s(:if, s()))
43
+ ifc.full_name.should == outer_name
37
44
  end
38
45
  end
39
46
 
@@ -5,11 +5,11 @@ require 'reek/if_context'
5
5
  include Reek
6
6
 
7
7
  describe IfContext do
8
- it 'should find a class within top-level code' do
8
+ it 'finds a class within top-level code' do
9
9
  'unless jim; class Array; end; end'.should_not reek
10
10
  end
11
11
 
12
- it 'should find class within top-level code' do
12
+ it 'finds class within top-level code' do
13
13
  stopctx = StopContext.new
14
14
  ifctx = IfContext.new(stopctx, [:if, [:vcall, :jim]])
15
15
  ifctx.find_module(Name.new(:Array)).should_not be_nil
@@ -35,4 +35,12 @@ describe ModuleContext do
35
35
  element = ModuleContext.create(StopContext.new, [:module, [:colon3, :GlobalTestModule], nil])
36
36
  element.myself.name.should == 'GlobalTestModule'
37
37
  end
38
+
39
+ context 'full_name' do
40
+ it "reports full context" do
41
+ element = StopContext.new
42
+ element = ModuleContext.new(element, Name.new(:mod), s(:module, :mod, nil))
43
+ element.full_name.should == 'mod'
44
+ end
45
+ end
38
46
  end
@@ -6,12 +6,11 @@ require 'reek/stop_context'
6
6
 
7
7
  include Reek
8
8
 
9
- describe SingletonMethodContext, 'outer_name' do
10
-
11
- it "should report full context" do
9
+ describe SingletonMethodContext do
10
+ it "reports full context" do
12
11
  element = StopContext.new
13
12
  element = ModuleContext.new(element, Name.new(:mod), s(:module, :mod, nil))
14
13
  element = SingletonMethodContext.new(element, s(:defs, s(:call, nil, :a, s(:arglist)), :b, s(:args)))
15
- element.outer_name.should match(/mod::a\.b/)
14
+ element.full_name.should match(/mod#a\.b/)
16
15
  end
17
16
  end
@@ -5,12 +5,12 @@ require 'reek/smells/feature_envy'
5
5
 
6
6
  include Reek
7
7
 
8
- describe SmellWarning, 'equality' do
8
+ describe SmellWarning do
9
9
  context 'sort order' do
10
10
  context 'smells differing only by masking' do
11
11
  before :each do
12
- @first = SmellWarning.new(Smells::FeatureEnvy.new, "self", "self", true)
13
- @second = SmellWarning.new(Smells::FeatureEnvy.new, "self", "self", false)
12
+ @first = SmellWarning.new(Smells::FeatureEnvy.new, "self", 27, "self", true)
13
+ @second = SmellWarning.new(Smells::FeatureEnvy.new, "self", 27, "self", false)
14
14
  end
15
15
 
16
16
  it 'should hash equal when the smell is the same' do
@@ -46,8 +46,8 @@ describe SmellWarning, 'equality' do
46
46
 
47
47
  context 'smells differing only by detector' do
48
48
  before :each do
49
- @first = SmellWarning.new(Smells::Duplication.new, "self", "self", false)
50
- @second = SmellWarning.new(Smells::FeatureEnvy.new, "self", "self", true)
49
+ @first = SmellWarning.new(Smells::Duplication.new, "self", 27, "self", false)
50
+ @second = SmellWarning.new(Smells::FeatureEnvy.new, "self", 27, "self", true)
51
51
  end
52
52
 
53
53
  it_should_behave_like 'first sorts ahead of second'
@@ -55,8 +55,8 @@ describe SmellWarning, 'equality' do
55
55
 
56
56
  context 'smells differing only by context' do
57
57
  before :each do
58
- @first = SmellWarning.new(Smells::FeatureEnvy.new, "first", "self", true)
59
- @second = SmellWarning.new(Smells::FeatureEnvy.new, "second", "self", false)
58
+ @first = SmellWarning.new(Smells::FeatureEnvy.new, "first", 27, "self", true)
59
+ @second = SmellWarning.new(Smells::FeatureEnvy.new, "second", 27, "self", false)
60
60
  end
61
61
 
62
62
  it_should_behave_like 'first sorts ahead of second'
@@ -64,8 +64,8 @@ describe SmellWarning, 'equality' do
64
64
 
65
65
  context 'smells differing only by message' do
66
66
  before :each do
67
- @first = SmellWarning.new(Smells::FeatureEnvy.new, "context", "first", true)
68
- @second = SmellWarning.new(Smells::FeatureEnvy.new, "context", "second", false)
67
+ @first = SmellWarning.new(Smells::FeatureEnvy.new, "context", 27, "first", true)
68
+ @second = SmellWarning.new(Smells::FeatureEnvy.new, "context", 27, "second", false)
69
69
  end
70
70
 
71
71
  it_should_behave_like 'first sorts ahead of second'
@@ -73,8 +73,8 @@ describe SmellWarning, 'equality' do
73
73
 
74
74
  context 'message takes precedence over smell name' do
75
75
  before :each do
76
- @first = SmellWarning.new(Smells::UtilityFunction.new, "context", "first", true)
77
- @second = SmellWarning.new(Smells::FeatureEnvy.new, "context", "second", false)
76
+ @first = SmellWarning.new(Smells::UtilityFunction.new, "context", 27, "first", true)
77
+ @second = SmellWarning.new(Smells::FeatureEnvy.new, "context", 27, "second", false)
78
78
  end
79
79
 
80
80
  it_should_behave_like 'first sorts ahead of second'
@@ -82,14 +82,14 @@ describe SmellWarning, 'equality' do
82
82
 
83
83
  context 'smells differing everywhere' do
84
84
  before :each do
85
- @first = SmellWarning.new(Smells::UncommunicativeName.new, "Dirty", "has the variable name '@s'", true)
86
- @second = SmellWarning.new(Smells::Duplication.new, 'Dirty#a', "calls @s.title twice", false)
85
+ @first = SmellWarning.new(Smells::UncommunicativeName.new, "Dirty", 27, "has the variable name '@s'", true)
86
+ @second = SmellWarning.new(Smells::Duplication.new, 'Dirty#a', 27, "calls @s.title twice", false)
87
87
  end
88
88
 
89
89
  it_should_behave_like 'first sorts ahead of second'
90
90
  end
91
91
  end
92
-
92
+
93
93
  context 'masked reporting' do
94
94
  class CountingReport
95
95
  attr_reader :masked, :non_masked
@@ -106,8 +106,8 @@ describe SmellWarning, 'equality' do
106
106
  end
107
107
 
108
108
  before :each do
109
- @masked = SmellWarning.new(Smells::FeatureEnvy.new, "self", "self", true)
110
- @visible = SmellWarning.new(Smells::FeatureEnvy.new, "self", "self", false)
109
+ @masked = SmellWarning.new(Smells::FeatureEnvy.new, 'Fred', 27, "self", true)
110
+ @visible = SmellWarning.new(Smells::FeatureEnvy.new, 'Fred', 27, "self", false)
111
111
  end
112
112
 
113
113
  it 'reports as masked when masked' do
@@ -124,4 +124,28 @@ describe SmellWarning, 'equality' do
124
124
  rpt.non_masked.should == 1
125
125
  end
126
126
  end
127
+
128
+ context 'YAML representation' do
129
+ before :each do
130
+ @message = 'message'
131
+ # Use a random string and a random bool
132
+ warning = SmellWarning.new(Smells::FeatureEnvy.new, 'Fred', 27, @message, true)
133
+ @yaml = warning.to_yaml
134
+ end
135
+ it 'includes the smell class' do
136
+ @yaml.should match(/smell:\s*FeatureEnvy/)
137
+ end
138
+ it 'includes the context' do
139
+ @yaml.should match(/context:\s*Fred/)
140
+ end
141
+ it 'includes the message' do
142
+ @yaml.should match(/message:\s*#{@message}/)
143
+ end
144
+ it 'indicates the masking' do
145
+ @yaml.should match(/is_masked:\s*true/)
146
+ end
147
+ it 'includes the line number' do
148
+ @yaml.should match(/line:\s*27/)
149
+ end
150
+ end
127
151
  end
@@ -119,18 +119,28 @@ EOS
119
119
  end
120
120
  end
121
121
 
122
- describe UncommunicativeName, '#examine' do
122
+ describe UncommunicativeName do
123
123
  before :each do
124
- @uc = UncommunicativeName.new
124
+ @detector = UncommunicativeName.new
125
125
  end
126
-
127
- it 'should return true when reporting a smell' do
128
- mc = MethodContext.new(StopContext.new, s(:defn, :x, s(:args)))
129
- @uc.examine(mc).should == true
126
+
127
+ context '#examine' do
128
+ it 'should return true when reporting a smell' do
129
+ mc = MethodContext.new(StopContext.new, s(:defn, :x, s(:args)))
130
+ @detector.examine(mc).should == true
131
+ end
132
+
133
+ it 'should return false when not reporting a smell' do
134
+ mc = MethodContext.new(StopContext.new, s(:defn, :not_bad, s(:args)))
135
+ @detector.examine(mc).should == false
136
+ end
130
137
  end
131
-
132
- it 'should return false when not reporting a smell' do
133
- mc = MethodContext.new(StopContext.new, s(:defn, :not_bad, s(:args)))
134
- @uc.examine(mc).should == false
138
+
139
+ context 'accepting names' do
140
+ it 'accepts Inline::C' do
141
+ ctx = mock('context')
142
+ ctx.should_receive(:full_name).and_return('Inline::C')
143
+ @detector.accept?(ctx).should == true
144
+ end
135
145
  end
136
146
  end
@@ -24,4 +24,10 @@ describe StopContext do
24
24
  @stop.find_module('Reek').name.should == 'Reek'
25
25
  end
26
26
  end
27
+
28
+ context 'full_name' do
29
+ it "reports full context" do
30
+ @stop.full_name.should == ''
31
+ end
32
+ end
27
33
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reek
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.5
4
+ version: 1.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Rutherford
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-19 00:00:00 +00:00
12
+ date: 2009-11-28 00:00:00 +00:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -42,7 +42,10 @@ dependencies:
42
42
  - !ruby/object:Gem::Version
43
43
  version: "3.0"
44
44
  version:
45
- description: Code smell detector for Ruby
45
+ description: |
46
+ Reek is a tool that examines Ruby classes, modules and methods
47
+ and reports any code smells it finds.
48
+
46
49
  email:
47
50
  - kevin@rutherford-software.com
48
51
  executables: