reek 0.3.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. data/History.txt +20 -0
  2. data/README.txt +4 -80
  3. data/Rakefile +15 -4
  4. data/bin/reek +10 -16
  5. data/config/defaults.reek +53 -0
  6. data/lib/reek.rb +1 -21
  7. data/lib/reek/block_context.rb +37 -0
  8. data/lib/reek/class_context.rb +73 -0
  9. data/lib/reek/code_context.rb +47 -0
  10. data/lib/reek/code_parser.rb +204 -0
  11. data/lib/reek/exceptions.reek +13 -0
  12. data/lib/reek/if_context.rb +25 -0
  13. data/lib/reek/method_context.rb +85 -0
  14. data/lib/reek/module_context.rb +34 -0
  15. data/lib/reek/name.rb +42 -0
  16. data/lib/reek/object_refs.rb +3 -6
  17. data/lib/reek/options.rb +60 -40
  18. data/lib/reek/rake_task.rb +20 -29
  19. data/lib/reek/report.rb +16 -27
  20. data/lib/reek/sexp_formatter.rb +52 -0
  21. data/lib/reek/singleton_method_context.rb +27 -0
  22. data/lib/reek/smell_warning.rb +49 -0
  23. data/lib/reek/smells/control_couple.rb +21 -13
  24. data/lib/reek/smells/duplication.rb +23 -27
  25. data/lib/reek/smells/feature_envy.rb +18 -25
  26. data/lib/reek/smells/large_class.rb +32 -17
  27. data/lib/reek/smells/long_method.rb +24 -16
  28. data/lib/reek/smells/long_parameter_list.rb +25 -18
  29. data/lib/reek/smells/long_yield_list.rb +7 -9
  30. data/lib/reek/smells/nested_iterators.rb +13 -9
  31. data/lib/reek/smells/smell_detector.rb +66 -0
  32. data/lib/reek/smells/smells.rb +71 -10
  33. data/lib/reek/smells/uncommunicative_name.rb +49 -41
  34. data/lib/reek/smells/utility_function.rb +18 -18
  35. data/lib/reek/source.rb +116 -0
  36. data/lib/reek/spec.rb +146 -0
  37. data/lib/reek/stop_context.rb +62 -0
  38. data/lib/reek/yield_call_context.rb +14 -0
  39. data/reek.gemspec +42 -0
  40. data/spec/integration/reek_source_spec.rb +20 -0
  41. data/spec/{script_spec.rb → integration/script_spec.rb} +11 -24
  42. data/spec/reek/class_context_spec.rb +198 -0
  43. data/spec/reek/code_context_spec.rb +92 -0
  44. data/spec/reek/code_parser_spec.rb +44 -0
  45. data/spec/reek/config_spec.rb +42 -0
  46. data/spec/reek/if_context_spec.rb +17 -0
  47. data/spec/reek/method_context_spec.rb +52 -0
  48. data/spec/reek/module_context_spec.rb +38 -0
  49. data/spec/reek/options_spec.rb +2 -28
  50. data/spec/reek/report_spec.rb +6 -40
  51. data/spec/reek/sexp_formatter_spec.rb +31 -0
  52. data/spec/reek/singleton_method_context_spec.rb +17 -0
  53. data/spec/reek/smells/control_couple_spec.rb +10 -18
  54. data/spec/reek/smells/duplication_spec.rb +53 -32
  55. data/spec/reek/smells/feature_envy_spec.rb +87 -49
  56. data/spec/reek/smells/large_class_spec.rb +45 -4
  57. data/spec/reek/smells/long_method_spec.rb +25 -41
  58. data/spec/reek/smells/long_parameter_list_spec.rb +30 -76
  59. data/spec/reek/smells/nested_iterators_spec.rb +19 -29
  60. data/spec/reek/smells/smell_spec.rb +9 -18
  61. data/spec/reek/smells/uncommunicative_name_spec.rb +88 -53
  62. data/spec/reek/smells/utility_function_spec.rb +45 -44
  63. data/spec/samples/inline_spec.rb +40 -0
  64. data/spec/samples/optparse_spec.rb +100 -0
  65. data/spec/samples/redcloth_spec.rb +93 -0
  66. data/spec/spec_helper.rb +3 -1
  67. data/tasks/reek.rake +1 -10
  68. data/tasks/rspec.rake +16 -35
  69. metadata +43 -46
  70. data/lib/reek/checker.rb +0 -66
  71. data/lib/reek/class_checker.rb +0 -25
  72. data/lib/reek/file_checker.rb +0 -20
  73. data/lib/reek/method_checker.rb +0 -198
  74. data/lib/reek/printer.rb +0 -154
  75. data/lib/reek/smells/smell.rb +0 -56
  76. data/lib/reek/version.rb +0 -9
  77. data/setup.rb +0 -1585
  78. data/spec/integration_spec.rb +0 -30
  79. data/spec/reek/class_checker_spec.rb +0 -48
  80. data/spec/reek/method_checker_spec.rb +0 -67
  81. data/spec/reek/printer_spec.rb +0 -30
  82. data/spec/reek_source_spec.rb +0 -12
  83. data/spec/samples/inline.reek +0 -27
  84. data/spec/samples/optparse.reek +0 -79
  85. data/spec/samples/optparse/date.rb +0 -17
  86. data/spec/samples/optparse/shellwords.rb +0 -6
  87. data/spec/samples/optparse/time.rb +0 -10
  88. data/spec/samples/optparse/uri.rb +0 -6
  89. data/spec/samples/optparse/version.rb +0 -70
  90. data/spec/samples/redcloth.reek +0 -65
  91. data/tasks/samples.rake +0 -17
  92. data/website/index.html +0 -71
  93. data/website/index.txt +0 -40
  94. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  95. data/website/stylesheets/screen.css +0 -138
  96. data/website/template.rhtml +0 -48
