kevinrutherford-reek 0.3.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/History.txt +92 -0
  2. data/README.txt +6 -0
  3. data/Rakefile +7 -0
  4. data/bin/reek +19 -0
  5. data/lib/reek/block_context.rb +37 -0
  6. data/lib/reek/class_context.rb +73 -0
  7. data/lib/reek/code_context.rb +47 -0
  8. data/lib/reek/code_parser.rb +204 -0
  9. data/lib/reek/exceptions.reek +13 -0
  10. data/lib/reek/if_context.rb +25 -0
  11. data/lib/reek/method_context.rb +85 -0
  12. data/lib/reek/module_context.rb +34 -0
  13. data/lib/reek/name.rb +42 -0
  14. data/lib/reek/object_refs.rb +53 -0
  15. data/lib/reek/options.rb +92 -0
  16. data/lib/reek/rake_task.rb +121 -0
  17. data/lib/reek/report.rb +42 -0
  18. data/lib/reek/sexp_formatter.rb +52 -0
  19. data/lib/reek/singleton_method_context.rb +27 -0
  20. data/lib/reek/smell_warning.rb +49 -0
  21. data/lib/reek/smells/control_couple.rb +61 -0
  22. data/lib/reek/smells/duplication.rb +50 -0
  23. data/lib/reek/smells/feature_envy.rb +58 -0
  24. data/lib/reek/smells/large_class.rb +50 -0
  25. data/lib/reek/smells/long_method.rb +43 -0
  26. data/lib/reek/smells/long_parameter_list.rb +43 -0
  27. data/lib/reek/smells/long_yield_list.rb +18 -0
  28. data/lib/reek/smells/nested_iterators.rb +28 -0
  29. data/lib/reek/smells/smell_detector.rb +66 -0
  30. data/lib/reek/smells/smells.rb +85 -0
  31. data/lib/reek/smells/uncommunicative_name.rb +80 -0
  32. data/lib/reek/smells/utility_function.rb +34 -0
  33. data/lib/reek/source.rb +116 -0
  34. data/lib/reek/spec.rb +130 -0
  35. data/lib/reek/stop_context.rb +62 -0
  36. data/lib/reek/yield_call_context.rb +14 -0
  37. data/lib/reek.rb +8 -0
  38. data/spec/integration/reek_source_spec.rb +20 -0
  39. data/spec/integration/script_spec.rb +55 -0
  40. data/spec/reek/class_context_spec.rb +198 -0
  41. data/spec/reek/code_context_spec.rb +92 -0
  42. data/spec/reek/code_parser_spec.rb +44 -0
  43. data/spec/reek/config_spec.rb +42 -0
  44. data/spec/reek/module_context_spec.rb +38 -0
  45. data/spec/reek/object_refs_spec.rb +129 -0
  46. data/spec/reek/options_spec.rb +13 -0
  47. data/spec/reek/report_spec.rb +48 -0
  48. data/spec/reek/sexp_formatter_spec.rb +31 -0
  49. data/spec/reek/singleton_method_context_spec.rb +17 -0
  50. data/spec/reek/smells/control_couple_spec.rb +23 -0
  51. data/spec/reek/smells/duplication_spec.rb +81 -0
  52. data/spec/reek/smells/feature_envy_spec.rb +129 -0
  53. data/spec/reek/smells/large_class_spec.rb +86 -0
  54. data/spec/reek/smells/long_method_spec.rb +59 -0
  55. data/spec/reek/smells/long_parameter_list_spec.rb +92 -0
  56. data/spec/reek/smells/nested_iterators_spec.rb +33 -0
  57. data/spec/reek/smells/smell_spec.rb +24 -0
  58. data/spec/reek/smells/uncommunicative_name_spec.rb +118 -0
  59. data/spec/reek/smells/utility_function_spec.rb +96 -0
  60. data/spec/samples/inline.rb +704 -0
  61. data/spec/samples/inline_spec.rb +40 -0
  62. data/spec/samples/optparse.rb +1788 -0
  63. data/spec/samples/optparse_spec.rb +100 -0
  64. data/spec/samples/redcloth.rb +1130 -0
  65. data/spec/samples/redcloth_spec.rb +93 -0
  66. data/spec/spec.opts +1 -0
  67. data/spec/spec_helper.rb +15 -0
  68. data/tasks/reek.rake +20 -0
  69. data/tasks/rspec.rake +22 -0
  70. metadata +167 -0
