brakeman 1.7.1 → 1.8.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 (57) hide show
  1. data/bin/brakeman +3 -0
  2. data/lib/brakeman.rb +2 -0
  3. data/lib/brakeman/brakeman.rake +4 -3
  4. data/lib/brakeman/checks/base_check.rb +40 -37
  5. data/lib/brakeman/checks/check_basic_auth.rb +3 -3
  6. data/lib/brakeman/checks/check_content_tag.rb +179 -0
  7. data/lib/brakeman/checks/check_cross_site_scripting.rb +41 -17
  8. data/lib/brakeman/checks/check_execute.rb +1 -1
  9. data/lib/brakeman/checks/check_file_access.rb +2 -2
  10. data/lib/brakeman/checks/check_link_to.rb +9 -7
  11. data/lib/brakeman/checks/check_link_to_href.rb +1 -1
  12. data/lib/brakeman/checks/check_mail_to.rb +1 -1
  13. data/lib/brakeman/checks/check_mass_assignment.rb +6 -5
  14. data/lib/brakeman/checks/check_redirect.rb +18 -17
  15. data/lib/brakeman/checks/check_render.rb +3 -1
  16. data/lib/brakeman/checks/check_select_tag.rb +2 -2
  17. data/lib/brakeman/checks/check_select_vulnerability.rb +3 -3
  18. data/lib/brakeman/checks/check_send.rb +3 -3
  19. data/lib/brakeman/checks/check_session_settings.rb +5 -5
  20. data/lib/brakeman/checks/check_single_quotes.rb +8 -8
  21. data/lib/brakeman/checks/check_skip_before_filter.rb +2 -2
  22. data/lib/brakeman/checks/check_sql.rb +36 -39
  23. data/lib/brakeman/checks/check_validation_regex.rb +3 -3
  24. data/lib/brakeman/checks/check_without_protection.rb +2 -2
  25. data/lib/brakeman/format/style.css +15 -0
  26. data/lib/brakeman/options.rb +4 -0
  27. data/lib/brakeman/processor.rb +1 -1
  28. data/lib/brakeman/processors/alias_processor.rb +63 -61
  29. data/lib/brakeman/processors/base_processor.rb +31 -45
  30. data/lib/brakeman/processors/controller_alias_processor.rb +11 -9
  31. data/lib/brakeman/processors/controller_processor.rb +26 -25
  32. data/lib/brakeman/processors/erb_template_processor.rb +12 -12
  33. data/lib/brakeman/processors/erubis_template_processor.rb +19 -17
  34. data/lib/brakeman/processors/gem_processor.rb +5 -5
  35. data/lib/brakeman/processors/haml_template_processor.rb +16 -12
  36. data/lib/brakeman/processors/lib/find_all_calls.rb +11 -17
  37. data/lib/brakeman/processors/lib/find_call.rb +16 -23
  38. data/lib/brakeman/processors/lib/processor_helper.rb +11 -5
  39. data/lib/brakeman/processors/lib/rails2_config_processor.rb +21 -20
  40. data/lib/brakeman/processors/lib/rails2_route_processor.rb +38 -34
  41. data/lib/brakeman/processors/lib/rails3_config_processor.rb +17 -17
  42. data/lib/brakeman/processors/lib/rails3_route_processor.rb +42 -40
  43. data/lib/brakeman/processors/lib/render_helper.rb +6 -6
  44. data/lib/brakeman/processors/lib/route_helper.rb +1 -1
  45. data/lib/brakeman/processors/library_processor.rb +11 -11
  46. data/lib/brakeman/processors/model_processor.rb +18 -16
  47. data/lib/brakeman/processors/template_alias_processor.rb +36 -29
  48. data/lib/brakeman/processors/template_processor.rb +4 -4
  49. data/lib/brakeman/report.rb +23 -4
  50. data/lib/brakeman/templates/error_overview.html.erb +9 -1
  51. data/lib/brakeman/templates/view_warnings.html.erb +16 -3
  52. data/lib/brakeman/tracker.rb +3 -0
  53. data/lib/brakeman/util.rb +5 -1
  54. data/lib/brakeman/version.rb +1 -1
  55. data/lib/brakeman/warning.rb +1 -1
  56. data/lib/ruby_parser/bm_sexp.rb +302 -2
  57. metadata +6 -5
@@ -17,12 +17,16 @@ class Brakeman::TemplateAliasProcessor < Brakeman::AliasProcessor
17
17
 