data/History.txt CHANGED
@@ -1,3 +1,23 @@
1
+ == 1.0.0 2009-04-05
2
+
3
+ === Major enhancements:
4
+
5
+ * Use *.reek files in source tree to configure Reek's 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
+
1
21
  == 0.3.1 2008-11-17
2
22
 
3
23
  * Minor enhancements:
data/README.txt CHANGED
@@ -1,82 +1,6 @@
1
- = reek
1
+ = Reek
2
2
 
3
- * http://rubyforge.org/projects/reek/
4
- * mailto:kevin@rutherford-software.com
3
+ Code smell detection for Ruby.
5
4
 
6
- == DESCRIPTION:
7
-
8
- Reek is a tool that examines Ruby classes, modules and methods and
9
- reports any code smells it finds.
10
-
11
- === SUPPORTED CODE SMELLS:
12
-
13
- Reek currently includes very naive checks for the following smells:
14
-
15
- * Long Method
16
- * Large Class
17
- * Feature Envy
18
- * Uncommunicative Name
19
- * Long Parameter List
20
- * Utility Function
21
- * Nested Iterators
22
- * Control Couple
23
- * Duplication
24
-
25
- == FEATURES/PROBLEMS:
26
-
27
- * Most of the current checks are quite naive.
28
- * Not many smells checked right now; more coming soon.
29
- * The current Feature Envy check is probably over zealous.
30
- * There's no convenient programmer's API just yet.
31
-
32
- == SYNOPSIS:
33
-
34
- $ reek [options] sources
35
-
36
- (See `reek --help` for details.)
37
-
38
- == DEPENDENCIES:
39
-
40
- Reek makes use of the following other gems:
41
-
42
- * ParseTree
43
- * sexp_processor
44
-
45
- == INSTALL:
46
-
47
- Get the latest version of the gem:
48
-
49
- $ gem install reek
50
-
51
- Or get the latest source code from:
52
-
53
- $ git clone git://github.com/kevinrutherford/reek.git
54
-
55
- or
56
-
57
- $ git clone git://rubyforge.org/reek.git
58
-
59
- == LICENSE:
60
-
61
- (The MIT License)
62
-
63
- Copyright (c) 2008 Kevin Rutherford, Rutherford Software Ltd
64
-
65
- Permission is hereby granted, free of charge, to any person obtaining
66
- a copy of this software and associated documentation files (the
67
- 'Software'), to deal in the Software without restriction, including
68
- without limitation the rights to use, copy, modify, merge, publish,
69
- distribute, sublicense, and/or sell copies of the Software, and to
70
- permit persons to whom the Software is furnished to do so, subject to
71
- the following conditions:
72
-
73
- The above copyright notice and this permission notice shall be
74
- included in all copies or substantial portions of the Software.
75
-
76
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
77
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
78
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
79
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
80
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
81
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
82
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5
+ The documentation is at http://wiki.github.com/kevinrutherford/reek
6
+ The code lives at http://github.com/kevinrutherford/reek/tree
data/Rakefile CHANGED
@@ -1,6 +1,17 @@
1
- require 'config/requirements'
2
- require 'config/hoe' # setup Hoe + all gem configuration
1
+ require 'rake'
2
+ require 'rake/clean'
3
3
 