data/History.txt ADDED
@@ -0,0 +1,92 @@
1
+ == 0.3.x 2009-??-??
2
+
3
+ === Major enhancements:
4
+
5
+ * Place *.reek files in source folder to configure smell detection behaviour
6
+ * Added -f option to configure report format
7
+ * --sort_order replaced by -f, -c and -s
8
+ * Matchers provided for rspec; eg. foo.should_not reek
9
+
10
+ === Minor enhancements:
11
+
12
+ * Smells in singleton methods are now analysed
13
+ * Uncommunicative parameter names in blocks now reported
14
+ * Modules and blocks now reflected in scope of smell reports
15
+
16
+ === Fixes:
17
+
18
+ * Corrected false reports of long arg lists to yield
19
+ * A method can now be a UtilityFunction only when it includes a call
20
+ * Removed the website from the gem [#12]
21
+
22
+ == 0.3.1 2008-11-17
23
+
24
+ * Minor enhancements:
25
+ * Uncommunicative Name now checks instance variables more thoroughly
26
+ * Uncommunicative Name now warns about names of the form 'x2'
27
+ * Added check for duplicated calls within a method
28
+ * Reduced scope of Feature Envy warnings to cover only overuse of lvars
29
+ * Added rdoc comments explaining what each smell is about
30
+ * Chained iterators are no longer mis-reported as nested
31
+
32
+ == 0.3.0 2008-11-02
33
+
34
+ * Minor enhancements:
35
+ * New smell: first naive checks for Control Couple
36
+ * reek now only checks sources passed on the command line
37
+ * Code snippets can be supplied on the commandline
38
+ * Added headings and warnings count when smells in multiple files
39
+ * Added Reek::RakeTask to run reek from rakefiles
40
+ * Tweaks:
41
+ * Fixed: Returns exit status 2 when smells are reported
42
+ * Fixed: no longer claims an empty method is a Utility Function
43
+
44
+ == 0.2.3 2008-09-22
45
+
46
+ * Minor enhancements:
47
+ * Only reports Feature Envy when the method isn't a Utility Function
48
+ * General improvements to assessing Feature Envy
49
+ * Tweaks:
50
+ * Fixed: coping with parameterless yield call
51
+ * Fixed: copes with :self as an expression
52
+ * Fixed: displaying the receiver of many more kinds of Feature Envy
53
+ * Fixed: Large Class calculation for Object
54
+
55
+ == 0.2.2 2008-09-15
56
+
57
+ * Tweaks:
58
+ * Fixed --version!
59
+
60
+ == 0.2.1 2008-09-14
61
+
62
+ * Tweaks:
63
+ * Now works from the source code, instead of requiring each named file
64
+ * Added integration tests that run reek on a couple of gems
65
+
66
+ == 0.2.0 2008-09-10
67
+
68
+ * Minor enhancements:
69
+ * Added --help, --version options
70
+ * Added --sort option to sort the report by smell or by code location
71
+
72
+ == 0.1.1 2008-09-09
73
+
74
+ * Some tweaks:
75
+ * Fixed report printing for Feature Envy when the receiver is a block
76
+ * Fixed: successive iterators reported as nested
77
+ * Fixed: Long Method now reports the total length of the method
78
+ * Fixed: each smell reported only once
79
+
80
+ == 0.1.0 2008-09-09
81
+
82
+ * 1 minor enhancement:
83
+ * Added a check for nested iterators within a method
84
+ * Some tweaks:
85
+ * Begun adding some rdoc
86
+ * Split some of the specs into more meaningful chunks
87
+ * Updated the rakefile so that rcov is no longer the default
88
+
89
+ == 0.0.1 2008-09-08
90
+
91
+ * 1 major enhancement:
92
+ * Initial release
data/README.txt ADDED
@@ -0,0 +1,6 @@
1
+ = Reek
2
+
3
+ Code smell detection for Ruby.
4
+
5
+ The documentation is at http://wiki.github.com/kevinrutherford/reek
6
+ The code lives at http://github.com/kevinrutherford/reek/tree
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'fileutils'
4
+
5
+ $:.unshift File.dirname(__FILE__) + '/lib'
6
+
7
+ Dir['tasks/**/*.rake'].each { |t| load t }
data/bin/reek ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Reek examines Ruby source code for smells.
4
+ # Visit http://reek.rubyforge.org/ for docs etc.
5
+ #
6
+ # Author: Kevin Rutherford
7
+ #
8
+
9
+ require 'reek'
10
+ require 'reek/source'
11
+ require 'reek/options'
12
+
13
+ exitstatus = 0
14
+ source = Reek::Options.parse(ARGV)
15
+ if source.smelly?
16
+ puts source.report
17
+ exitstatus = 2
18
+ end
19
+ exit(exitstatus)
@@ -0,0 +1,37 @@
1
+ require 'reek/code_context'
2
+
3
+ module Reek
4
+ class BlockContext < CodeContext
5
+
6
+ def initialize(outer, exp)
7
+ super
8
+ @parameters = []
9
+ @local_variables = []
10
+ @name = Name.new('block')
11
+ end
12
+
13
+ def inside_a_block?
14
+ true
15
+ end
16
+
17
+ def has_parameter(name)
18
+ @parameters.include?(name) or @outer.has_parameter(name)
19
+ end
20
+
21
+ def nested_block?
22
+ @outer.inside_a_block?
23
+ end
24
+
25
+ def record_parameter(sym)
26
+ @parameters << Name.new(sym)
27
+ end
28
+
29
+ def outer_name
30
+ "#{@outer.outer_name}#{@name}/"
31
+ end
32
+
33
+ def variable_names
34
+ @parameters + @local_variables
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,73 @@
1
+ require 'reek/code_context'
2
+
3
+ class Class
4
+ def non_inherited_methods
5
+ instance_methods(false) + private_instance_methods(false)
6
+ end
7
+
8
+ def is_overriding_method?(sym)
9
+ instance_methods(false).include?(sym) and superclass.instance_methods(true).include?(sym)
10
+ end
11
+ end
12
+
13
+ module Reek
14
+ class ClassContext < CodeContext
15
+
16
+ def ClassContext.create(outer, exp)
17
+ res = Name.resolve(exp[1], outer)
18
+ ClassContext.new(res[0], res[1], exp[2])
19
+ end
20
+
21
+ def initialize(outer, name, superclass = nil)
22
+ super(outer, nil)
23
+ @name = name
24
+ @superclass = superclass
25
+ @parsed_methods = []
26
+ @instance_variables = []
27
+ end
28
+
29
+ def myself
30
+ @myself ||= @outer.find_module(@name)
31
+ end
32
+
33
+ def find_module(modname)
34
+ sym = modname.to_s
35
+ return nil unless myself
36
+ myself.const_defined?(sym) ? myself.const_get(sym) : nil
37
+ end
38
+
39
+ def is_overriding_method?(name)
40
+ return false unless myself
41
+ myself.is_overriding_method?(name.to_s)
42
+ end
43
+
44
+ def is_struct?
45
+ @superclass == [:const, :Struct]
46
+ end
47
+
48
+ def num_methods
49
+ meths = myself ? myself.non_inherited_methods : @parsed_methods
50
+ meths.length
51
+ end
52
+
53
+ def record_instance_variable(sym)
54
+ @instance_variables << Name.new(sym)
55
+ end
56
+
57
+ def record_method(name)
58
+ @parsed_methods << name.to_s
59
+ end
60
+
61
+ def outer_name
62
+ "#{@outer.outer_name}#{@name}#"
63
+ end
64
+
65
+ def to_s
66
+ "#{@outer.outer_name}#{@name}"
67
+ end
68
+
69
+ def variable_names
70
+ @instance_variables
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,47 @@
1
+ module Reek
2
+
3
+ #
4
+ # Superclass for all types of source code context. Each instance represents
5
+ # a code element of some kind, and each provides behaviour relevant to that
6
+ # code element. CodeContexts form a tree in the same way the code does,
7
+ # with each context holding a reference to a unique outer context.
8
+ #
9
+ class CodeContext
10
+
11
+ attr_reader :name
12
+
13
+ def initialize(outer, exp)
14
+ @outer = outer
15
+ @exp = exp
16
+ @myself = nil
17
+ end
18
+
19
+ def matches?(strings)
20
+ me = @name.to_s
21
+ strings.any? do |str|
22
+ re = /#{str}/
23
+ re === me or re === self.to_s
24
+ end
25
+ end
26
+
27
+ #
28
+ # Bounces messages up the context tree to the first enclosing context
29
+ # that knows how to deal with the request.
30
+ #
31
+ def method_missing(method, *args)
32
+ @outer.send(method, *args)
33
+ end
34
+
35
+ def num_methods
36
+ 0
37
+ end
38
+
39
+ def outer_name
40
+ "#{@name}/"
41
+ end
42
+
43
+ def to_s
44
+ "#{@outer.outer_name}#{@name}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,204 @@
1
+ require 'rubygems'
2
+ require 'parse_tree'
3
+ require 'sexp_processor'
4
+ require 'reek/block_context'
5
+ require 'reek/class_context'
6
+ require 'reek/module_context'
7
+ require 'reek/stop_context'
8
+ require 'reek/if_context'
9
+ require 'reek/method_context'
10
+ require 'reek/singleton_method_context'
11
+ require 'reek/yield_call_context'
12
+
13
+ module Reek
14
+
15
+ class CodeParser < SexpProcessor
16
+
17
+ def self.parse_tree_for(code) # :nodoc:
18
+ ParseTree.new.parse_tree_for_string(code)
19
+ end
20
+
21
+ # Creates a new Ruby code checker. Any smells discovered by
22
+ # +check_source+ or +check_object+ will be stored in +report+.
23
+ def initialize(report, smells, ctx = StopContext.new)
24
+ super()
25
+ @report = report
26
+ @smells = smells
27
+ @element = ctx
28
+ @unsupported -= [:cfunc]
29
+ @default_method = :process_default
30
+ @require_empty = @warn_on_default = false
31
+ end
32
+
33
+ # Analyses the given Ruby source +code+ looking for smells.
34
+ # Any smells found are saved in the +Report+ object that
35
+ # was passed to this object's constructor.
36
+ def check_source(code)
37
+ check_parse_tree(CodeParser.parse_tree_for(code))
38
+ end
39
+
40
+ # Analyses the given Ruby object +obj+ looking for smells.
41
+ # Any smells found are saved in the +Report+ object that
42
+ # was passed to this object's constructor.
43
+ def check_object(obj)
44
+ check_parse_tree(ParseTree.new.parse_tree(obj))
45
+ end
46
+
47
+ def process_default(exp)
48
+ exp[1..-1].each { |sub| process(sub) if Array === sub }
49
+ s(exp)
50
+ end
51
+
52
+ def process_module(exp)
53
+ push(ModuleContext.create(@element, exp)) do
54
+ process_default(exp)
55
+ check_smells(:module)
56
+ end
57
+ s(exp)
58
+ end
59
+
60
+ def process_class(exp)
61
+ push(ClassContext.create(@element, exp)) do
62
+ process_default(exp) unless @element.is_struct?
63
+ check_smells(:class)
64
+ end
65
+ s(exp)
66
+ end
67
+
68
+ def process_defn(exp)
69
+ handle_context(MethodContext, :defn, exp)
70
+ end
71
+
72
+ def process_defs(exp)
73
+ handle_context(SingletonMethodContext, :defs, exp)
74
+ end
75
+
76
+ def process_args(exp)
77
+ exp[1..-1].each {|sym| @element.record_parameter(sym) }
78
+ s(exp)
79
+ end
80
+
81
+ def process_attrset(exp)
82
+ @element.record_depends_on_self if /^@/ === exp[1].to_s
83
+ s(exp)
84
+ end
85
+
86
+ def process_lit(exp)
87
+ val = exp[1]
88
+ @element.record_depends_on_self if val == :self
89
+ s(exp)
90
+ end
91
+
92
+ def process_iter(exp)
93
+ process(exp[1])
94
+ handle_context(BlockContext, :iter, exp[1..-1])
95
+ end
96
+
97
+ def process_dasgn_curr(exp)
98
+ @element.record_parameter(exp[1])
99
+ process_default(exp)
100
+ end
101
+
102
+ def process_block(exp)
103
+ @element.count_statements(CodeParser.count_statements(exp))
104
+ process_default(exp)
105
+ end
106
+
107
+ def process_yield(exp)
108
+ handle_context(YieldCallContext, :yield, exp)
109
+ end
110
+
111
+ def process_call(exp)
112
+ @element.record_call_to(exp)
113
+ process_default(exp)
114
+ end
115
+
116
+ def process_fcall(exp)
117
+ @element.record_depends_on_self
118
+ @element.refs.record_reference_to_self
119
+ process_default(exp)
120
+ end
121
+
122
+ def process_cfunc(exp)
123
+ @element.record_depends_on_self
124
+ s(exp)
125
+ end
126
+
127
+ def process_vcall(exp)
128
+ @element.record_depends_on_self
129
+ @element.refs.record_reference_to_self
130
+ s(exp)
131
+ end
132
+
133
+ def process_if(exp)
134
+ handle_context(IfContext, :if, exp)
135
+ end
136
+
137
+ def process_ivar(exp)
138
+ process_iasgn(exp)
139
+ end
140
+
141
+ def process_lasgn(exp)
142
+ @element.record_local_variable(exp[1])
143
+ process(exp[2])
144
+ s(exp)
145
+ end
146
+
147
+ def process_iasgn(exp)
148
+ @element.record_instance_variable(exp[1])
149
+ @element.record_depends_on_self
150
+ process_default(exp)
151
+ end
152
+
153
+ def process_self(exp)
154
+ @element.record_depends_on_self
155
+ s(exp)
156
+ end
157
+
158
+ private
159
+
160
+ def self.count_statements(exp)
161
+ stmts = exp[1..-1]
162
+ ignore = 0
163
+ ignore = 1 if is_expr?(stmts[0], :args)
164
+ ignore += 1 if stmts[1] == s(:nil)
165
+ stmts.length - ignore
166
+ end
167
+
168
+ def self.is_expr?(exp, type)
169
+ Array === exp and exp[0] == type
170
+ end
171
+
172
+ def self.is_global_variable?(exp)
173
+ is_expr?(exp, :gvar)
174
+ end
175
+
176
+ def handle_context(klass, type, exp)
177
+ push(klass.new(@element, exp)) do
178
+ process_default(exp)
179
+ check_smells(type)
180
+ end
181
+ s(exp)
182
+ end
183
+
184
+ def check_smells(type)
185
+ @smells[type].each {|smell| smell.examine(@element, @report) }
186
+ end
187
+
188
+ def push(context)
189
+ orig = @element
190
+ @element = context
191
+ yield
192
+ @element = orig
193
+ end
194
+
195
+ def pop(exp)
196
+ @element = @element.outer
197
+ s(exp)
198
+ end
199
+
200
+ def check_parse_tree(sexp) # :nodoc:
201
+ sexp.each { |exp| process(exp) }
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,13 @@
1
+ ---
2
+ FeatureEnvy:
3
+ exclude:
4
+ - examine_context
5
+ LargeClass:
6
+ exclude:
7
+ - CodeParser
8
+ LongMethod:
9
+ exclude:
10
+ - Reek::SexpFormatter#self.format
11
+ UtilityFunction:
12
+ exclude:
13
+ - Reek::Spec
@@ -0,0 +1,25 @@
1
+ require 'reek/code_context'
2
+
3
+ module Reek
4
+ class IfContext < CodeContext
5
+ attr_reader :if_expr
6
+
7
+ def initialize(outer, exp)
8
+ @outer = outer
9
+ @exp = exp
10
+ @if_expr = exp[1]
11
+ end
12
+
13
+ def tests_a_parameter?
14
+ @if_expr[0] == :lvar and has_parameter(@if_expr[1])
15
+ end
16
+
17
+ def outer_name
18
+ @outer.outer_name
19
+ end
20
+
21
+ def to_s
22
+ @outer.to_s
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,85 @@
1
+ require 'reek/name'
2
+ require 'reek/code_context'
3
+ require 'reek/object_refs'
4
+
5
+ module Reek
6
+ class MethodContext < CodeContext
7
+ attr_reader :parameters
8
+ attr_reader :calls
9
+ attr_reader :refs
10
+ attr_reader :num_statements
11
+
12
+ def initialize(outer, exp, record = true)
13
+ super(outer, exp)
14
+ @parameters = []
15
+ @local_variables = []
16
+ @name = Name.new(exp[1])
17
+ @num_statements = 0
18
+ @calls = Hash.new(0)
19
+ @depends_on_self = false
20
+ @refs = ObjectRefs.new
21
+ @outer.record_method(@name) # TODO: should be children of outer?
22
+ end
23
+
24
+ def count_statements(num)
25
+ @num_statements += num
26
+ end
27
+
28
+ def depends_on_instance?
29
+ @depends_on_self || is_overriding_method?(@name)
30
+ end
31
+
32
+ def has_parameter(sym)
33
+ @parameters.include?(sym.to_s)
34
+ end
35
+
36
+ def record_call_to(exp)
37
+ @calls[exp] += 1
38
+ receiver, meth = exp[1..2]
39
+ if receiver.nil?
40
+ record_depends_on_self
41
+ else
42
+ case receiver[0]
43
+ when :lvar
44
+ @refs.record_ref(receiver) unless meth == :new
45
+ when :ivar
46
+ record_depends_on_self
47
+ @refs.record_reference_to_self
48
+ end
49
+ end
50
+ end
51
+
52
+ def record_depends_on_self
53
+ @depends_on_self = true
54
+ end
55
+
56
+ def record_local_variable(sym)
57
+ @local_variables << Name.new(sym)
58
+ end
59
+
60
+ def self.is_block_arg?(param)
61
+ Array === param and param[0] == :block
62
+ end
63
+
64
+ def record_parameter(param)
65
+ @parameters << Name.new(param) unless MethodContext.is_block_arg?(param)
66
+ end
67
+
68
+ def outer_name
69
+ "#{@outer.outer_name}#{@name}/"
70
+ end
71
+
72
+ def to_s
73
+ "#{@outer.outer_name}#{@name}"
74
+ end
75
+
76
+ def envious_receivers
77
+ return [] if @refs.self_is_max?
78
+ @refs.max_keys
79
+ end
80
+
81
+ def variable_names
82
+ @parameters + @local_variables
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,34 @@
1
+ require 'reek/code_context'
2
+
3
+ module Reek
4
+ class ModuleContext < CodeContext
5
+
6
+ def ModuleContext.create(outer, exp)
7
+ res = Name.resolve(exp[1], outer)
8
+ ModuleContext.new(res[0], res[1])
9
+ end
10
+
11
+ def initialize(outer, name)
12
+ super(outer, nil)
13
+ @name = name
14
+ end
15
+
16
+ def myself
17
+ @myself ||= @outer.find_module(@name)
18
+ end
19
+
20
+ def find_module(modname)
21
+ return nil unless myself
22
+ sym = modname.to_s
23
+ myself.const_defined?(sym) ? myself.const_get(sym) : nil
24
+ end
25
+
26
+ def outer_name
27
+ "#{@outer.outer_name}#{@name}::"
28
+ end
29
+
30
+ def variable_names
31
+ []
32
+ end
33
+ end
34
+ end
data/lib/reek/name.rb ADDED
@@ -0,0 +1,42 @@
1
+ module Reek
2
+ class Name
3
+ include Comparable
4
+
5
+ def self.resolve(exp, context)
6
+ return [context, new(exp)] unless Array === exp
7
+ name = exp[1]
8
+ case exp[0]
9
+ when :colon2
10
+ return [resolve(name, context)[0], new(exp[2])]
11
+ when :const
12
+ return [ModuleContext.create(context, exp), new(name)]
13
+ when :colon3
14
+ return [StopContext.new, new(name)]
15
+ else
16
+ return [context, new(name)]
17
+ end
18
+ end
19
+
20
+ def initialize(sym)
21
+ @name = sym.to_s
22
+ end
23
+
24
+ def hash # :nodoc:
25
+ @name.hash
26
+ end
27
+
28
+ def <=>(other) # :nodoc:
29
+ @name <=> other.to_s
30
+ end
31
+
32
+ alias eql? <=>
33
+
34
+ def effective_name
35
+ @name.gsub(/^@*/, '')
36
+ end
37
+
38
+ def to_s
39
+ @name
40
+ end
41
+ end
42
+ end