railroader 4.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (165) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +1091 -0
  3. data/FEATURES +16 -0
  4. data/README.md +174 -0
  5. data/bin/railroader +8 -0
  6. data/lib/railroader/app_tree.rb +191 -0
  7. data/lib/railroader/call_index.rb +219 -0
  8. data/lib/railroader/checks/base_check.rb +505 -0
  9. data/lib/railroader/checks/check_basic_auth.rb +88 -0
  10. data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
  11. data/lib/railroader/checks/check_content_tag.rb +200 -0
  12. data/lib/railroader/checks/check_create_with.rb +74 -0
  13. data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
  14. data/lib/railroader/checks/check_default_routes.rb +86 -0
  15. data/lib/railroader/checks/check_deserialize.rb +56 -0
  16. data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
  17. data/lib/railroader/checks/check_digest_dos.rb +38 -0
  18. data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
  19. data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
  20. data/lib/railroader/checks/check_escape_function.rb +21 -0
  21. data/lib/railroader/checks/check_evaluation.rb +35 -0
  22. data/lib/railroader/checks/check_execute.rb +189 -0
  23. data/lib/railroader/checks/check_file_access.rb +71 -0
  24. data/lib/railroader/checks/check_file_disclosure.rb +35 -0
  25. data/lib/railroader/checks/check_filter_skipping.rb +31 -0
  26. data/lib/railroader/checks/check_forgery_setting.rb +81 -0
  27. data/lib/railroader/checks/check_header_dos.rb +31 -0
  28. data/lib/railroader/checks/check_i18n_xss.rb +48 -0
  29. data/lib/railroader/checks/check_jruby_xml.rb +36 -0
  30. data/lib/railroader/checks/check_json_encoding.rb +47 -0
  31. data/lib/railroader/checks/check_json_parsing.rb +107 -0
  32. data/lib/railroader/checks/check_link_to.rb +132 -0
  33. data/lib/railroader/checks/check_link_to_href.rb +146 -0
  34. data/lib/railroader/checks/check_mail_to.rb +49 -0
  35. data/lib/railroader/checks/check_mass_assignment.rb +196 -0
  36. data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
  37. data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
  38. data/lib/railroader/checks/check_model_attributes.rb +119 -0
  39. data/lib/railroader/checks/check_model_serialize.rb +67 -0
  40. data/lib/railroader/checks/check_nested_attributes.rb +38 -0
  41. data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
  42. data/lib/railroader/checks/check_number_to_currency.rb +74 -0
  43. data/lib/railroader/checks/check_permit_attributes.rb +43 -0
  44. data/lib/railroader/checks/check_quote_table_name.rb +40 -0
  45. data/lib/railroader/checks/check_redirect.rb +256 -0
  46. data/lib/railroader/checks/check_regex_dos.rb +68 -0
  47. data/lib/railroader/checks/check_render.rb +97 -0
  48. data/lib/railroader/checks/check_render_dos.rb +37 -0
  49. data/lib/railroader/checks/check_render_inline.rb +53 -0
  50. data/lib/railroader/checks/check_response_splitting.rb +21 -0
  51. data/lib/railroader/checks/check_route_dos.rb +42 -0
  52. data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
  53. data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
  54. data/lib/railroader/checks/check_secrets.rb +40 -0
  55. data/lib/railroader/checks/check_select_tag.rb +59 -0
  56. data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
  57. data/lib/railroader/checks/check_send.rb +47 -0
  58. data/lib/railroader/checks/check_send_file.rb +19 -0
  59. data/lib/railroader/checks/check_session_manipulation.rb +35 -0
  60. data/lib/railroader/checks/check_session_settings.rb +176 -0
  61. data/lib/railroader/checks/check_simple_format.rb +58 -0
  62. data/lib/railroader/checks/check_single_quotes.rb +101 -0
  63. data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
  64. data/lib/railroader/checks/check_sql.rb +700 -0
  65. data/lib/railroader/checks/check_sql_cves.rb +106 -0
  66. data/lib/railroader/checks/check_ssl_verify.rb +48 -0
  67. data/lib/railroader/checks/check_strip_tags.rb +89 -0
  68. data/lib/railroader/checks/check_symbol_dos.rb +71 -0
  69. data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
  70. data/lib/railroader/checks/check_translate_bug.rb +45 -0
  71. data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
  72. data/lib/railroader/checks/check_unscoped_find.rb +57 -0
  73. data/lib/railroader/checks/check_validation_regex.rb +116 -0
  74. data/lib/railroader/checks/check_weak_hash.rb +148 -0
  75. data/lib/railroader/checks/check_without_protection.rb +80 -0
  76. data/lib/railroader/checks/check_xml_dos.rb +45 -0
  77. data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
  78. data/lib/railroader/checks.rb +209 -0
  79. data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
  80. data/lib/railroader/commandline.rb +179 -0
  81. data/lib/railroader/differ.rb +66 -0
  82. data/lib/railroader/file_parser.rb +54 -0
  83. data/lib/railroader/format/style.css +133 -0
  84. data/lib/railroader/options.rb +339 -0
  85. data/lib/railroader/parsers/rails2_erubis.rb +6 -0
  86. data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
  87. data/lib/railroader/parsers/rails3_erubis.rb +81 -0
  88. data/lib/railroader/parsers/template_parser.rb +108 -0
  89. data/lib/railroader/processor.rb +102 -0
  90. data/lib/railroader/processors/alias_processor.rb +1229 -0
  91. data/lib/railroader/processors/base_processor.rb +295 -0
  92. data/lib/railroader/processors/config_processor.rb +14 -0
  93. data/lib/railroader/processors/controller_alias_processor.rb +278 -0
  94. data/lib/railroader/processors/controller_processor.rb +249 -0
  95. data/lib/railroader/processors/erb_template_processor.rb +77 -0
  96. data/lib/railroader/processors/erubis_template_processor.rb +92 -0
  97. data/lib/railroader/processors/gem_processor.rb +64 -0
  98. data/lib/railroader/processors/haml_template_processor.rb +191 -0
  99. data/lib/railroader/processors/lib/basic_processor.rb +37 -0
  100. data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
  101. data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
  102. data/lib/railroader/processors/lib/find_call.rb +183 -0
  103. data/lib/railroader/processors/lib/find_return_value.rb +166 -0
  104. data/lib/railroader/processors/lib/module_helper.rb +111 -0
  105. data/lib/railroader/processors/lib/processor_helper.rb +88 -0
  106. data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
  107. data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
  108. data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
  109. data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
  110. data/lib/railroader/processors/lib/render_helper.rb +181 -0
  111. data/lib/railroader/processors/lib/render_path.rb +107 -0
  112. data/lib/railroader/processors/lib/route_helper.rb +68 -0
  113. data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
  114. data/lib/railroader/processors/library_processor.rb +74 -0
  115. data/lib/railroader/processors/model_processor.rb +91 -0
  116. data/lib/railroader/processors/output_processor.rb +144 -0
  117. data/lib/railroader/processors/route_processor.rb +17 -0
  118. data/lib/railroader/processors/slim_template_processor.rb +111 -0
  119. data/lib/railroader/processors/template_alias_processor.rb +118 -0
  120. data/lib/railroader/processors/template_processor.rb +85 -0
  121. data/lib/railroader/report/config/remediation.yml +71 -0
  122. data/lib/railroader/report/ignore/config.rb +153 -0
  123. data/lib/railroader/report/ignore/interactive.rb +362 -0
  124. data/lib/railroader/report/pager.rb +112 -0
  125. data/lib/railroader/report/renderer.rb +24 -0
  126. data/lib/railroader/report/report_base.rb +292 -0
  127. data/lib/railroader/report/report_codeclimate.rb +79 -0
  128. data/lib/railroader/report/report_csv.rb +55 -0
  129. data/lib/railroader/report/report_hash.rb +23 -0
  130. data/lib/railroader/report/report_html.rb +216 -0
  131. data/lib/railroader/report/report_json.rb +45 -0
  132. data/lib/railroader/report/report_markdown.rb +107 -0
  133. data/lib/railroader/report/report_table.rb +117 -0
  134. data/lib/railroader/report/report_tabs.rb +17 -0
  135. data/lib/railroader/report/report_text.rb +198 -0
  136. data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
  137. data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
  138. data/lib/railroader/report/templates/error_overview.html.erb +29 -0
  139. data/lib/railroader/report/templates/header.html.erb +58 -0
  140. data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
  141. data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
  142. data/lib/railroader/report/templates/overview.html.erb +38 -0
  143. data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
  144. data/lib/railroader/report/templates/template_overview.html.erb +21 -0
  145. data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
  146. data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
  147. data/lib/railroader/report.rb +88 -0
  148. data/lib/railroader/rescanner.rb +483 -0
  149. data/lib/railroader/scanner.rb +321 -0
  150. data/lib/railroader/tracker/collection.rb +93 -0
  151. data/lib/railroader/tracker/config.rb +154 -0
  152. data/lib/railroader/tracker/constants.rb +171 -0
  153. data/lib/railroader/tracker/controller.rb +161 -0
  154. data/lib/railroader/tracker/library.rb +17 -0
  155. data/lib/railroader/tracker/model.rb +90 -0
  156. data/lib/railroader/tracker/template.rb +33 -0
  157. data/lib/railroader/tracker.rb +362 -0
  158. data/lib/railroader/util.rb +503 -0
  159. data/lib/railroader/version.rb +3 -0
  160. data/lib/railroader/warning.rb +294 -0
  161. data/lib/railroader/warning_codes.rb +117 -0
  162. data/lib/railroader.rb +544 -0
  163. data/lib/ruby_parser/bm_sexp.rb +626 -0
  164. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  165. metadata +386 -0
