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.
- checksums.yaml +7 -0
- data/CHANGES.md +1091 -0
- data/FEATURES +16 -0
- data/README.md +174 -0
- data/bin/railroader +8 -0
- data/lib/railroader/app_tree.rb +191 -0
- data/lib/railroader/call_index.rb +219 -0
- data/lib/railroader/checks/base_check.rb +505 -0
- data/lib/railroader/checks/check_basic_auth.rb +88 -0
- data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
- data/lib/railroader/checks/check_content_tag.rb +200 -0
- data/lib/railroader/checks/check_create_with.rb +74 -0
- data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
- data/lib/railroader/checks/check_default_routes.rb +86 -0
- data/lib/railroader/checks/check_deserialize.rb +56 -0
- data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
- data/lib/railroader/checks/check_digest_dos.rb +38 -0
- data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
- data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
- data/lib/railroader/checks/check_escape_function.rb +21 -0
- data/lib/railroader/checks/check_evaluation.rb +35 -0
- data/lib/railroader/checks/check_execute.rb +189 -0
- data/lib/railroader/checks/check_file_access.rb +71 -0
- data/lib/railroader/checks/check_file_disclosure.rb +35 -0
- data/lib/railroader/checks/check_filter_skipping.rb +31 -0
- data/lib/railroader/checks/check_forgery_setting.rb +81 -0
- data/lib/railroader/checks/check_header_dos.rb +31 -0
- data/lib/railroader/checks/check_i18n_xss.rb +48 -0
- data/lib/railroader/checks/check_jruby_xml.rb +36 -0
- data/lib/railroader/checks/check_json_encoding.rb +47 -0
- data/lib/railroader/checks/check_json_parsing.rb +107 -0
- data/lib/railroader/checks/check_link_to.rb +132 -0
- data/lib/railroader/checks/check_link_to_href.rb +146 -0
- data/lib/railroader/checks/check_mail_to.rb +49 -0
- data/lib/railroader/checks/check_mass_assignment.rb +196 -0
- data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
- data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
- data/lib/railroader/checks/check_model_attributes.rb +119 -0
- data/lib/railroader/checks/check_model_serialize.rb +67 -0
- data/lib/railroader/checks/check_nested_attributes.rb +38 -0
- data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
- data/lib/railroader/checks/check_number_to_currency.rb +74 -0
- data/lib/railroader/checks/check_permit_attributes.rb +43 -0
- data/lib/railroader/checks/check_quote_table_name.rb +40 -0
- data/lib/railroader/checks/check_redirect.rb +256 -0
- data/lib/railroader/checks/check_regex_dos.rb +68 -0
- data/lib/railroader/checks/check_render.rb +97 -0
- data/lib/railroader/checks/check_render_dos.rb +37 -0
- data/lib/railroader/checks/check_render_inline.rb +53 -0
- data/lib/railroader/checks/check_response_splitting.rb +21 -0
- data/lib/railroader/checks/check_route_dos.rb +42 -0
- data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
- data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
- data/lib/railroader/checks/check_secrets.rb +40 -0
- data/lib/railroader/checks/check_select_tag.rb +59 -0
- data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
- data/lib/railroader/checks/check_send.rb +47 -0
- data/lib/railroader/checks/check_send_file.rb +19 -0
- data/lib/railroader/checks/check_session_manipulation.rb +35 -0
- data/lib/railroader/checks/check_session_settings.rb +176 -0
- data/lib/railroader/checks/check_simple_format.rb +58 -0
- data/lib/railroader/checks/check_single_quotes.rb +101 -0
- data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
- data/lib/railroader/checks/check_sql.rb +700 -0
- data/lib/railroader/checks/check_sql_cves.rb +106 -0
- data/lib/railroader/checks/check_ssl_verify.rb +48 -0
- data/lib/railroader/checks/check_strip_tags.rb +89 -0
- data/lib/railroader/checks/check_symbol_dos.rb +71 -0
- data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
- data/lib/railroader/checks/check_translate_bug.rb +45 -0
- data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
- data/lib/railroader/checks/check_unscoped_find.rb +57 -0
- data/lib/railroader/checks/check_validation_regex.rb +116 -0
- data/lib/railroader/checks/check_weak_hash.rb +148 -0
- data/lib/railroader/checks/check_without_protection.rb +80 -0
- data/lib/railroader/checks/check_xml_dos.rb +45 -0
- data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
- data/lib/railroader/checks.rb +209 -0
- data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
- data/lib/railroader/commandline.rb +179 -0
- data/lib/railroader/differ.rb +66 -0
- data/lib/railroader/file_parser.rb +54 -0
- data/lib/railroader/format/style.css +133 -0
- data/lib/railroader/options.rb +339 -0
- data/lib/railroader/parsers/rails2_erubis.rb +6 -0
- data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
- data/lib/railroader/parsers/rails3_erubis.rb +81 -0
- data/lib/railroader/parsers/template_parser.rb +108 -0
- data/lib/railroader/processor.rb +102 -0
- data/lib/railroader/processors/alias_processor.rb +1229 -0
- data/lib/railroader/processors/base_processor.rb +295 -0
- data/lib/railroader/processors/config_processor.rb +14 -0
- data/lib/railroader/processors/controller_alias_processor.rb +278 -0
- data/lib/railroader/processors/controller_processor.rb +249 -0
- data/lib/railroader/processors/erb_template_processor.rb +77 -0
- data/lib/railroader/processors/erubis_template_processor.rb +92 -0
- data/lib/railroader/processors/gem_processor.rb +64 -0
- data/lib/railroader/processors/haml_template_processor.rb +191 -0
- data/lib/railroader/processors/lib/basic_processor.rb +37 -0
- data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
- data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
- data/lib/railroader/processors/lib/find_call.rb +183 -0
- data/lib/railroader/processors/lib/find_return_value.rb +166 -0
- data/lib/railroader/processors/lib/module_helper.rb +111 -0
- data/lib/railroader/processors/lib/processor_helper.rb +88 -0
- data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
- data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
- data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
- data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
- data/lib/railroader/processors/lib/render_helper.rb +181 -0
- data/lib/railroader/processors/lib/render_path.rb +107 -0
- data/lib/railroader/processors/lib/route_helper.rb +68 -0
- data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
- data/lib/railroader/processors/library_processor.rb +74 -0
- data/lib/railroader/processors/model_processor.rb +91 -0
- data/lib/railroader/processors/output_processor.rb +144 -0
- data/lib/railroader/processors/route_processor.rb +17 -0
- data/lib/railroader/processors/slim_template_processor.rb +111 -0
- data/lib/railroader/processors/template_alias_processor.rb +118 -0
- data/lib/railroader/processors/template_processor.rb +85 -0
- data/lib/railroader/report/config/remediation.yml +71 -0
- data/lib/railroader/report/ignore/config.rb +153 -0
- data/lib/railroader/report/ignore/interactive.rb +362 -0
- data/lib/railroader/report/pager.rb +112 -0
- data/lib/railroader/report/renderer.rb +24 -0
- data/lib/railroader/report/report_base.rb +292 -0
- data/lib/railroader/report/report_codeclimate.rb +79 -0
- data/lib/railroader/report/report_csv.rb +55 -0
- data/lib/railroader/report/report_hash.rb +23 -0
- data/lib/railroader/report/report_html.rb +216 -0
- data/lib/railroader/report/report_json.rb +45 -0
- data/lib/railroader/report/report_markdown.rb +107 -0
- data/lib/railroader/report/report_table.rb +117 -0
- data/lib/railroader/report/report_tabs.rb +17 -0
- data/lib/railroader/report/report_text.rb +198 -0
- data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
- data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/error_overview.html.erb +29 -0
- data/lib/railroader/report/templates/header.html.erb +58 -0
- data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
- data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/overview.html.erb +38 -0
- data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
- data/lib/railroader/report/templates/template_overview.html.erb +21 -0
- data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
- data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
- data/lib/railroader/report.rb +88 -0
- data/lib/railroader/rescanner.rb +483 -0
- data/lib/railroader/scanner.rb +321 -0
- data/lib/railroader/tracker/collection.rb +93 -0
- data/lib/railroader/tracker/config.rb +154 -0
- data/lib/railroader/tracker/constants.rb +171 -0
- data/lib/railroader/tracker/controller.rb +161 -0
- data/lib/railroader/tracker/library.rb +17 -0
- data/lib/railroader/tracker/model.rb +90 -0
- data/lib/railroader/tracker/template.rb +33 -0
- data/lib/railroader/tracker.rb +362 -0
- data/lib/railroader/util.rb +503 -0
- data/lib/railroader/version.rb +3 -0
- data/lib/railroader/warning.rb +294 -0
- data/lib/railroader/warning_codes.rb +117 -0
- data/lib/railroader.rb +544 -0
- data/lib/ruby_parser/bm_sexp.rb +626 -0
- data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
- 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
|