4
- Dir['tasks/**/*.rake'].each { |rake| load rake }
4
+ $:.unshift File.dirname(__FILE__) + '/lib'
5
5
 
6
- task :default => :spec
6
+ PROJECT_NAME = 'reek'
7
+
8
+ BUILD_DIR = 'build'; directory BUILD_DIR
9
+ PKG_DIR = "#{BUILD_DIR}/pkg"; directory PKG_DIR
10
+ RDOC_DIR = "#{BUILD_DIR}/rdoc"; directory RDOC_DIR
11
+
12
+ GEM_MANIFEST = "Manifest.txt"
13
+ VERSION_FILE = 'lib/reek.rb'
14
+
15
+ CLOBBER.include("#{BUILD_DIR}/*")
16
+
17
+ Dir['tasks/**/*.rake'].each { |t| load t }
data/bin/reek CHANGED
@@ -1,25 +1,19 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Created on 2008-2-17.
4
- # Copyright (c) 2008 Kevin Rutherford, Rutherford Software Ltd. All rights reserved.
3
+ # Reek examines Ruby source code for smells.
4
+ # Visit http://reek.rubyforge.org/ for docs etc.
5
+ #
6
+ # Author: Kevin Rutherford
5
7
  #
6
8
 
7
- require File.join(File.dirname(__FILE__), '../lib/', 'reek')
8
- require File.join(File.dirname(__FILE__), '../lib/', 'reek/options')
9
+ require 'reek'
10
+ require 'reek/source'
11
+ require 'reek/options'
9
12
 
10
- sources = Reek::Options.parse(ARGV)
11
13
  exitstatus = 0
12
- sources.each do |src|
13
- smells = Reek.analyse(src)
14
- next if smells.empty?
15
- if sources.size == 1
16
- puts smells.to_s
17
- else
18
- puts "\"#{src}\" -- #{smells.length} warnings:"
19
- puts smells.to_s
20
- puts
21
- end
14
+ source = Reek::Options.parse(ARGV)
15
+ if source.smelly?
16
+ puts source.report
22
17
  exitstatus = 2
23
18
  end
24
-
25
19
  exit(exitstatus)
@@ -0,0 +1,53 @@
1
+ ---
2
+ LargeClass:
3
+ max_methods: 25
4
+ exclude:
5
+ - Array
6
+ - Hash
7
+ - Module
8
+ - String
9
+ enabled: true
10
+ LongParameterList:
11
+ max_params: 3
12
+ exclude: []
13
+
14
+ enabled: true
15
+ FeatureEnvy:
16
+ exclude:
17
+ - initialize
18
+ enabled: true
19
+ UncommunicativeName:
20
+ accept:
21
+ - Inline::C
22
+ exclude: []
23
+
24
+ enabled: true
25
+ reject:
26
+ - !ruby/regexp /^.[0-9]*$/
27
+ NestedIterators:
28
+ exclude: []
29
+
30
+ enabled: true
31
+ LongMethod:
32
+ max_statements: 5
33
+ exclude:
34
+ - initialize
35
+ enabled: true
36
+ Duplication:
37
+ exclude: []
38
+
39
+ enabled: true
40
+ max_calls: 1
41
+ UtilityFunction:
42
+ exclude: []
43
+
44
+ enabled: true
45
+ ControlCouple:
46
+ exclude:
47
+ - initialize
48
+ enabled: true
49
+ LongYieldList:
50
+ max_params: 3
51
+ exclude: []
52
+
53
+ enabled: true
data/lib/reek.rb CHANGED
@@ -1,25 +1,5 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
2
 
3
- require 'reek/file_checker'
4
- require 'reek/report'
5
-
6
3
  module Reek # :doc:
7
-
8
- #
9
- # Analyse the given source, looking for code smells.
10
- # The source can be a filename or a String containing Ruby code.
11
- # Returns a +Report+ listing the smells found.
12
- #
13
- def self.analyse(src) # :doc:
14
- report = Report.new
15
- source = Reek.get_source(src)
16
- FileChecker.new(report).check_source(source)
17
- report
18
- end
19
-
20
- private
21
-
22
- def self.get_source(src)
23
- File.exists?(src) ? IO.readlines(src).join : src
24
- end
4
+ VERSION = '1.0.0'
25
5
  end
@@ -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