code_analyzer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format nested
2
+ --color
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm_gemset_create_on_use_flag=1
2
+ rvm gemset use ruby-1.9.3-p194@code_analyzer
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in code_analyzer.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Richard Huang
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # CodeAnalyzer
2
+
3
+ code_analyzer is extracted from [rails_best_practices][0], it helps you
4
+ easily build your own code analyzer tool.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'code_analyzer'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install code_analyzer
19
+
20
+ ## Usage
21
+
22
+ TODO: I will write instructions later.
23
+
24
+ ## Contributing
25
+
26
+ 1. Fork it
27
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
28
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
29
+ 4. Push to the branch (`git push origin my-new-feature`)
30
+ 5. Create new Pull Request
31
+
32
+ [0]: https://github.com/railsbp/rails_best_practices
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/code_analyzer/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Richard Huang"]
6
+ gem.email = ["flyerhzm@gmail.com"]
7
+ gem.description = %q{a code analyzer tool which extracted from rails_best_practices, it helps you easily build your own code analyzer tool.}
8
+ gem.summary = %q{a code analyzer helps you build your own code analyzer tool.}
9
+ gem.homepage = "https://github.com/flyerhzm/code_analyzer"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "code_analyzer"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = CodeAnalyzer::VERSION
17
+
18
+ gem.add_dependency "sexp_processor"
19
+ gem.add_development_dependency "rspec"
20
+ end
@@ -0,0 +1,11 @@
1
+ require "ripper"
2
+ require "code_analyzer/version"
3
+ require "code_analyzer/nil"
4
+ require "code_analyzer/sexp"
5
+
6
+ module CodeAnalyzer
7
+ autoload :AnalyzerException, "code_analyzer/analyzer_exception"
8
+ autoload :Checker, "code_analyzer/checker"
9
+ autoload :CheckingVisitor, "code_analyzer/checking_visitor"
10
+ autoload :Warning, "code_analyzer/warning"
11
+ end
@@ -0,0 +1,3 @@
1
+ module CodeAnalyzer
2
+ class AnalyzerException < Exception; end
3
+ end
@@ -0,0 +1,90 @@
1
+ # encoding: utf-8
2
+ module CodeAnalyzer
3
+ # A checker class that takes charge of checking the sexp.
4
+ class Checker
5
+ # interesting nodes that the check will parse.
6
+ def interesting_nodes
7
+ self.class.interesting_nodes
8
+ end
9
+
10
+ # interesting files that the check will parse.
11
+ def interesting_files
12
+ self.class.interesting_files
13
+ end
14
+
15
+ # check if the checker will parse the node file.
16
+ #
17
+ # @param [String] the file name of node.
18
+ # @return [Boolean] true if the checker will parse the file.
19
+ def parse_file?(node_file)
20
+ interesting_files.any? { |pattern| node_file =~ pattern }
21
+ end
22
+
23
+ # delegate to start_### according to the sexp_type, like
24
+ #
25
+ # start_call
26
+ # start_def
27
+ #
28
+ # @param [Sexp] node
29
+ def node_start(node)
30
+ @node = node
31
+ self.class.get_callbacks("start_#{node.sexp_type}".to_sym).each do |block|
32
+ self.instance_exec(node, &block)
33
+ end
34
+ end
35
+
36
+ # delegate to end_### according to the sexp_type, like
37
+ #
38
+ # end_call
39
+ # end_def
40
+ #
41
+ # @param [Sexp] node
42
+ def node_end(node)
43
+ @node = node
44
+ self.class.get_callbacks("end_#{node.sexp_type}".to_sym).each do |block|
45
+ self.instance_exec(node, &block)
46
+ end
47
+ end
48
+
49
+ # add an warning.
50
+ #
51
+ # @param [String] message, is the warning message
52
+ # @param [String] filename, is the filename of source code
53
+ # @param [Integer] line_number, is the line number of the source code which is reviewing
54
+ def add_warning(message, filename = @node.file, line_number = @node.line)
55
+ warnings << Warning.new(filename: filename, line_number: line_number, message: message)
56
+ end
57
+
58
+ def warnings
59
+ @warnings ||= []
60
+ end
61
+
62
+ class <<self
63
+ def interesting_nodes(*nodes)
64
+ @interesting_nodes ||= []
65
+ @interesting_nodes += nodes
66
+ end
67
+
68
+ def interesting_files(*file_patterns)
69
+ @interesting_files ||= []
70
+ @interesting_files += file_patterns
71
+ end
72
+
73
+ def get_callbacks(name)
74
+ callbacks[name] ||= []
75
+ callbacks[name]
76
+ end
77
+
78
+ def add_callback(*names, &block)
79
+ names.each do |name|
80
+ callbacks[name] ||= []
81
+ callbacks[name] << block
82
+ end
83
+ end
84
+
85
+ def callbacks
86
+ @callbacks ||= {}
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+ module CodeAnalyzer
3
+ module CheckingVisitor
4
+ autoload :Plain, "code_analyzer/checking_visitor/plain"
5
+ autoload :Default, "code_analyzer/checking_visitor/default"
6
+ end
7
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+ module CodeAnalyzer::CheckingVisitor
3
+ class Default
4
+ def initialize(options={})
5
+ @checks = {}
6
+ @checkers = options[:checkers]
7
+ @checkers.each do |checker|
8
+ checker.interesting_nodes.each do |node|
9
+ @checks[node] ||= []
10
+ @checks[node] << checker
11
+ @checks[node].uniq!
12
+ end
13
+ end
14
+ end
15
+
16
+ def check(filename, content)
17
+ node = parse(filename, content)
18
+ node.file = filename
19
+ check_node(node)
20
+ end
21
+
22
+ def after_check
23
+ @checkers.each do |checker|
24
+ after_check_callbacks = checker.class.get_callbacks(:after_check)
25
+ after_check_callbacks.each do |block|
26
+ checker.instance_exec &block
27
+ end
28
+ end
29
+ end
30
+
31
+ # parse ruby code.
32
+ #
33
+ # @param [String] filename is the filename of ruby file.
34
+ # @param [String] content is the source code of ruby file.
35
+ def parse(filename, content)
36
+ Sexp.from_array(Ripper::SexpBuilder.new(content).parse)
37
+ rescue Exception
38
+ raise AnalyzerException.new("#{filename} looks like it's not a valid Ruby file. Skipping...")
39
+ end
40
+
41
+ def check_node(node)
42
+ checkers = @checks[node.sexp_type]
43
+ if checkers
44
+ checkers.each { |checker|
45
+ checker.node_start(node) if checker.parse_file?(node.file)
46
+ }
47
+ end
48
+ node.children.each { |child_node|
49
+ child_node.file = node.file
50
+ child_node.check(self)
51
+ }
52
+ if checkers
53
+ checkers.each { |checker|
54
+ checker.node_end(node) if checker.parse_file?(node.file)
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+ module CodeAnalyzer::CheckingVisitor
3
+ class Plain
4
+ def initialize(options={})
5
+ @checkers = options[:checkers]
6
+ end
7
+
8
+ def check(filename, content)
9
+ @checkers.each do |checker|
10
+ checker.check(filename, content)
11
+ end
12
+ end
13
+
14
+ def after_check
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ module CodeAnalyzer
3
+ # Fake nil.
4
+ class Nil
5
+ # hash_size is 0.
6
+ def hash_size
7
+ 0
8
+ end
9
+
10
+ # array_size is 0.
11
+ def array_size
12
+ 0
13
+ end
14
+
15
+ # return self for to_s.
16
+ def to_s
17
+ self
18
+ end
19
+
20
+ # false
21
+ def present?
22
+ false
23
+ end
24
+
25
+ # true
26
+ def blank?
27
+ true
28
+ end
29
+
30
+ # return self.
31
+ def method_missing(method_sym, *arguments, &block)
32
+ self
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,836 @@
1
+ # encoding: utf-8
2
+ require 'sexp'
3
+
4
+ class Sexp
5
+ # check current node.
6
+ #
7
+ # @param [CodeAnalyzer::CheckingVisitor::Default] visitor the visitor to check current node
8
+ def check(visitor)
9
+ visitor.check_node(self)
10
+ end
11
+
12
+ # return the line number of a sexp node.
13
+ #
14
+ # s(:@ident, "test", s(2, 12)
15
+ # => 2
16
+ def line
17
+ case sexp_type
18
+ when :def, :defs, :command, :command_call, :call, :fcall, :method_add_arg, :method_add_block,
19
+ :var_ref, :vcall, :const_ref, :const_path_ref, :class, :module, :if, :unless, :elsif, :ifop, :binary,
20
+ :alias, :symbol_literal, :symbol, :aref, :hash, :assoc_new, :string_literal
21
+ self[1].line
22
+ when :assoclist_from_args, :bare_assoc_hash
23
+ self[1][0].line
24
+ when :string_add
25
+ self[2].line
26
+ when :array
27
+ array_values.first.line
28
+ else
29
+ self.last.first if self.last.is_a? Array
30
+ end
31
+ end
32
+
33
+ # return child nodes of a sexp node.
34
+ #
35
+ # @return [Array] child nodes.
36
+ def children
37
+ find_all { | sexp | Sexp === sexp }
38
+ end
39
+
40
+ # recursively find all child nodes, and yeild each child node.
41
+ def recursive_children
42
+ children.each do |child|
43
+ yield child
44
+ child.recursive_children { |c| yield c }
45
+ end
46
+ end
47
+
48
+ # grep all the recursive child nodes with conditions, and yield each match node.
49
+ #
50
+ # @param [Hash] options grep conditions
51
+ #
52
+ # options is the grep conditions, like
53
+ #
54
+ # sexp_type: :call,
55
+ # receiver: "Post",
56
+ # message: ["find", "new"]
57
+ # to_s: "devise"
58
+ #
59
+ # the condition key is one of :sexp_type, :receiver, :message, :to_s,
60
+ # the condition value can be Symbol, Array or Sexp.
61
+ def grep_nodes(options)
62
+ sexp_type = options[:sexp_type]
63
+ receiver = options[:receiver]
64
+ message = options[:message]
65
+ to_s = options[:to_s]
66
+ self.recursive_children do |child|
67
+ if (!sexp_type || (sexp_type.is_a?(Array) ? sexp_type.include?(child.sexp_type) : sexp_type == child.sexp_type)) &&
68
+ (!receiver || (receiver.is_a?(Array) ? receiver.include?(child.receiver.to_s) : receiver == child.receiver.to_s)) &&
69
+ (!message || (message.is_a?(Array) ? message.include?(child.message.to_s) : message == child.message.to_s)) &&
70
+ (!to_s || (to_s.is_a?(Array) ? to_s.include?(child.to_s) : to_s == child.to_s))
71
+ yield child
72
+ end
73
+ end
74
+ end
75
+
76
+ # grep all the recursive child nodes with conditions, and yield the first match node.
77
+ #
78
+ # @param [Hash] options grep conditions
79
+ #
80
+ # options is the grep conditions, like
81
+ #
82
+ # sexp_type: :call,
83
+ # receiver: s(:const, Post),
84
+ # message: [:find, :new]
85
+ #
86
+ # the condition key is one of :sexp_type, :receiver, :message, and to_s,
87
+ # the condition value can be Symbol, Array or Sexp.
88
+ def grep_node(options)
89
+ result = CodeAnalyzer::Nil.new
90
+ grep_nodes(options) { |node| result = node; break; }
91
+ result
92
+ end
93
+
94
+ # grep all the recursive child nodes with conditions, and get the count of match nodes.
95
+ #
96
+ # @param [Hash] options grep conditions
97
+ # @return [Integer] the count of metch nodes
98
+ def grep_nodes_count(options)
99
+ count = 0
100
+ grep_nodes(options) { |node| count += 1 }
101
+ count
102
+ end
103
+
104
+ # Get receiver node.
105
+ #
106
+ # s(:call,
107
+ # s(:var_ref,
108
+ # s(:@ident, "user", s(1, 0))
109
+ # ),
110
+ # :".",
111
+ # s(:@ident, "name", s(1, 5))
112
+ # )
113
+ # => s(:var_ref,
114
+ # s(:@ident, "user", s(1, 0))
115
+ # )
116
+ #
117
+ # @return [Sexp] receiver node
118
+ def receiver
119
+ case sexp_type
120
+ when :assign, :field, :call, :binary, :command_call
121
+ self[1]
122
+ when :method_add_arg, :method_add_block
123
+ self[1].receiver
124
+ end
125
+ end
126
+
127
+ # Get the module name of the module node.
128
+ #
129
+ # s(:module,
130
+ # s(:const_ref, s(:@const, "Admin", s(1, 7))),
131
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
132
+ # )
133
+ # => s(:const_ref, s(:@const, "Admin", s(1, 7))),
134
+ #
135
+ # @return [Sexp] module name node
136
+ def module_name
137
+ if :module == sexp_type
138
+ self[1]
139
+ end
140
+ end
141
+
142
+ # Get the class name of the class node.
143
+ #
144
+ # s(:class,
145
+ # s(:const_ref, s(:@const, "User", s(1, 6))),
146
+ # nil,
147
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
148
+ # )
149
+ # => s(:const_ref, s(:@const, "User", s(1, 6))),
150
+ #
151
+ # @return [Sexp] class name node
152
+ def class_name
153
+ if :class == sexp_type
154
+ self[1]
155
+ end
156
+ end
157
+
158
+ # Get the base class of the class node.
159
+ #
160
+ # s(:class,
161
+ # s(:const_ref, s(:@const, "User", s(1, 6))),
162
+ # s(:const_path_ref, s(:var_ref, s(:@const, "ActiveRecord", s(1, 13))), s(:@const, "Base", s(1, 27))),
163
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
164
+ # )
165
+ # => s(:const_path_ref, s(:var_ref, s(:@const, "ActiveRecord", s(1, 13))), s(:@const, "Base", s(1, 27))),
166
+ #
167
+ # @return [Sexp] base class of class node
168
+ def base_class
169
+ if :class == sexp_type
170
+ self[2]
171
+ end
172
+ end
173
+
174
+ # Get the left value of the assign node.
175
+ #
176
+ # s(:assign,
177
+ # s(:var_field, s(:@ident, "user", s(1, 0))),
178
+ # s(:var_ref, s(:@ident, "current_user", s(1, 7)))
179
+ # )
180
+ # => s(:var_field, s(:@ident, "user", s(1, 0))),
181
+ #
182
+ # @return [Symbol] left value of lasgn or iasgn node
183
+ def left_value
184
+ if :assign == sexp_type
185
+ self[1]
186
+ end
187
+ end
188
+
189
+ # Get the right value of assign node.
190
+ #
191
+ # s(:assign,
192
+ # s(:var_field, s(:@ident, "user", s(1, 0))),
193
+ # s(:var_ref, s(:@ident, "current_user", s(1, 7)))
194
+ # )
195
+ # => s(:var_ref, s(:@ident, "current_user", s(1, 7)))
196
+ #
197
+ # @return [Sexp] right value of assign node
198
+ def right_value
199
+ if :assign == sexp_type
200
+ self[2]
201
+ end
202
+ end
203
+
204
+ # Get the message node.
205
+ #
206
+ # s(:command,
207
+ # s(:@ident, "has_many", s(1, 0)),
208
+ # s(:args_add_block,
209
+ # s(:args_add, s(:args_new),
210
+ # s(:symbol_literal, s(:symbol, s(:@ident, "projects", s(1, 10))))
211
+ # ),
212
+ # false
213
+ # )
214
+ # )
215
+ # => s(:@ident, "has_many", s(1, 0)),
216
+ #
217
+ # @return [Symbol] message node
218
+ def message
219
+ case sexp_type
220
+ when :command, :fcall
221
+ self[1]
222
+ when :binary
223
+ self[2]
224
+ when :command_call, :field, :call
225
+ self[3]
226
+ when :method_add_arg, :method_add_block
227
+ self[1].message
228
+ end
229
+ end
230
+
231
+ # Get arguments node.
232
+ #
233
+ # s(:command,
234
+ # s(:@ident, "resources", s(1, 0)),
235
+ # s(:args_add_block,
236
+ # s(:args_add, s(:args_new),
237
+ # s(:symbol_literal, s(:symbol, s(:@ident, "posts", s(1, 11))))
238
+ # ), false
239
+ # )
240
+ # )
241
+ # => s(:args_add_block,
242
+ # s(:args_add, s(:args_new),
243
+ # s(:symbol_literal, s(:symbol, s(:@ident, "posts", s(1, 11))))
244
+ # ), false
245
+ # )
246
+ #
247
+ # @return [Sexp] arguments node
248
+ def arguments
249
+ case sexp_type
250
+ when :command
251
+ self[2]
252
+ when :command_call
253
+ self[4]
254
+ when :method_add_arg
255
+ self[2].arguments
256
+ when :method_add_block
257
+ self[1].arguments
258
+ when :arg_paren
259
+ self[1]
260
+ when :array
261
+ self
262
+ end
263
+ end
264
+
265
+ # Get only argument for binary.
266
+ #
267
+ # s(:binary,
268
+ # s(:var_ref, s(:@ident, "user", s(1, 0))),
269
+ # :==,
270
+ # s(:var_ref, s(:@ident, "current_user", s(1, 8)))
271
+ # )
272
+ # => s(:var_ref, s(:@ident, "current_user", s(1, 8)))
273
+ #
274
+ # @return [Sexp] argument node
275
+ def argument
276
+ if :binary == sexp_type
277
+ self[3]
278
+ end
279
+ end
280
+
281
+ # Get all arguments.
282
+ #
283
+ # s(:args_add_block,
284
+ # s(:args_add,
285
+ # s(:args_add, s(:args_new), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "hello", s(1, 6))))),
286
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "world", s(1, 15))))
287
+ # ), false
288
+ # )
289
+ # => [
290
+ # s(:args_add, s(:args_new), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "hello", s(1, 6))))),
291
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "world", s(1, 15))))
292
+ # ]
293
+ #
294
+ # @return [Array] all arguments
295
+ def all
296
+ nodes = []
297
+ case sexp_type
298
+ when :args_add_block, :array
299
+ if :args_new == self[1].sexp_type
300
+ nodes << self[2]
301
+ else
302
+ node = self[1]
303
+ while true
304
+ if [:args_add, :args_add_star].include? node.sexp_type
305
+ nodes.unshift node[2]
306
+ node = node[1]
307
+ elsif :args_new == node.sexp_type
308
+ break
309
+ end
310
+ end
311
+ end
312
+ when :args_add
313
+ nodes.unshift self[2]
314
+ end
315
+ nodes
316
+ end
317
+
318
+ # Get the conditional statement of if node.
319
+ #
320
+ # s(:if,
321
+ # s(:var_ref, s(:@kw, "true", s(1, 3))),
322
+ # s(:stmts_add, s(:stmts_new), s(:void_stmt)),
323
+ # nil
324
+ # )
325
+ # => s(:var_ref, s(:@kw, "true", s(1, 3))),
326
+ #
327
+ # @return [Sexp] conditional statement of if node
328
+ def conditional_statement
329
+ if [:if, :unless, :elsif, :ifop].include? sexp_type
330
+ self[1]
331
+ end
332
+ end
333
+
334
+ # Get all condition nodes.
335
+ #
336
+ # s(:binary,
337
+ # s(:binary,
338
+ # s(:var_ref, s(:@ident, "user", s(1, 0))),
339
+ # :==,
340
+ # s(:var_ref, s(:@ident, "current_user", s(1, 8)))
341
+ # ),
342
+ # :"&&",
343
+ # s(:call,
344
+ # s(:var_ref, s(:@ident, "user", s(1, 24))),
345
+ # :".",
346
+ # s(:@ident, "valid?", s(1, 29))
347
+ # )
348
+ # )
349
+ # => [
350
+ # s(:binary,
351
+ # s(:var_ref, s(:@ident, "user", s(1, 0))),
352
+ # :==,
353
+ # s(:var_ref, s(:@ident, "current_user", s(1, 8)))
354
+ # ),
355
+ # s(:call,
356
+ # s(:var_ref, s(:@ident, "user", s(1, 24))),
357
+ # :".",
358
+ # s(:@ident, "valid?", s(1, 29))
359
+ # )
360
+ # ]
361
+ #
362
+ # @return [Array] all condition nodes
363
+ def all_conditions
364
+ nodes = []
365
+ if :binary == sexp_type && %w(&& || and or).include?(self[2].to_s)
366
+ if :binary == self[1].sexp_type && %w(&& || and or).include?(self[1][2].to_s)
367
+ nodes += self[1].all_conditions
368
+ else
369
+ nodes << self[1]
370
+ end
371
+ if :binary == self[3].sexp_type && %w(&& || and or).include?(self[3][2].to_s)
372
+ nodes += self[3].all_conditions
373
+ else
374
+ nodes << self[3]
375
+ end
376
+ else
377
+ self
378
+ end
379
+ end
380
+
381
+ # Get the method name of def node.
382
+ #
383
+ # s(:def,
384
+ # s(:@ident, "show", s(1, 4)),
385
+ # s(:params, nil, nil, nil, nil, nil),
386
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
387
+ # )
388
+ # => s(:@ident, "show", s(1, 4)),
389
+ #
390
+ # @return [Sexp] method name node
391
+ def method_name
392
+ case sexp_type
393
+ when :def
394
+ self[1]
395
+ when :defs
396
+ self[3]
397
+ else
398
+ end
399
+ end
400
+
401
+ # Get body node.
402
+ #
403
+ # s(:class,
404
+ # s(:const_ref, s(:@const, "User", s(1, 6))),
405
+ # nil,
406
+ # s(:bodystmt,
407
+ # s(:stmts_add, s(:stmts_new),
408
+ # s(:def,
409
+ # s(:@ident, "login", s(1, 16)),
410
+ # s(:params, nil, nil, nil, nil, nil),
411
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
412
+ # )
413
+ # ), nil, nil, nil
414
+ # )
415
+ # )
416
+ # => s(:bodystmt,
417
+ # s(:stmts_add, s(:stmts_new),
418
+ # s(:def,
419
+ # s(:@ident, "login", s(1, 16)),
420
+ # s(:params, nil, nil, nil, nil, nil),
421
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
422
+ # )
423
+ # ), nil, nil, nil
424
+ # )
425
+ #
426
+ # @return [Sexp] body node
427
+ def body
428
+ case sexp_type
429
+ when :else
430
+ self[1]
431
+ when :module, :if, :elsif, :unless
432
+ self[2]
433
+ when :class, :def
434
+ self[3]
435
+ when :defs
436
+ self[5]
437
+ end
438
+ end
439
+
440
+ # Get block node.
441
+ #
442
+ # s(:method_add_block,
443
+ # s(:command,
444
+ # s(:@ident, "resources", s(1, 0)),
445
+ # s(:args_add_block, s(:args_add, s(:args_new), s(:symbol_literal, s(:symbol, s(:@ident, "posts", s(1, 11))))), false)
446
+ # ),
447
+ # s(:do_block, nil,
448
+ # s(:stmts_add, s(:stmts_add, s(:stmts_new), s(:void_stmt)),
449
+ # s(:command,
450
+ # s(:@ident, "resources", s(1, 21)),
451
+ # s(:args_add_block, s(:args_add, s(:args_new), s(:symbol_literal, s(:symbol, s(:@ident, "comments", s(1, 32))))), false))
452
+ # )
453
+ # )
454
+ # )
455
+ # => s(:do_block, nil,
456
+ # s(:stmts_add, s(:stmts_add, s(:stmts_new), s(:void_stmt)),
457
+ # s(:command,
458
+ # s(:@ident, "resources", s(1, 21)),
459
+ # s(:args_add_block, s(:args_add, s(:args_new), s(:symbol_literal, s(:symbol, s(:@ident, "comments", s(1, 32))))), false))
460
+ # )
461
+ # )
462
+ #
463
+ # @return [Sexp] body node
464
+ def block
465
+ case sexp_type
466
+ when :method_add_block
467
+ self[2]
468
+ end
469
+ end
470
+
471
+ # Get all statements nodes.
472
+ #
473
+ # s(:bodystmt,
474
+ # s(:stmts_add,
475
+ # s(:stmts_add, s(:stmts_new),
476
+ # s(:def,
477
+ # s(:@ident, "login?", s(1, 16)),
478
+ # s(:params, nil, nil, nil, nil, nil),
479
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
480
+ # )
481
+ # ),
482
+ # s(:def,
483
+ # s(:@ident, "admin?", s(1, 33)),
484
+ # s(:params, nil, nil, nil, nil, nil),
485
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
486
+ # )
487
+ # ), nil, nil, nil
488
+ # )
489
+ # => [
490
+ # s(:def,
491
+ # s(:@ident, "login?", s(1, 16)),
492
+ # s(:params, nil, nil, nil, nil, nil),
493
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
494
+ # ),
495
+ # s(:def,
496
+ # s(:@ident, "admin?", s(1, 33)),
497
+ # s(:params, nil, nil, nil, nil, nil),
498
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
499
+ # )
500
+ # ]
501
+ #
502
+ # @return [Array] all statements
503
+ def statements
504
+ stmts = []
505
+ node = case sexp_type
506
+ when :do_block, :brace_block
507
+ self[2]
508
+ when :bodystmt
509
+ self[1]
510
+ else
511
+ end
512
+ if node
513
+ while true
514
+ if :stmts_add == node.sexp_type && s(:void_stmt) != node[2]
515
+ stmts.unshift node[2]
516
+ node = node[1]
517
+ else
518
+ break
519
+ end
520
+ end
521
+ end
522
+ stmts
523
+ end
524
+
525
+ # Get hash value node.
526
+ #
527
+ # s(:hash,
528
+ # s(:assoclist_from_args,
529
+ # s(
530
+ # s(:assoc_new, s(:@label, "first_name:", s(1, 1)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Richard", s(1, 14))))),
531
+ # s(:assoc_new, s(:@label, "last_name:", s(1, 24)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Huang", s(1, 36)))))
532
+ # )
533
+ # )
534
+ # )
535
+ # => s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Richard", s(1, 14))))
536
+ #
537
+ # @return [Sexp] hash value node
538
+ def hash_value(key)
539
+ pair_nodes = case sexp_type
540
+ when :bare_assoc_hash
541
+ self[1]
542
+ when :hash
543
+ self[1][1]
544
+ else
545
+ end
546
+ if pair_nodes
547
+ pair_nodes.size.times do |i|
548
+ if key == pair_nodes[i][1].to_s
549
+ return pair_nodes[i][2]
550
+ end
551
+ end
552
+ end
553
+ CodeAnalyzer::Nil.new
554
+ end
555
+
556
+ # Get hash size.
557
+ #
558
+ # s(:hash,
559
+ # s(:assoclist_from_args,
560
+ # s(
561
+ # s(:assoc_new, s(:@label, "first_name:", s(1, 1)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Richard", s(1, 14))))),
562
+ # s(:assoc_new, s(:@label, "last_name:", s(1, 24)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Huang", s(1, 36)))))
563
+ # )
564
+ # )
565
+ # )
566
+ # => 2
567
+ #
568
+ # @return [Integer] hash size
569
+ def hash_size
570
+ case sexp_type
571
+ when :hash
572
+ self[1].hash_size
573
+ when :assoclist_from_args
574
+ self[1].size
575
+ when :bare_assoc_hash
576
+ self[1].size
577
+ end
578
+ end
579
+
580
+ # Get the hash keys.
581
+ #
582
+ # s(:hash,
583
+ # s(:assoclist_from_args,
584
+ # s(
585
+ # s(:assoc_new, s(:@label, "first_name:", s(1, 1)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Richard", s(1, 14))))),
586
+ # s(:assoc_new, s(:@label, "last_name:", s(1, 24)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Huang", s(1, 36)))))
587
+ # )
588
+ # )
589
+ # )
590
+ # => ["first_name", "last_name"]
591
+ #
592
+ # @return [Array] hash keys
593
+ def hash_keys
594
+ pair_nodes = case sexp_type
595
+ when :bare_assoc_hash
596
+ self[1]
597
+ when :hash
598
+ self[1][1]
599
+ else
600
+ end
601
+ if pair_nodes
602
+ keys = []
603
+ pair_nodes.size.times do |i|
604
+ keys << pair_nodes[i][1].to_s
605
+ end
606
+ keys
607
+ end
608
+ end
609
+
610
+ # Get the hash values.
611
+ #
612
+ # s(:hash,
613
+ # s(:assoclist_from_args,
614
+ # s(
615
+ # s(:assoc_new, s(:@label, "first_name:", s(1, 1)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Richard", s(1, 14))))),
616
+ # s(:assoc_new, s(:@label, "last_name:", s(1, 24)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Huang", s(1, 36)))))
617
+ # )
618
+ # )
619
+ # )
620
+ # => [
621
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Richard", s(1, 14)))),
622
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Huang", s(1, 36))))
623
+ # ]
624
+ #
625
+ # @return [Array] hash values
626
+ def hash_values
627
+ pair_nodes = case sexp_type
628
+ when :bare_assoc_hash
629
+ self[1]
630
+ when :hash
631
+ self[1][1]
632
+ else
633
+ end
634
+ if pair_nodes
635
+ values = []
636
+ pair_nodes.size.times do |i|
637
+ values << pair_nodes[i][2]
638
+ end
639
+ values
640
+ end
641
+ end
642
+
643
+ # Get the array size.
644
+ #
645
+ # s(:array,
646
+ # s(:args_add,
647
+ # s(:args_add, s(:args_new), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "first_name", s(1, 2))))),
648
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "last_name", s(1, 16))))
649
+ # )
650
+ # )
651
+ # => 2
652
+ #
653
+ # @return [Integer] array size
654
+ def array_size
655
+ if :array == sexp_type
656
+ first_node = self[1]
657
+ array_size = 0
658
+ if first_node
659
+ while true
660
+ array_size += 1
661
+ first_node = s(:args_new) == first_node[1] ? first_node[2] : first_node[1]
662
+ if :args_add != first_node.sexp_type
663
+ if :array == first_node.sexp_type
664
+ array_size += first_node.array_size
665
+ end
666
+ break
667
+ end
668
+ end
669
+ end
670
+ array_size
671
+ end
672
+ end
673
+
674
+ # Get the array values.
675
+ #
676
+ # s(:array,
677
+ # s(:args_add,
678
+ # s(:args_add, s(:args_new), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "first_name", s(1, 2))))),
679
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "last_name", s(1, 16))))
680
+ # )
681
+ # )
682
+ # => [
683
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "first_name", s(1, 2)))),
684
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "last_name", s(1, 16))))
685
+ # ]
686
+ #
687
+ # @return [Array] array values
688
+ def array_values
689
+ if :array == sexp_type
690
+ if nil == self[1]
691
+ []
692
+ elsif :qwords_add == self[1].sexp_type
693
+ self[1].array_values
694
+ else
695
+ arguments.all
696
+ end
697
+ elsif :qwords_add
698
+ values = []
699
+ node = self
700
+ while true
701
+ if :qwords_add == node.sexp_type
702
+ values.unshift node[2]
703
+ node = node[1]
704
+ elsif :qwords_new == node.sexp_type
705
+ break
706
+ end
707
+ end
708
+ values
709
+ end
710
+ end
711
+
712
+ # old method for alias node.
713
+ #
714
+ # s(:alias,
715
+ # s(:symbol_literal, s(:@ident, "new", s(1, 6))),
716
+ # s(:symbol_literal, s(:@ident, "old", s(1, 10)))
717
+ # )
718
+ # => s(:symbol_literal, s(:@ident, "old", s(1, 10))),
719
+ def old_method
720
+ self[2]
721
+ end
722
+
723
+ # new method for alias node.
724
+ #
725
+ # s(:alias,
726
+ # s(:symbol_literal, s(:@ident, "new", s(1, 6))),
727
+ # s(:symbol_literal, s(:@ident, "old", s(1, 10)))
728
+ # )
729
+ # => s(:symbol_literal, s(:@ident, "new", s(1, 6))),
730
+ def new_method
731
+ self[1]
732
+ end
733
+
734
+ # To object.
735
+ #
736
+ # s(:array,
737
+ # s(:args_add,
738
+ # s(:args_add, s(:args_new), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "first_name", s(1, 2))))),
739
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "last_name", s(1, 16))))
740
+ # )
741
+ # )
742
+ # => ["first_name", "last_name"]
743
+ #
744
+ # @return [Object]
745
+ def to_object
746
+ case sexp_type
747
+ when :array
748
+ array_values.map(&:to_s)
749
+ else
750
+ to_s
751
+ end
752
+ end
753
+
754
+ # to_s.
755
+ #
756
+ # @return [String] to_s
757
+ def to_s
758
+ case sexp_type
759
+ when :string_literal, :xstring_literal, :string_content, :const_ref, :symbol_literal, :symbol,
760
+ :args_add_block, :var_ref, :vcall, :var_field,
761
+ :@ident, :@tstring_content, :@const, :@ivar, :@kw, :@gvar, :@cvar
762
+ self[1].to_s
763
+ when :string_add
764
+ if s(:string_content) == self[1]
765
+ self[2].to_s
766
+ else
767
+ self[1].to_s
768
+ end
769
+ when :args_add
770
+ if s(:args_new) == self[1]
771
+ self[2].to_s
772
+ else
773
+ self[1].to_s
774
+ end
775
+ when :qwords_add
776
+ if s(:qwords_new) == self[1]
777
+ self[2].to_s
778
+ else
779
+ self[1].to_s
780
+ end
781
+ when :const_path_ref
782
+ "#{self[1]}::#{self[2]}"
783
+ when :@label
784
+ self[1].to_s[0..-2]
785
+ when :aref
786
+ "#{self[1]}[#{self[2]}]"
787
+ when :call, :field
788
+ "#{self.receiver}.#{self.message}"
789
+ else
790
+ ""
791
+ end
792
+ end
793
+
794
+ # check if the self node is a const.
795
+ def const?
796
+ :@const == self.sexp_type || ([:var_ref, :vcall].include?(self.sexp_type) && :@const == self[1].sexp_type)
797
+ end
798
+
799
+ # true
800
+ def present?
801
+ true
802
+ end
803
+
804
+ # false
805
+ def blank?
806
+ false
807
+ end
808
+
809
+ # remove the line and column info from sexp.
810
+ def remove_line_and_column
811
+ node = self.clone
812
+ last_node = node.last
813
+ if Sexp === last_node && last_node.size == 2 && last_node.first.is_a?(Integer) && last_node.last.is_a?(Integer)
814
+ node.delete_at(-1)
815
+ end
816
+ node.sexp_body.each_with_index do |child, index|
817
+ if Sexp === child
818
+ node[index+1] = child.remove_line_and_column
819
+ end
820
+ end
821
+ node
822
+ end
823
+
824
+ # if the return value of these methods is nil, then return CodeAnalyzer::Nil.new instead
825
+ [:sexp_type, :receiver, :message, :arguments, :argument, :class_name, :base_class, :method_name,
826
+ :body, :block, :conditional_statement, :left_value, :right_value].each do |method|
827
+ class_eval <<-EOS
828
+ alias_method :origin_#{method}, :#{method}
829
+
830
+ def #{method}
831
+ ret = origin_#{method}
832
+ ret.nil? ? CodeAnalyzer::Nil.new : ret
833
+ end
834
+ EOS
835
+ end
836
+ end