reek 1.2.5 → 1.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: