code_analyzer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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