railroader 4.3.4

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 (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