18
18
  #Process template
19
19
  def process_template name, args
20
- if @called_from and @called_from.match(/Template:#{name}$/)
21
- Brakeman.debug "Skipping circular render from #{@template[:name]} to #{name}"
22
- return
23
- end
20
+ if @called_from
21
+ unless @called_from.grep(/Template:#{name}$/).empty?
22
+ Brakeman.debug "Skipping circular render from #{@template[:name]} to #{name}"
23
+ return
24
+ end
24
25
 
25
- super name, args, "Template:#{@template[:name]}"
26
+ super name, args, @called_from + ["Template:#{@template[:name]}"]
27
+ else
28
+ super name, args, ["Template:#{@template[:name]}"]
29
+ end
26
30
  end
27
31
 
28
32
  #Determine template name
@@ -37,28 +41,31 @@ class Brakeman::TemplateAliasProcessor < Brakeman::AliasProcessor
37
41
  def process_call_with_block exp
38
42
  process_default exp
39
43
 
40
- call = exp[1]
41
- target = call[1]
42
- method = call[2]
43
- args = exp[2]
44
- block = exp[3]
45
-
46
- #Check for e.g. Model.find.each do ... end
47
- if method == :each and args and block and model = get_model_target(target)
48
- if node_type? args, :lasgn
49
- if model == target[1]
50
- env[Sexp.new(:lvar, args[1])] = Sexp.new(:call, model, :new, Sexp.new(:arglist))
51
- else
52
- env[Sexp.new(:lvar, args[1])] = Sexp.new(:call, Sexp.new(:const, Brakeman::Tracker::UNKNOWN_MODEL), :new, Sexp.new(:arglist))
44
+ call = exp.block_call
45
+
46
+ if call? call
47
+ target = call.target
48
+ method = call.method
49
+ args = exp.block_args
50
+ block = exp.block
51
+
52
+ #Check for e.g. Model.find.each do ... end
53
+ if method == :each and args and block and model = get_model_target(target)
54
+ if node_type? args, :lasgn
55
+ if model == target.target
56
+ env[Sexp.new(:lvar, args.lhs)] = Sexp.new(:call, model, :new, Sexp.new(:arglist))
57
+ else
58
+ env[Sexp.new(:lvar, args.lhs)] = Sexp.new(:call, Sexp.new(:const, Brakeman::Tracker::UNKNOWN_MODEL), :new, Sexp.new(:arglist))
59
+ end
60
+
61
+ process block if sexp? block
53
62
  end
54
-
55
- process block if sexp? block
56
- end
57
- elsif FORM_METHODS.include? method
58
- if node_type? args, :lasgn
59
- env[Sexp.new(:lvar, args[1])] = Sexp.new(:call, Sexp.new(:const, :FormBuilder), :new, Sexp.new(:arglist))
63
+ elsif FORM_METHODS.include? method
64
+ if node_type? args, :lasgn
65
+ env[Sexp.new(:lvar, args.lhs)] = Sexp.new(:call, Sexp.new(:const, :FormBuilder), :new, Sexp.new(:arglist))
60
66
 
61
- process block if sexp? block
67
+ process block if sexp? block
68
+ end
62
69
  end
63
70
  end
64
71
 
@@ -70,9 +77,9 @@ class Brakeman::TemplateAliasProcessor < Brakeman::AliasProcessor
70
77
  #Checks if +exp+ is a call to Model.all or Model.find*
71
78
  def get_model_target exp
72
79
  if call? exp
73
- target = exp[1]
80
+ target = exp.target
74
81
 
75
- if exp[2] == :all or exp[2].to_s[0,4] == "find"
82
+ if exp.method == :all or exp.method.to_s[0,4] == "find"
76
83
  models = Set.new @tracker.models.keys
77
84
 
78
85
  begin
@@ -91,9 +98,9 @@ class Brakeman::TemplateAliasProcessor < Brakeman::AliasProcessor
91
98
 
92
99
  def find_push_target exp
93
100
  if sexp? exp
94
- if exp.node_type == :lvar and (exp[1] == :_buf or exp[1] == :_erbout)
101
+ if exp.node_type == :lvar and (exp.value == :_buf or exp.value == :_erbout)
95
102
  return nil
96
- elsif exp.node_type == :ivar and exp[1] == :@output_buffer
103
+ elsif exp.node_type == :ivar and exp.value == :@output_buffer
97
104
  return nil
98
105
  end
99
106
  end
@@ -35,19 +35,19 @@ class Brakeman::TemplateProcessor < Brakeman::BaseProcessor
35
35
 
36
36
  #Ignore initial variable assignment
37
37
  def process_lasgn exp
38
- if exp[1] == :_erbout and exp[2].node_type == :str #ignore
38
+ if exp.lhs == :_erbout and exp.rhs.node_type == :str #ignore
39
39
  ignore
40
- elsif exp[1] == :_buf and exp[2].node_type == :str
40
+ elsif exp.lhs == :_buf and exp.rhs.node_type == :str
41
41
  ignore
42
42
  else
43
- exp[2] = process exp[2]
43
+ exp.rhs = process exp.rhs
44
44
  exp
45
45
  end
46
46
  end
47
47
 
48
48
  #Adds output to the list of outputs.
49
49
  def process_output exp
50
- process exp[1]
50
+ process exp.value
51
51
  @current_template[:outputs] << exp
52
52
  exp
53
53
  end
@@ -1,5 +1,6 @@
1
1
  require 'cgi'
2
2
  require 'set'
3
+ require 'pathname'
3
4
  require 'brakeman/processors/output_processor'
4
5
  require 'brakeman/util'
5
6
  require 'terminal-table'
@@ -136,6 +137,8 @@ class Brakeman::Report
136
137
  w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
137
138
  w["Message"] = with_context warning, w["Message"]
138
139
  w["Warning Type"] = with_link warning, w["Warning Type"]
140
+ w["Called From"] = warning.called_from
141
+ w["Template Name"] = warning.template[:name]
139
142
  else
140
143
  w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
141
144
  w["Message"] = text_message warning, w["Message"]
@@ -549,7 +552,7 @@ class Brakeman::Report
549
552
  message
550
553
  end <<
551
554
  "<table id='#{code_id}' class='context' style='display:none'>" <<
552
- "<caption>#{(warning.file || '').gsub(tracker.options[:app_path], "")}</caption>"
555
+ "<caption>#{warning_file(warning, :relative) || ''}</caption>"
553
556
 
554
557
  unless context.empty?
555
558
  if warning.line - 1 == 1 or warning.line + 1 == 1
@@ -612,7 +615,7 @@ class Brakeman::Report
612
615
  checks.send(meth).map do |w|
613
616
  line = w.line || 0
614
617
  w.warning_type.gsub!(/[^\w\s]/, ' ')
615
- "#{file_for w}\t#{line}\t#{w.warning_type}\t#{category}\t#{w.format_message}\t#{TEXT_CONFIDENCE[w.confidence]}"
618
+ "#{warning_file w}\t#{line}\t#{w.warning_type}\t#{category}\t#{w.format_message}\t#{TEXT_CONFIDENCE[w.confidence]}"
616
619
  end.join "\n"
617
620
 
618
621
  end.join "\n"
@@ -635,7 +638,6 @@ class Brakeman::Report
635
638
  w.code = ""
636
639
  end
637
640
  w.context = context_for(w).join("\n")
638
- w.file = file_for w
639
641
  end
640
642
  end
641
643
 
@@ -648,7 +650,14 @@ class Brakeman::Report
648
650
  require 'json'
649
651
 
650
652
  errors = tracker.errors.map{|e| { :error => e[:error], :location => e[:backtrace][0] }}
651
- warnings = all_warnings.map { |w| w.to_hash }.sort_by{|w| w[:file]}
653
+ app_path = tracker.options[:app_path]
654
+
655
+ warnings = all_warnings.map do |w|
656
+ hash = w.to_hash
657
+ hash[:file] = warning_file w
658
+ hash
659
+ end.sort_by { |w| w[:file] }
660
+
652
661
  scan_info = {
653
662
  :app_path => File.expand_path(tracker.options[:app_path]),
654
663
  :rails_version => rails_version,
@@ -678,6 +687,16 @@ class Brakeman::Report
678
687
  Set.new(tracker.templates.map {|k,v| v[:name].to_s[/[^.]+/]}).length
679
688
  end
680
689
 
690
+ def warning_file warning, relative = false
691
+ return nil if warning.file.nil?
692
+
693
+ if @tracker.options[:relative_paths] or relative
694
+ Pathname.new(warning.file).relative_path_from(Pathname.new(tracker.options[:app_path])).to_s
695
+ else
696
+ warning.file
697
+ end
698
+ end
699
+
681
700
  private
682
701
 
683
702
  def load_and_render_erb file, bind
@@ -7,7 +7,15 @@
7
7
  <% tracker.errors.each do |warning| %>
8
8
  <tr>
9
9
  <td><%= CGI.escapeHTML warning[:error] %></td>
10
- <td><%= warning[:backtrace][0] %></td>
10
+ <td>
11
+ <% if tracker.options[:debug] %>
12
+ <% warning[:backtrace].each do |line| %>
13
+ <%= line %><br/>
14
+ <% end %>
15
+ <% else %>
16
+ <%= warning[:backtrace][0] %>
17
+ <% end %>
18
+ </td>
11
19
  </tr>
12
20
  <% end %>
13
21
  </table>
@@ -6,12 +6,25 @@
6
6
  <th>Warning Type</th>
7
7
  <th>Message</th>
8
8
  </tr>
9
- <% warnings.each do |warning| %>
9
+ <% warnings.each_with_index do |warning, i| %>
10
10
  <tr>
11
11
  <td><%= warning['Confidence']%></td>
12
- <td><%= warning['Template']%></td>
12
+ <td>
13
+ <% if warning['Called From'] and warning['Called From'].length > 1 %>
14
+ <div class="template_name" onClick="toggle('callers<%= i %>')" >
15
+ <div>
16
+ <%= warning['Template'] %>
17
+ </div>
18
+ <div class="render_path" id="callers<%= i %>" >
19
+ <%= warning['Called From'].join(' &rarr; ') %> &rarr; <%= warning['Template Name'] %>
20
+ </div>
21
+ </div>
22
+ <% else %>
23
+ <%= warning['Template']%>
24
+ <% end %>
25
+ </td>
13
26
  <td><%= warning['Warning Type']%></td>
14
27
  <td><%= warning['Message']%></td>
15
28
  </tr>
16
29
  <% end %>
17
- </table>
30
+ </table>
@@ -54,6 +54,9 @@ class Brakeman::Tracker
54
54
  backtrace = [ backtrace ]
55
55
  end
56
56
 
57
+ Brakeman.debug exception
58
+ Brakeman.debug backtrace
59
+
57
60
  @errors << { :error => exception.to_s.gsub("\n", " "), :backtrace => backtrace }
58
61
  end
59
62
 
@@ -346,7 +346,11 @@ module Brakeman::Util
346
346
  end
347
347
 
348
348
  def truncate_table str
349
- @terminal_width ||= ::HighLine::SystemExtensions::terminal_size[0]
349
+ @terminal_width ||= if $stdin && $stdin.tty?
350
+ ::HighLine::SystemExtensions::terminal_size[0]
351
+ else
352
+ 80
353
+ end
350
354
  lines = str.lines
351
355
 
352
356
  lines.map do |line|
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "1.7.1"
2
+ Version = "1.8.0"
3
3
  end
@@ -66,7 +66,7 @@ class Brakeman::Warning
66
66
  def view_name
67
67
  return @view_name if @view_name
68
68
  if called_from
69
- @view_name = "#{template[:name]} (#{called_from})"
69
+ @view_name = "#{template[:name]} (#{called_from.last})"
70
70
  else
71
71
  @view_name = template[:name]
72
72
  end
@@ -3,20 +3,44 @@
3
3
  #of a Sexp.
4
4
  class Sexp
5
5
  attr_reader :paren
6
+ ASSIGNMENT_BOOL = [:gasgn, :iasgn, :lasgn, :cvdecl, :cdecl, :or, :and]
7
+
8
+ def method_missing name, *args
9
+ #Brakeman does not use this functionality,
10
+ #so overriding it to raise a NoMethodError.
11
+ #
12
+ #The original functionality calls find_node and optionally
13
+ #deletes the node if found.
14
+ raise NoMethodError.new("No method '#{name}' for Sexp", name, args)
15
+ end
6
16
 
7
17
  def paren
8
18
  @paren ||= false
9
19
  end
10
20
 
11
21
  def value
12
- raise "multi item sexp" if size > 2
22
+ raise WrongSexpError, "Sexp#value called on multi-item Sexp", caller[1..-1] if size > 2
13
23
  last
14
24
  end
15
25
 
26
+ def second
27
+ self[1]
28
+ end
29
+
16
30
  def to_sym
17
31
  self.value.to_sym
18
32
  end
19
33
 
34
+ def node_type= type
35
+ self[0] = type
36
+ end
37
+
38
+ def resbody delete = false
39
+ #RubyParser relies on method_missing for this, but since we don't want to use
40
+ #method_missing, here's a real method.
41
+ find_node :resbody, delete
42
+ end
43
+
20
44
  alias :node_type :sexp_type
21
45
  alias :values :sexp_body # TODO: retire
22
46
 
@@ -86,6 +110,282 @@ class Sexp
86
110
  @my_hash_value = nil
87
111
  old_comments_set(*args)
88
112
  end
113
+
114
+ #Iterates over the Sexps in an Sexp, skipping values that are not
115
+ #an Sexp.
116
+ def each_sexp
117
+ self.each do |e|
118
+ yield e if Sexp === e
119
+ end
120
+ end
121
+
122
+ #Raise a WrongSexpError if the nodes type does not match one of the expected
123
+ #types.
124
+ def expect *types
125
+ unless types.include? self.node_type
126
+ raise WrongSexpError, "Expected #{types.join ' or '} but given #{self.inspect}", caller[1..-1]
127
+ end
128
+ end
129
+
130
+ #Returns target of a method call:
131
+ #
132
+ #s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1)))
133
+ # ^-----------target-----------^
134
+ def target
135
+ expect :call, :attrasgn
136
+ self[1]
137
+ end
138
+
139
+ #Sets the target of a method call:
140
+ def target= exp
141
+ expect :call, :attrasgn
142
+ self[1] = exp
143
+ end
144
+
145
+ #Returns method of a method call:
146
+ #
147
+ #s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1)))
148
+ # ^- method
149
+ def method
150
+ expect :call, :attrasgn
151
+ self[2]
152
+ end
153
+
154
+ #Sets the arglist in a method call.
155
+ def arglist= exp
156
+ expect :call, :attrasgn
157
+ self[3] = exp
158
+ #RP 3 TODO
159
+ end
160
+
161
+ #Returns arglist for method call. This differs from Sexp#args, as Sexp#args
162
+ #does not return a 'real' Sexp (it does not have a node type) but
163
+ #Sexp#arglist returns a s(:arglist, ...)
164
+ #
165
+ # s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1), s(:lit, 2)))
166
+ # ^------------ arglist ------------^
167
+ def arglist
168
+ expect :call, :attrasgn
169
+ self[3]
170
+
171
+ #For new ruby_parser
172
+ #Sexp.new(:arglist, *self[3..-1])
173
+ end
174
+
175
+ #Returns arguments of a method call. This will be an 'untyped' Sexp.
176
+ #
177
+ # s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1), s(:lit, 2)))
178
+ # ^--------args--------^
179
+ def args
180
+ expect :call, :attrasgn
181
+ #For new ruby_parser
182
+ #if self[3]
183
+ # self[3..-1]
184
+ #else
185
+ # []
186
+ #end
187
+
188
+ #For old ruby_parser
189
+ if self[3]
190
+ self[3][1..-1]
191
+ else
192
+ []
193
+ end
194
+ end
195
+
196
+ #Returns first argument of a method call.
197
+ def first_arg
198
+ expect :call, :attrasgn
199
+ if self[3]
200
+ self[3][1]
201
+ end
202
+ end
203
+
204
+ #Sets first argument of a method call.
205
+ def first_arg= exp
206
+ expect :call, :attrasgn
207
+ if self[3]
208
+ self[3][1] = exp
209
+ end
210
+ end
211
+
212
+ #Returns second argument of a method call.
213
+ def second_arg
214
+ expect :call, :attrasgn
215
+ if self[3]
216
+ self[3][2]
217
+ end
218
+ end
219
+
220
+ #Sets second argument of a method call.
221
+ def second_arg= exp
222
+ expect :call, :attrasgn
223
+ if self[3]
224
+ self[3][2] = exp
225
+ end
226
+ end
227
+
228
+ #Returns condition of an if expression:
229
+ #
230
+ # s(:if,
231
+ # s(:lvar, :condition), <-- condition
232
+ # s(:lvar, :then_val),
233
+ # s(:lvar, :else_val)))
234
+ def condition
235
+ expect :if
236
+ self[1]
237
+ end
238
+
239
+ #Returns 'then' clause of an if expression:
240
+ #
241
+ # s(:if,
242
+ # s(:lvar, :condition),
243
+ # s(:lvar, :then_val), <-- then clause
244
+ # s(:lvar, :else_val)))
245
+ def then_clause
246
+ expect :if
247
+ self[2]
248
+ end
249
+
250
+ #Returns 'else' clause of an if expression:
251
+ #
252
+ # s(:if,
253
+ # s(:lvar, :condition),
254
+ # s(:lvar, :then_val),
255
+ # s(:lvar, :else_val)))
256
+ # ^---else caluse---^
257
+ def else_clause
258
+ expect :if
259
+ self[3]
260
+ end
261
+
262
+ #Method call associated with a block:
263
+ #
264
+ # s(:iter,
265
+ # s(:call, nil, :x, s(:arglist)), <- block_call
266
+ # s(:lasgn, :y),
267
+ # s(:block, s(:lvar, :y), s(:call, nil, :z, s(:arglist))))
268
+ def block_call
269
+ expect :iter, :call_with_block
270
+ self[1]
271
+ end
272
+
273
+ #Returns block of a call with a block.
274
+ #Could be a single expression or a block:
275
+ #
276
+ # s(:iter,
277
+ # s(:call, nil, :x, s(:arglist)),
278
+ # s(:lasgn, :y),
279
+ # s(:block, s(:lvar, :y), s(:call, nil, :z, s(:arglist))))
280
+ # ^-------------------- block --------------------------^
281
+ def block
282
+ expect :iter, :call_with_block, :scope
283
+
284
+ case self.node_type
285
+ when :iter, :call_with_block
286
+ self[3]
287
+ when :scope
288
+ self[1]
289
+ end
290
+ end
291
+
292
+ #Returns parameters for a block
293
+ #
294
+ # s(:iter,
295
+ # s(:call, nil, :x, s(:arglist)),
296
+ # s(:lasgn, :y), <- block_args
297
+ # s(:call, nil, :p, s(:arglist, s(:lvar, :y))))
298
+ def block_args
299
+ expect :iter, :call_with_block
300
+ self[2]
301
+ end
302
+
303
+ #Returns the left hand side of assignment or boolean:
304
+ #
305
+ # s(:lasgn, :x, s(:lit, 1))
306
+ # ^--lhs
307
+ def lhs
308
+ expect *ASSIGNMENT_BOOL
309
+ self[1]
310
+ end
311
+
312
+ #Sets the left hand side of assignment or boolean.
313
+ def lhs= exp
314
+ expect *ASSIGNMENT_BOOL
315
+ self[1] = exp
316
+ end
317
+
318
+ #Returns right side (value) of assignment or boolean:
319
+ #
320
+ # s(:lasgn, :x, s(:lit, 1))
321
+ # ^--rhs---^
322
+ def rhs
323
+ expect *ASSIGNMENT_BOOL
324
+ self[2]
325
+ end
326
+
327
+ #Sets the right hand side of assignment or boolean.
328
+ def rhs= exp
329
+ expect *ASSIGNMENT_BOOL
330
+ self[2] = exp
331
+ end
332
+
333
+ #Returns name of method being defined in a method definition.
334
+ def method_name
335
+ expect :defn, :defs, :methdef, :selfdef
336
+
337
+ case self.node_type
338
+ when :defn, :methdef
339
+ self[1]
340
+ when :defs, :selfdef
341
+ self[2]
342
+ end
343
+ end
344
+
345
+ #Sets body
346
+ def body= exp
347
+ expect :defn, :defs, :methdef, :selfdef, :class, :module
348
+
349
+ case self.node_type
350
+ when :defn, :methdef, :class
351
+ self[3] = exp
352
+ when :defs, :selfdef
353
+ self[4] = exp
354
+ when :module
355
+ self[2] = exp
356
+ end
357
+ end
358
+
359
+ #Returns body of a method definition, class, or module.
360
+ def body
361
+ expect :defn, :defs, :methdef, :selfdef, :class, :module
362
+
363
+ case self.node_type
364
+ when :defn, :methdef, :class
365
+ self[3]
366
+ when :defs, :selfdef
367
+ self[4]
368
+ when :module
369
+ self[2]
370
+ end
371
+ end
372
+
373
+ def render_type
374
+ expect :render
375
+ self[1]
376
+ end
377
+
378
+ def class_name
379
+ expect :class, :module
380
+ self[1]
381
+ end
382
+
383
+ alias module_name class_name
384
+
385
+ def parent_name
386
+ expect :class
387
+ self[2]
388
+ end
89
389
  end
90
390
 
91
391
  #Invalidate hash cache if the Sexp changes
@@ -103,4 +403,4 @@ end
103
403
  RUBY
104
404
  end
105
405
 
106
-
406
+ class WrongSexpError < RuntimeError; end