@@ -0,0 +1,626 @@
1
+ #Sexp changes from ruby_parser
2
+ #and some changes for caching hash value and tracking 'original' line number
3
+ #of a Sexp.
4
+ class Sexp
5
+ attr_accessor :original_line, :or_depth
6
+ ASSIGNMENT_BOOL = [:gasgn, :iasgn, :lasgn, :cvdecl, :cvasgn, :cdecl, :or, :and, :colon2, :op_asgn_or]
7
+ CALLS = [:call, :attrasgn, :safe_call, :safe_attrasgn]
8
+
9
+ def method_missing name, *args
10
+ #Railroader does not use this functionality,
11
+ #so overriding it to raise a NoMethodError.
12
+ #
13
+ #The original functionality calls find_node and optionally
14
+ #deletes the node if found.
15
+ #
16
+ #Defining a method named "return" seems like a bad idea, so we have to
17
+ #check for it here instead
18
+ if name == :return
19
+ find_node name, *args
20
+ else
21
+ raise NoMethodError.new("No method '#{name}' for Sexp", name, args)
22
+ end
23
+ end
24
+
25
+ #Create clone of Sexp and nested Sexps but not their non-Sexp contents.
26
+ #If a line number is provided, also sets line/original_line on all Sexps.
27
+ def deep_clone line = nil
28
+ s = Sexp.new
29
+
30
+ self.each do |e|
31
+ if e.is_a? Sexp
32
+ s << e.deep_clone(line)
33
+ else
34
+ s << e
35
+ end
36
+ end
37
+
38
+ if line
39
+ s.original_line = self.original_line || self.line
40
+ s.line(line)
41
+ else
42
+ s.original_line = self.original_line
43
+ s.line(self.line)
44
+ end
45
+
46
+ s
47
+ end
48
+
49
+ def paren
50
+ @paren ||= false
51
+ end
52
+
53
+ def value
54
+ raise WrongSexpError, "Sexp#value called on multi-item Sexp: `#{self.inspect}`" if size > 2
55
+ self[1]
56
+ end
57
+
58
+ def value= exp
59
+ raise WrongSexpError, "Sexp#value= called on multi-item Sexp: `#{self.inspect}`" if size > 2
60
+ @my_hash_value = nil
61
+ self[1] = exp
62
+ end
63
+
64
+ def second
65
+ self[1]
66
+ end
67
+
68
+ def to_sym
69
+ self.value.to_sym
70
+ end
71
+
72
+ def node_type= type
73
+ @my_hash_value = nil
74
+ self[0] = type
75
+ end
76
+
77
+ #Join self and exp into an :or Sexp.
78
+ #Sets or_depth.
79
+ #Used for combining "branched" values in AliasProcessor.
80
+ def combine exp, line = nil
81
+ combined = Sexp.new(:or, self, exp).line(line || -2)
82
+
83
+ combined.or_depth = [self.or_depth, exp.or_depth].compact.reduce(0, :+) + 1
84
+
85
+ combined
86
+ end
87
+
88
+ alias :node_type :sexp_type
89
+ alias :values :sexp_body # TODO: retire
90
+
91
+ alias :old_push :<<
92
+ alias :old_compact :compact
93
+ alias :old_fara :find_and_replace_all
94
+ alias :old_find_node :find_node
95
+
96
+ def << arg
97
+ @my_hash_value = nil
98
+ old_push arg
99
+ end
100
+
101
+ def hash
102
+ #There still seems to be some instances in which the hash of the
103
+ #Sexp changes, but I have not found what method call is doing it.
104
+ #Of course, Sexp is subclasses from Array, so who knows what might
105
+ #be going on.
106
+ @my_hash_value ||= super
107
+ end
108
+
109
+ def compact
110
+ @my_hash_value = nil
111
+ old_compact
112
+ end
113
+
114
+ def find_and_replace_all *args
115
+ @my_hash_value = nil
116
+ old_fara(*args)
117
+ end
118
+
119
+ def find_node *args
120
+ @my_hash_value = nil
121
+ old_find_node(*args)
122
+ end
123
+
124
+ #Raise a WrongSexpError if the nodes type does not match one of the expected
125
+ #types.
126
+ def expect *types
127
+ unless types.include? self.node_type
128
+ raise WrongSexpError, "Expected #{types.join ' or '} but given #{self.inspect}", caller[1..-1]
129
+ end
130
+ end
131
+
132
+ #Returns target of a method call:
133
+ #
134
+ #s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1)))
135
+ # ^-----------target-----------^
136
+ def target
137
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
138
+ self[1]
139
+ end
140
+
141
+ #Sets the target of a method call:
142
+ def target= exp
143
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
144
+ @my_hash_value = nil
145
+ self[1] = exp
146
+ end
147
+
148
+ #Returns method of a method call:
149
+ #
150
+ #s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1)))
151
+ # ^- method
152
+ def method
153
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn, :super, :zsuper, :result
154
+
155
+ case self.node_type
156
+ when :call, :attrasgn, :safe_call, :safe_attrasgn
157
+ self[2]
158
+ when :super, :zsuper
159
+ :super
160
+ when :result
161
+ self.last
162
+ end
163
+ end
164
+
165
+ def method= name
166
+ expect :call, :safe_call
167
+
168
+ self[2] = name
169
+ end
170
+
171
+ #Sets the arglist in a method call.
172
+ def arglist= exp
173
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
174
+ @my_hash_value = nil
175
+ start_index = 3
176
+
177
+ if exp.is_a? Sexp and exp.node_type == :arglist
178
+ exp = exp[1..-1]
179
+ end
180
+
181
+ exp.each_with_index do |e, i|
182
+ self[start_index + i] = e
183
+ end
184
+ end
185
+
186
+ def set_args *exp
187
+ self.arglist = exp
188
+ end
189
+
190
+ #Returns arglist for method call. This differs from Sexp#args, as Sexp#args
191
+ #does not return a 'real' Sexp (it does not have a node type) but
192
+ #Sexp#arglist returns a s(:arglist, ...)
193
+ #
194
+ # s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1), s(:lit, 2)))
195
+ # ^------------ arglist ------------^
196
+ def arglist
197
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn, :super, :zsuper
198
+
199
+ case self.node_type
200
+ when :call, :attrasgn, :safe_call, :safe_attrasgn
201
+ self[3..-1].unshift :arglist
202
+ when :super, :zsuper
203
+ if self[1]
204
+ self[1..-1].unshift :arglist
205
+ else
206
+ Sexp.new(:arglist)
207
+ end
208
+ end
209
+ end
210
+
211
+ #Returns arguments of a method call. This will be an 'untyped' Sexp.
212
+ #
213
+ # s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1), s(:lit, 2)))
214
+ # ^--------args--------^
215
+ def args
216
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn, :super, :zsuper
217
+
218
+ case self.node_type
219
+ when :call, :attrasgn, :safe_call, :safe_attrasgn
220
+ if self[3]
221
+ self[3..-1]
222
+ else
223
+ Sexp.new
224
+ end
225
+ when :super, :zsuper
226
+ if self[1]
227
+ self[1..-1]
228
+ else
229
+ Sexp.new
230
+ end
231
+ end
232
+ end
233
+
234
+ def each_arg replace = false
235
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn, :super, :zsuper
236
+ range = nil
237
+
238
+ case self.node_type
239
+ when :call, :attrasgn, :safe_call, :safe_attrasgn
240
+ if self[3]
241
+ range = (3...self.length)
242
+ end
243
+ when :super, :zsuper
244
+ if self[1]
245
+ range = (1...self.length)
246
+ end
247
+ end
248
+
249
+ if range
250
+ range.each do |i|
251
+ res = yield self[i]
252
+ self[i] = res if replace
253
+ end
254
+ end
255
+
256
+ self
257
+ end
258
+
259
+ def each_arg! &block
260
+ @my_hash_value = nil
261
+ self.each_arg true, &block
262
+ end
263
+
264
+ #Returns first argument of a method call.
265
+ def first_arg
266
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
267
+ self[3]
268
+ end
269
+
270
+ #Sets first argument of a method call.
271
+ def first_arg= exp
272
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
273
+ @my_hash_value = nil
274
+ self[3] = exp
275
+ end
276
+
277
+ #Returns second argument of a method call.
278
+ def second_arg
279
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
280
+ self[4]
281
+ end
282
+
283
+ #Sets second argument of a method call.
284
+ def second_arg= exp
285
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
286
+ @my_hash_value = nil
287
+ self[4] = exp
288
+ end
289
+
290
+ def third_arg
291
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
292
+ self[5]
293
+ end
294
+
295
+ def third_arg= exp
296
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
297
+ @my_hash_value = nil
298
+ self[5] = exp
299
+ end
300
+
301
+ def last_arg
302
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
303
+
304
+ if self[3]
305
+ self[-1]
306
+ else
307
+ nil
308
+ end
309
+ end
310
+
311
+ def call_chain
312
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
313
+
314
+ chain = []
315
+ call = self
316
+
317
+ while call.class == Sexp and CALLS.include? call.first
318
+ chain << call.method
319
+ call = call.target
320
+ end
321
+
322
+ chain.reverse!
323
+ chain
324
+ end
325
+
326
+ #Returns condition of an if expression:
327
+ #
328
+ # s(:if,
329
+ # s(:lvar, :condition), <-- condition
330
+ # s(:lvar, :then_val),
331
+ # s(:lvar, :else_val)))
332
+ def condition
333
+ expect :if
334
+ self[1]
335
+ end
336
+
337
+ def condition= exp
338
+ expect :if
339
+ self[1] = exp
340
+ end
341
+
342
+
343
+ #Returns 'then' clause of an if expression:
344
+ #
345
+ # s(:if,
346
+ # s(:lvar, :condition),
347
+ # s(:lvar, :then_val), <-- then clause
348
+ # s(:lvar, :else_val)))
349
+ def then_clause
350
+ expect :if
351
+ self[2]
352
+ end
353
+
354
+ #Returns 'else' clause of an if expression:
355
+ #
356
+ # s(:if,
357
+ # s(:lvar, :condition),
358
+ # s(:lvar, :then_val),
359
+ # s(:lvar, :else_val)))
360
+ # ^---else caluse---^
361
+ def else_clause
362
+ expect :if
363
+ self[3]
364
+ end
365
+
366
+ #Method call associated with a block:
367
+ #
368
+ # s(:iter,
369
+ # s(:call, nil, :x, s(:arglist)), <- block_call
370
+ # s(:lasgn, :y),
371
+ # s(:block, s(:lvar, :y), s(:call, nil, :z, s(:arglist))))
372
+ def block_call
373
+ expect :iter
374
+ self[1]
375
+ end
376
+
377
+ #Returns block of a call with a block.
378
+ #Could be a single expression or a block:
379
+ #
380
+ # s(:iter,
381
+ # s(:call, nil, :x, s(:arglist)),
382
+ # s(:lasgn, :y),
383
+ # s(:block, s(:lvar, :y), s(:call, nil, :z, s(:arglist))))
384
+ # ^-------------------- block --------------------------^
385
+ def block delete = nil
386
+ unless delete.nil? #this is from RubyParser
387
+ return find_node :block, delete
388
+ end
389
+
390
+ expect :iter, :scope, :resbody
391
+
392
+ case self.node_type
393
+ when :iter
394
+ self[3]
395
+ when :scope
396
+ self[1]
397
+ when :resbody
398
+ #This is for Ruby2Ruby ONLY
399
+ find_node :block
400
+ end
401
+ end
402
+
403
+ #Returns parameters for a block
404
+ #
405
+ # s(:iter,
406
+ # s(:call, nil, :x, s(:arglist)),
407
+ # s(:lasgn, :y), <- block_args
408
+ # s(:call, nil, :p, s(:arglist, s(:lvar, :y))))
409
+ def block_args
410
+ expect :iter
411
+ if self[2] == 0 # ?! See https://github.com/presidentbeef/railroader/issues/331
412
+ return Sexp.new(:args)
413
+ else
414
+ self[2]
415
+ end
416
+ end
417
+
418
+ def first_param
419
+ expect :args
420
+ self[1]
421
+ end
422
+
423
+ #Returns the left hand side of assignment or boolean:
424
+ #
425
+ # s(:lasgn, :x, s(:lit, 1))
426
+ # ^--lhs
427
+ def lhs
428
+ expect(*ASSIGNMENT_BOOL)
429
+ self[1]
430
+ end
431
+
432
+ #Sets the left hand side of assignment or boolean.
433
+ def lhs= exp
434
+ expect(*ASSIGNMENT_BOOL)
435
+ @my_hash_value = nil
436
+ self[1] = exp
437
+ end
438
+
439
+ #Returns right side (value) of assignment or boolean:
440
+ #
441
+ # s(:lasgn, :x, s(:lit, 1))
442
+ # ^--rhs---^
443
+ def rhs
444
+ expect :attrasgn, :safe_attrasgn, *ASSIGNMENT_BOOL
445
+
446
+ if self.node_type == :attrasgn or self.node_type == :safe_attrasgn
447
+ if self[2] == :[]=
448
+ self[4]
449
+ else
450
+ self[3]
451
+ end
452
+ else
453
+ self[2]
454
+ end
455
+ end
456
+
457
+ #Sets the right hand side of assignment or boolean.
458
+ def rhs= exp
459
+ expect :attrasgn, :safe_attrasgn, *ASSIGNMENT_BOOL
460
+ @my_hash_value = nil
461
+
462
+ if self.node_type == :attrasgn or self.node_type == :safe_attrasgn
463
+ self[3] = exp
464
+ else
465
+ self[2] = exp
466
+ end
467
+ end
468
+
469
+ #Returns name of method being defined in a method definition.
470
+ def method_name
471
+ expect :defn, :defs
472
+
473
+ case self.node_type
474
+ when :defn
475
+ self[1]
476
+ when :defs
477
+ self[2]
478
+ end
479
+ end
480
+
481
+ def formal_args
482
+ expect :defn, :defs
483
+
484
+ case self.node_type
485
+ when :defn
486
+ self[2]
487
+ when :defs
488
+ self[3]
489
+ end
490
+ end
491
+
492
+ #Sets body, which is now a complicated process because the body is no longer
493
+ #a separate Sexp, but just a list of Sexps.
494
+ def body= exp
495
+ expect :defn, :defs, :class, :module
496
+ @my_hash_value = nil
497
+
498
+ case self.node_type
499
+ when :defn, :class
500
+ index = 3
501
+ when :defs
502
+ index = 4
503
+ when :module
504
+ index = 2
505
+ end
506
+
507
+ self.slice!(index..-1) #Remove old body
508
+
509
+ if exp.first == :rlist
510
+ exp = exp[1..-1]
511
+ end
512
+
513
+ #Insert new body
514
+ exp.each do |e|
515
+ self[index] = e
516
+ index += 1
517
+ end
518
+ end
519
+
520
+ #Returns body of a method definition, class, or module.
521
+ #This will be an untyped Sexp containing a list of Sexps from the body.
522
+ def body
523
+ expect :defn, :defs, :class, :module
524
+
525
+ case self.node_type
526
+ when :defn, :class
527
+ self[3..-1]
528
+ when :defs
529
+ self[4..-1]
530
+ when :module
531
+ self[2..-1]
532
+ end
533
+ end
534
+
535
+ #Like Sexp#body, except the returned Sexp is of type :rlist
536
+ #instead of untyped.
537
+ def body_list
538
+ self.body.unshift :rlist
539
+ end
540
+
541
+ def render_type
542
+ expect :render
543
+ self[1]
544
+ end
545
+
546
+ def class_name
547
+ expect :class, :module
548
+ self[1]
549
+ end
550
+
551
+ alias module_name class_name
552
+
553
+ def parent_name
554
+ expect :class
555
+ self[2]
556
+ end
557
+
558
+ #Returns the call Sexp in a result returned from FindCall
559
+ def call
560
+ expect :result
561
+
562
+ self.last
563
+ end
564
+
565
+ #Returns the module the call is inside
566
+ def module
567
+ expect :result
568
+
569
+ self[1]
570
+ end
571
+
572
+ #Return the class the call is inside
573
+ def result_class
574
+ expect :result
575
+
576
+ self[2]
577
+ end
578
+
579
+ require 'set'
580
+ def inspect seen = Set.new
581
+ if seen.include? self.object_id
582
+ 's(...)'
583
+ else
584
+ seen << self.object_id
585
+ sexp_str = self.map do |x|
586
+ if x.is_a? Sexp
587
+ x.inspect seen
588
+ else
589
+ x.inspect
590
+ end
591
+ end.join(', ')
592
+
593
+ "s(#{sexp_str})"
594
+ end
595
+ end
596
+ end
597
+
598
+ #Invalidate hash cache if the Sexp changes
599
+ [:[]=, :clear, :collect!, :compact!, :concat, :delete, :delete_at,
600
+ :delete_if, :drop, :drop_while, :fill, :flatten!, :replace, :insert,
601
+ :keep_if, :map!, :pop, :push, :reject!, :replace, :reverse!, :rotate!,
602
+ :select!, :shift, :shuffle!, :slice!, :sort!, :sort_by!, :transpose,
603
+ :uniq!, :unshift].each do |method|
604
+
605
+ Sexp.class_eval <<-RUBY
606
+ def #{method} *args
607
+ @my_hash_value = nil
608
+ super
609
+ end
610
+ RUBY
611
+ end
612
+
613
+ #Methods used by RubyParser which would normally go through method_missing but
614
+ #we don't want that to happen because it hides Railroader errors
615
+ [:resbody, :lasgn, :iasgn, :splat].each do |method|
616
+ Sexp.class_eval <<-RUBY
617
+ def #{method} delete = false
618
+ if delete
619
+ @my_hash_value = false
620
+ end
621
+ find_node :#{method}, delete
622
+ end
623
+ RUBY
624
+ end
625
+
626
+ class WrongSexpError < RuntimeError; end
@@ -0,0 +1,116 @@
1
+ ##
2
+ # SexpProcessor provides a uniform interface to process Sexps.
3
+ #
4
+ # In order to create your own SexpProcessor subclass you'll need
5
+ # to call super in the initialize method, then set any of the
6
+ # Sexp flags you want to be different from the defaults.
7
+ #
8
+ # SexpProcessor uses a Sexp's type to determine which process method
9
+ # to call in the subclass. For Sexp <code>s(:lit, 1)</code>
10
+ # SexpProcessor will call #process_lit, if it is defined.
11
+ #
12
+
13
+ class Railroader::SexpProcessor
14
+
15
+ VERSION = 'CUSTOM'
16
+
17
+ ##
18
+ # Return a stack of contexts. Most recent node is first.
19
+
20
+ attr_reader :context
21
+
22
+ ##
23
+ # Expected result class
24
+
25
+ attr_accessor :expected
26
+
27
+ ##
28
+ # A scoped environment to make you happy.
29
+
30
+ attr_reader :env
31
+
32
+ # Cache process methods per class
33
+
34
+ def self.processors
35
+ @processors ||= {}
36
+ end
37
+
38
+ ##
39
+ # Creates a new SexpProcessor. Use super to invoke this
40
+ # initializer from SexpProcessor subclasses, then use the
41
+ # attributes above to customize the functionality of the
42
+ # SexpProcessor
43
+
44
+ def initialize
45
+ @expected = Sexp
46
+ @processors = self.class.processors
47
+ @context = []
48
+
49
+ if @processors.empty?
50
+ public_methods.each do |name|
51
+ if name.to_s.start_with? "process_" then
52
+ @processors[name[8..-1].to_sym] = name.to_sym
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ ##
59
+ # Default Sexp processor. Invokes process_<type> methods matching
60
+ # the Sexp type given. Performs additional checks as specified by
61
+ # the initializer.
62
+
63
+ def process(exp)
64
+ return nil if exp.nil?
65
+
66
+ result = nil
67
+
68
+ type = exp.first
69
+ raise "Type should be a Symbol, not: #{exp.first.inspect} in #{exp.inspect}" unless Symbol === type
70
+
71
+ in_context type do
72
+ # now do a pass with the real processor (or generic)
73
+ meth = @processors[type]
74
+ if meth then
75
+ result = self.send(meth, exp)
76
+ else
77
+ result = self.process_default(exp)
78
+ end
79
+ end
80
+
81
+ raise SexpTypeError, "Result must be a #{@expected}, was #{result.class}:#{result.inspect}" unless @expected === result
82
+
83
+ result
84
+ end
85
+
86
+ ##
87
+ # Add a scope level to the current env. Eg:
88
+ #
89
+ # def process_defn exp
90
+ # name = exp.shift
91
+ # args = process(exp.shift)
92
+ # scope do
93
+ # body = process(exp.shift)
94
+ # # ...
95
+ # end
96
+ # end
97
+ #
98
+ # env[:x] = 42
99
+ # scope do
100
+ # env[:x] # => 42
101
+ # env[:y] = 24
102
+ # end
103
+ # env[:y] # => nil
104
+
105
+ def scope &block
106
+ env.scope(&block)
107
+ end
108
+
109
+ def in_context type
110
+ self.context.unshift type
111
+
112
+ yield
113
+
114
+ self.context.shift
115
+ end
116
+ end