ruby-lint 0.0.1a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.gitignore +5 -0
  2. data/.rbenv-version +1 -0
  3. data/.yardopts +10 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +19 -0
  6. data/MANIFEST +79 -0
  7. data/README.md +48 -0
  8. data/Rakefile +14 -0
  9. data/bin/rlint +6 -0
  10. data/doc/.gitkeep +0 -0
  11. data/doc/build/.gitkeep +0 -0
  12. data/doc/css/.gitkeep +0 -0
  13. data/doc/css/common.css +68 -0
  14. data/lib/rlint/analyze/coding_style.rb +407 -0
  15. data/lib/rlint/analyze/definitions.rb +244 -0
  16. data/lib/rlint/analyze/method_validation.rb +104 -0
  17. data/lib/rlint/analyze/shadowing_variables.rb +37 -0
  18. data/lib/rlint/analyze/undefined_variables.rb +99 -0
  19. data/lib/rlint/analyze/unused_variables.rb +103 -0
  20. data/lib/rlint/callback.rb +67 -0
  21. data/lib/rlint/cli.rb +167 -0
  22. data/lib/rlint/constant_importer.rb +102 -0
  23. data/lib/rlint/definition.rb +230 -0
  24. data/lib/rlint/formatter/text.rb +54 -0
  25. data/lib/rlint/helper/definition_resolver.rb +143 -0
  26. data/lib/rlint/helper/scoping.rb +138 -0
  27. data/lib/rlint/iterator.rb +193 -0
  28. data/lib/rlint/options.rb +58 -0
  29. data/lib/rlint/parser.rb +1252 -0
  30. data/lib/rlint/parser_error.rb +42 -0
  31. data/lib/rlint/report.rb +98 -0
  32. data/lib/rlint/token/assignment_token.rb +46 -0
  33. data/lib/rlint/token/begin_rescue_token.rb +57 -0
  34. data/lib/rlint/token/block_token.rb +17 -0
  35. data/lib/rlint/token/case_token.rb +44 -0
  36. data/lib/rlint/token/class_token.rb +24 -0
  37. data/lib/rlint/token/method_definition_token.rb +64 -0
  38. data/lib/rlint/token/method_token.rb +58 -0
  39. data/lib/rlint/token/parameters_token.rb +99 -0
  40. data/lib/rlint/token/regexp_token.rb +15 -0
  41. data/lib/rlint/token/statement_token.rb +69 -0
  42. data/lib/rlint/token/token.rb +162 -0
  43. data/lib/rlint/token/variable_token.rb +18 -0
  44. data/lib/rlint/version.rb +3 -0
  45. data/lib/rlint.rb +36 -0
  46. data/ruby-lint.gemspec +23 -0
  47. data/spec/benchmarks/memory.rb +52 -0
  48. data/spec/benchmarks/parse_parser.rb +16 -0
  49. data/spec/helper.rb +4 -0
  50. data/spec/rlint/analyze/coding_style.rb +224 -0
  51. data/spec/rlint/analyze/definitions/classes.rb +114 -0
  52. data/spec/rlint/analyze/definitions/methods.rb +91 -0
  53. data/spec/rlint/analyze/definitions/modules.rb +207 -0
  54. data/spec/rlint/analyze/definitions/variables.rb +103 -0
  55. data/spec/rlint/analyze/method_validation.rb +177 -0
  56. data/spec/rlint/analyze/shadowing_variables.rb +30 -0
  57. data/spec/rlint/analyze/undefined_variables.rb +230 -0
  58. data/spec/rlint/analyze/unused_variables.rb +225 -0
  59. data/spec/rlint/callback.rb +28 -0
  60. data/spec/rlint/constant_importer.rb +27 -0
  61. data/spec/rlint/definition.rb +96 -0
  62. data/spec/rlint/formatter/text.rb +21 -0
  63. data/spec/rlint/iterator.rb +452 -0
  64. data/spec/rlint/parser/arrays.rb +147 -0
  65. data/spec/rlint/parser/classes.rb +152 -0
  66. data/spec/rlint/parser/errors.rb +19 -0
  67. data/spec/rlint/parser/hashes.rb +136 -0
  68. data/spec/rlint/parser/methods.rb +249 -0
  69. data/spec/rlint/parser/modules.rb +49 -0
  70. data/spec/rlint/parser/objects.rb +39 -0
  71. data/spec/rlint/parser/operators.rb +75 -0
  72. data/spec/rlint/parser/procs.rb +113 -0
  73. data/spec/rlint/parser/ranges.rb +49 -0
  74. data/spec/rlint/parser/regexp.rb +31 -0
  75. data/spec/rlint/parser/scalars.rb +93 -0
  76. data/spec/rlint/parser/statements.rb +550 -0
  77. data/spec/rlint/parser/variables.rb +181 -0
  78. data/spec/rlint/report.rb +30 -0
  79. data/task/test.rake +6 -0
  80. metadata +188 -0
@@ -0,0 +1,58 @@
1
+ module Rlint
2
+ ##
3
+ # {Rlint::Options} is a class that can be used to configure various parts of
4
+ # Rlint such as what formatter to use, the reporting levels, etc.
5
+ #
6
+ class Options
7
+ ##
8
+ # Array containing the analyzer classes that are always enabled. The order
9
+ # of these classes is preserved.
10
+ #
11
+ # @return [Array]
12
+ #
13
+ REQUIRED_ANALYZERS = [Analyze::Definitions]
14
+
15
+ ##
16
+ # The reporting formatter to use, set to {Rlint::Formatter::Text} by
17
+ # default.
18
+ #
19
+ # @return [Rlint::Formatter]
20
+ #
21
+ attr_accessor :formatter
22
+
23
+ ##
24
+ # The enabled reporting levels. See {Rlint::Report#levels} and
25
+ # {Rlint::Report#initialize} for more information.
26
+ #
27
+ # @return [Array]
28
+ #
29
+ attr_accessor :levels
30
+
31
+ ##
32
+ # Array of classes to use for analyzing code. By default all the classes
33
+ # defined under {Rlint::Analyze} are used.
34
+ #
35
+ # @return [Array]
36
+ #
37
+ attr_accessor :analyzers
38
+
39
+ ##
40
+ # Sets the default values for various options.
41
+ #
42
+ def initialize
43
+ @formatter = Formatter::Text
44
+ @levels = Report::DEFAULT_LEVELS
45
+ @analyzers = REQUIRED_ANALYZERS.dup
46
+
47
+ Analyze.constants.each do |c|
48
+ const = Analyze.const_get(c)
49
+
50
+ @analyzers << const unless @analyzers.include?(const)
51
+ end
52
+ end
53
+ end # Options
54
+
55
+ @options = Options.new
56
+
57
+ class << self; attr_reader :options; end
58
+ end # Rlint
@@ -0,0 +1,1252 @@
1
+ module Rlint
2
+ ##
3
+ # {Rlint::Parser} uses Ripper to parse Ruby source code and turn it into an
4
+ # AST. Instead of returning arrays (the Ripper default) this class uses the
5
+ # various token classes such as {Rlint::Token::Token} and
6
+ # {Rlint::Token::VariableToken}. It also takes care of a few more things such
7
+ # as saving the associated line of code and setting method visibility.
8
+ #
9
+ # Parsing Ruby code requires two steps:
10
+ #
11
+ # 1. Create a new instance of this class and pass a string containing source
12
+ # code to the constructor method.
13
+ # 2. Call the method {Rlint::Parser#parse} and do something with the returned
14
+ # AST.
15
+ #
16
+ # For example, to parse a simple "Hello World" example you'd use this parser
17
+ # as following:
18
+ #
19
+ # require 'pp'
20
+ #
21
+ # parser = Rlint::Parser.new('puts "Hello, world!"')
22
+ #
23
+ # pp parser.parse
24
+ #
25
+ # This outputs the following AST:
26
+ #
27
+ # [#<Rlint::Token::MethodToken:0x000000012f04d0
28
+ # @code="puts \"Hello, world!\"",
29
+ # @column=0,
30
+ # @event=:method,
31
+ # @line=1,
32
+ # @name="puts",
33
+ # @parameters=
34
+ # [#<Rlint::Token::Token:0x000000012e9fb8
35
+ # @code="puts \"Hello, world!\"",
36
+ # @column=6,
37
+ # @event=:string,
38
+ # @line=1,
39
+ # @name="Hello, world!",
40
+ # @type=:string,
41
+ # @value="Hello, world!">],
42
+ # @type=:method>]
43
+ #
44
+ class Parser < Ripper::SexpBuilderPP
45
+ ##
46
+ # Array containing all the event names of which the methods should simply
47
+ # returned the passed argument.
48
+ #
49
+ # @return [Array]
50
+ #
51
+ RETURN_FIRST_ARG_EVENTS = [
52
+ :program,
53
+ :var_field,
54
+ :args_add_block,
55
+ :assoclist_from_args,
56
+ :symbol_literal,
57
+ :begin,
58
+ :mrhs_new_from_args,
59
+ :blockarg,
60
+ :rest_param,
61
+ :arg_paren,
62
+ :block_var,
63
+ :const_ref,
64
+ :top_const_ref
65
+ ]
66
+
67
+ ##
68
+ # Array of events of which the callback method should simply return nil.
69
+ #
70
+ # @return [Array]
71
+ #
72
+ RETURN_NIL_EVENTS = [:void_stmt]
73
+
74
+ ##
75
+ # Array of event names that should return an instance of
76
+ # {Rlint::Token::MethodToken}.
77
+ #
78
+ # @return [Array]
79
+ #
80
+ RETURN_METHOD_EVENTS = [:fcall, :vcall]
81
+
82
+ ##
83
+ # Hash containing the names of various event callbacks that should return
84
+ # a token class containing details about a single line statement.
85
+ #
86
+ # @return [Hash]
87
+ #
88
+ MOD_STATEMENT_EVENTS = {
89
+ :while_mod => :while,
90
+ :if_mod => :if,
91
+ :unless_mod => :unless,
92
+ :until_mod => :until
93
+ }
94
+
95
+ ##
96
+ # Array containing the three method calls that set the visibility of a
97
+ # method.
98
+ #
99
+ # @return [Array]
100
+ #
101
+ METHOD_VISIBILITY = ['public', 'protected', 'private']
102
+
103
+ ##
104
+ # Symbol containing the default method visibility.
105
+ #
106
+ # @return [Symbol]
107
+ #
108
+ DEFAULT_VISIBILITY = :public
109
+
110
+ # Return an Rlint::Token::Token instance for each scanner event instead of
111
+ # an array with multiple indexes.
112
+ SCANNER_EVENTS.each do |event|
113
+ define_method("on_#{event}") do |token|
114
+ return Token::Token.new(
115
+ :name => token,
116
+ :type => event,
117
+ :value => token,
118
+ :line => lineno,
119
+ :column => column,
120
+ :code => code(lineno)
121
+ )
122
+ end
123
+ end
124
+
125
+ RETURN_FIRST_ARG_EVENTS.each do |event|
126
+ define_method("on_#{event}") { |*args| return args[0] }
127
+ end
128
+
129
+ RETURN_NIL_EVENTS.each do |event|
130
+ define_method("on_#{event}") { |*args| return nil }
131
+ end
132
+
133
+ RETURN_METHOD_EVENTS.each do |event|
134
+ define_method("on_#{event}") do |token|
135
+ if METHOD_VISIBILITY.include?(token.name)
136
+ @visibility = token.name.to_sym
137
+
138
+ return nil
139
+ end
140
+
141
+ return Token::MethodToken.new(
142
+ :name => token.name,
143
+ :line => token.line,
144
+ :column => token.column,
145
+ :value => token.name,
146
+ :code => code(token.line)
147
+ )
148
+ end
149
+ end
150
+
151
+ MOD_STATEMENT_EVENTS.each do |ripper_event, rlint_event|
152
+ define_method("on_#{ripper_event}") do |statement, value|
153
+ value = [value] unless value.is_a?(Array)
154
+
155
+ return Token::StatementToken.new(
156
+ :type => rlint_event,
157
+ :statement => statement,
158
+ :value => value,
159
+ :line => lineno,
160
+ :column => column,
161
+ :code => code(lineno)
162
+ )
163
+ end
164
+ end
165
+
166
+ ##
167
+ # Creates a new instance of the parser and pre-defines various instance
168
+ # variables.
169
+ #
170
+ # @see Ripper::SexpBuilderPP#initialize
171
+ #
172
+ def initialize(code, file = '(rlint)', line = 1)
173
+ super
174
+
175
+ @file = file
176
+ @visibility = DEFAULT_VISIBILITY
177
+ @lines = []
178
+
179
+ code.each_line do |code_line|
180
+ @lines << code_line.chomp
181
+ end
182
+ end
183
+
184
+ ##
185
+ # Called when a parser error was encountered.
186
+ #
187
+ # @param [String] message The error message.
188
+ # @raise [Rlint::ParserError]
189
+ #
190
+ def on_parse_error(message)
191
+ raise ParserError.new(message, lineno, column, @file)
192
+ end
193
+
194
+ ##
195
+ # Called when a string literal was found.
196
+ #
197
+ # @param [Array] token Array containing details about the string.
198
+ # @return [Rlint::Token::Token]
199
+ #
200
+ def on_string_literal(token)
201
+ if token and token[1]
202
+ return token[1]
203
+ else
204
+ return Token::Token.new(
205
+ :name => '',
206
+ :value => '',
207
+ :line => lineno,
208
+ :column => column,
209
+ :type => :string
210
+ )
211
+ end
212
+ end
213
+
214
+ ##
215
+ # Called when a symbol is found.
216
+ #
217
+ # @param [Rlint::Token::Token] token The symbol token.
218
+ # @return [Rlint::Token::Token]
219
+ #
220
+ def on_symbol(token)
221
+ token.type = :symbol
222
+
223
+ return token
224
+ end
225
+
226
+ ##
227
+ # Called when a symbol using quotes was found.
228
+ #
229
+ # @see Rlint::Parser#on_symbol
230
+ #
231
+ def on_dyna_symbol(token)
232
+ return on_symbol(token[0])
233
+ end
234
+
235
+ ##
236
+ # Called when an array is found.
237
+ #
238
+ # @param [Array] values The values of the array.
239
+ # @return [Rlint::Token::Token]
240
+ #
241
+ def on_array(values)
242
+ values ||= []
243
+
244
+ return Token::Token.new(
245
+ :type => :array,
246
+ :value => values.map { |v| v.is_a?(Array) ? v[0] : v },
247
+ :line => lineno,
248
+ :column => column,
249
+ :code => code(lineno)
250
+ )
251
+ end
252
+
253
+ ##
254
+ # Called when a reference to a particular array index is found.
255
+ #
256
+ # @param [Rlint::Token::Token] array The array that was referenced.
257
+ # @param [Rlint::Token::Token] index The index that was referenced.
258
+ # @return [Rlint::Token::Token]
259
+ #
260
+ def on_aref(array, index)
261
+ array.key = index
262
+
263
+ return array
264
+ end
265
+
266
+ ##
267
+ # Called when a value is assigned to an array index.
268
+ #
269
+ # @param [Rlint::Token::Token] array The array that was referenced.
270
+ # @param [Rlint::Token::Token] index The index of the array that was
271
+ # referenced.
272
+ # @return [Rlint::Token::Token]
273
+ #
274
+ def on_aref_field(array, index)
275
+ array.key = index
276
+
277
+ return Token::AssignmentToken.new(
278
+ :receiver => array,
279
+ :line => lineno,
280
+ :column => column,
281
+ :type => array.type,
282
+ :code => code(lineno)
283
+ )
284
+ end
285
+
286
+ ##
287
+ # Called when a Hash is found.
288
+ #
289
+ # @param [Array] pairs An array of key/value pairs of the hash.
290
+ # @return [Rlint::Token::Token]
291
+ #
292
+ def on_hash(pairs)
293
+ # column() is set to the column number of the very end of the hash.
294
+ col = pairs[0].column rescue column
295
+
296
+ return Token::Token.new(
297
+ :type => :hash,
298
+ :value => pairs,
299
+ :line => lineno,
300
+ :column => col,
301
+ :code => code(lineno)
302
+ )
303
+ end
304
+
305
+ ##
306
+ # Called when a bare Hash is found. A bare Hash is a hash that's declared
307
+ # without the curly braces.
308
+ #
309
+ # @see Rlint::Parser#on_hash
310
+ #
311
+ def on_bare_assoc_hash(pairs)
312
+ return on_hash(pairs)
313
+ end
314
+
315
+ ##
316
+ # Called when a new key/value pair of a Hash is found.
317
+ #
318
+ # @param [Rlint::Token::Token] key The key of the pair.
319
+ # @param [Rlint::Token::Token] value The value of the pair.
320
+ # @return [Rlint::Token::Token]
321
+ #
322
+ def on_assoc_new(key, value)
323
+ key.name = key.value
324
+ key.value = value
325
+
326
+ return key
327
+ end
328
+
329
+ ##
330
+ # Called when a block is created using curly braces.
331
+ #
332
+ # @param [Rlint::Token::ParametersToken] params The parameters of the
333
+ # block.
334
+ # @param [Array] body Array containing the tokens of the block.
335
+ # @return [Rlint::Token::BlockToken]
336
+ #
337
+ def on_brace_block(params, body)
338
+ return Token::BlockToken.new(
339
+ :parameters => params,
340
+ :value => body,
341
+ :line => lineno,
342
+ :column => column,
343
+ :type => :block,
344
+ :code => code(lineno)
345
+ )
346
+ end
347
+
348
+ ##
349
+ # Called when a block is created using the do/end statements.
350
+ #
351
+ # @see Rlint::Parser#on_brace_block
352
+ #
353
+ def on_do_block(params, body)
354
+ return on_brace_block(params, body)
355
+ end
356
+
357
+ ##
358
+ # Called when a lambda is found.
359
+ #
360
+ # @see Rlint::Parser#on_brace_block
361
+ #
362
+ def on_lambda(params, body)
363
+ token = on_brace_block(params, body)
364
+ token.type = :lambda
365
+
366
+ return token
367
+ end
368
+
369
+ ##
370
+ # Called when a Range is found.
371
+ #
372
+ # @param [Rlint::Token::Token] start The start value of the range.
373
+ # @param [Rlint::Token::Token] stop The end value of the range.
374
+ # @return [Rlint::Token::Token]
375
+ #
376
+ def on_dot2(start, stop)
377
+ return Token::Token.new(
378
+ :type => :range,
379
+ :line => start.line,
380
+ :column => start.line,
381
+ :value => [start, stop],
382
+ :code => code(start.line)
383
+ )
384
+ end
385
+
386
+ ##
387
+ # Called when a regular expression is found.
388
+ #
389
+ # @param [Array] regexp The regular expression's value.
390
+ # @param [Rlint::Token::Token] modes The modes of the regular expression.
391
+ # @return [Rlint::Token::RegexpToken]
392
+ #
393
+ def on_regexp_literal(regexp, modes)
394
+ regexp = regexp[0]
395
+ modes_array = []
396
+
397
+ if modes
398
+ modes_array = modes.value.split('').select { |c| c =~ /\w/ }
399
+ end
400
+
401
+ return Token::RegexpToken.new(
402
+ :type => :regexp,
403
+ :value => regexp.value,
404
+ :line => regexp.line,
405
+ :column => regexp.column,
406
+ :modes => modes_array,
407
+ :code => code(lineno)
408
+ )
409
+ end
410
+
411
+ ##
412
+ # Called when a value is assigned to a variable.
413
+ #
414
+ # @param [Rlint::Token::Token] variable The variable that is assigned.
415
+ # @param [Rlint::Token::Token] value The value to assign.
416
+ # @return [Rlint::Token::VariableToken]
417
+ #
418
+ def on_assign(variable, value)
419
+ if variable.class == Rlint::Token::AssignmentToken
420
+ variable.value = value
421
+
422
+ return variable
423
+ end
424
+
425
+ if variable.is_a?(Array)
426
+ line = variable[-1].line
427
+ col = variable[-1].column
428
+ type = variable[-1].type
429
+ name = variable
430
+ else
431
+ line = variable.line
432
+ col = variable.column
433
+ type = variable.type
434
+ name = variable.name
435
+ end
436
+
437
+ return Token::AssignmentToken.new(
438
+ :line => line,
439
+ :column => col,
440
+ :name => name,
441
+ :type => type,
442
+ :value => value,
443
+ :code => code(line)
444
+ )
445
+ end
446
+
447
+ ##
448
+ # Called when a set of values is assigned to multiple variables.
449
+ #
450
+ # @param [Array] variables The variables that are being assigned values.
451
+ # @param [Array] values The values to assign.
452
+ # @return [Rlint::Token::AssignmentToken]
453
+ #
454
+ def on_massign(variables, values)
455
+ return Token::AssignmentToken.new(
456
+ :line => variables[0].line,
457
+ :column => variables[0].column,
458
+ :code => code(variables[0].line),
459
+ :type => :mass_assignment,
460
+ :event => :mass_assignment,
461
+ :name => variables,
462
+ :value => values
463
+ )
464
+ end
465
+
466
+ ##
467
+ # Called when a value is assigned to an object attribute.
468
+ #
469
+ # @param [Rlint::Token::VariableToken] receiver The receiver of the
470
+ # assignment.
471
+ # @param [Symbol] operator The operator that was used to separate the
472
+ # object and attribute.
473
+ # @param [Rlint::Token::Token] attribute The attribute to which the value
474
+ # is assigned.
475
+ # @return [Rlint::Token::VariableToken]
476
+ #
477
+ def on_field(receiver, operator, attribute)
478
+ return Token::AssignmentToken.new(
479
+ :name => attribute.value,
480
+ :line => attribute.line,
481
+ :column => attribute.column,
482
+ :type => attribute.type,
483
+ :receiver => receiver,
484
+ :operator => operator,
485
+ :code => code(attribute.line)
486
+ )
487
+ end
488
+
489
+ ##
490
+ # Called when a (binary) operator operation is performed.
491
+ #
492
+ # @param [Rlint::Token::Token] left The left hand side of the operator.
493
+ # @param [Symbol] op The operator that was used.
494
+ # @param [Rlint::Token::Token] right The right hand side of the operator.
495
+ # @return [Rlint::Token::Token]
496
+ #
497
+ def on_binary(left, op, right)
498
+ return Token::Token.new(
499
+ :type => :binary,
500
+ :value => [left, op, right],
501
+ :line => lineno,
502
+ :column => column,
503
+ :code => code(lineno)
504
+ )
505
+ end
506
+
507
+ ##
508
+ # Called when an unary operator/operation is found.
509
+ #
510
+ # @param [Symbol] operator The unary operator.
511
+ # @param [Rlint::Token::Token] token The token after the unary operator.
512
+ # @return [Rlint::Token::Token]
513
+ #
514
+ def on_unary(operator, token)
515
+ return Token::Token.new(
516
+ :type => :unary,
517
+ :value => [operator, token],
518
+ :line => lineno,
519
+ :column => column,
520
+ :code => code(lineno)
521
+ )
522
+ end
523
+
524
+ ##
525
+ # Called when a set of parenthesis is found.
526
+ #
527
+ # @param [Array] value The data inside the parenthesis.
528
+ # @return [Rlint::Token::Token]
529
+ #
530
+ def on_paren(value)
531
+ if value.is_a?(Array)
532
+ return value[0]
533
+ else
534
+ return value
535
+ end
536
+ end
537
+
538
+ ##
539
+ # Called when a return statement is found.
540
+ #
541
+ # @param [Array] values The return values of the statement.
542
+ # @return [Rlint::Token::StatementToken]
543
+ #
544
+ def on_return(values)
545
+ source = code(lineno)
546
+ col = calculate_column(source, 'return')
547
+
548
+ return Token::StatementToken.new(
549
+ :type => :return,
550
+ :line => lineno,
551
+ :column => col,
552
+ :value => values,
553
+ :code => source
554
+ )
555
+ end
556
+
557
+ ##
558
+ # Called when a while loop is found.
559
+ #
560
+ # @param [Rlint::Token::Token] statement The statement to evaluate.
561
+ # @param [Rlint::Token::Token] value The body of the while loop.
562
+ # @return [Rlint::Token::StatementToken]
563
+ #
564
+ def on_while(statement, value)
565
+ source = code(statement.line)
566
+ col = calculate_column(source, 'while')
567
+
568
+ return Token::StatementToken.new(
569
+ :type => :while,
570
+ :statement => statement,
571
+ :value => value,
572
+ :line => statement.line,
573
+ :column => col,
574
+ :code => source
575
+ )
576
+ end
577
+
578
+ ##
579
+ # Called when a for loop is found.
580
+ #
581
+ # @param [Array] variables Array of variables to create for each iteration.
582
+ #
583
+ # pry_binding
584
+ # @param [Rlint::Token::Token] enumerable The enumerable to iterate.
585
+ # @param [Array] value The body of the for loop.
586
+ # @return [Rlint::Token::StatementToken]
587
+ #
588
+ def on_for(variables, enumerable, value)
589
+ source = code(variables[0].line)
590
+ col = calculate_column(source, 'for')
591
+
592
+ return Token::StatementToken.new(
593
+ :type => :for,
594
+ :statement => [variables, enumerable],
595
+ :value => value,
596
+ :column => col,
597
+ :line => variables[0].line,
598
+ :code => source
599
+ )
600
+ end
601
+
602
+ ##
603
+ # Called when an if statement is found.
604
+ #
605
+ # @param [Rlint::Token::Token] statement The if statement to evaluate.
606
+ # @param [Array] value Array containing the tokens of the code that will
607
+ # be executed if the if statement evaluates to true.
608
+ # @param [Array] rest Array containing the tokens for the elsif and else
609
+ # statements (if any).
610
+ # @return [Rlint::Token::StatementToken]
611
+ #
612
+ def on_if(statement, value, rest)
613
+ source = code(statement.line)
614
+ col = calculate_column(source, 'if')
615
+
616
+ else_statement = nil
617
+ elsif_statements = []
618
+
619
+ if rest and rest.respond_to?(:each)
620
+ rest.each do |token|
621
+ next if token.nil?
622
+
623
+ if token.type == :elsif
624
+ elsif_statements << token
625
+ else
626
+ else_statement = token
627
+ end
628
+ end
629
+ end
630
+
631
+ return Token::StatementToken.new(
632
+ :type => :if,
633
+ :statement => statement,
634
+ :value => value,
635
+ :line => statement.line,
636
+ :column => col,
637
+ :else => else_statement,
638
+ :elsif => elsif_statements.reverse,
639
+ :code => source
640
+ )
641
+ end
642
+
643
+ ##
644
+ # Called whne a tenary operator is found.
645
+ #
646
+ # @see Rlint::Parser#on_if
647
+ #
648
+ def on_ifop(statement, value, else_statement)
649
+ else_statement = Token::StatementToken.new(
650
+ :type => :else,
651
+ :value => [else_statement],
652
+ :line => else_statement.line,
653
+ :column => else_statement.column,
654
+ :code => code(else_statement.line)
655
+ )
656
+
657
+ return Token::StatementToken.new(
658
+ :type => :if,
659
+ :statement => statement,
660
+ :value => [value],
661
+ :line => statement.line,
662
+ :column => statement.column,
663
+ :code => code(statement.line),
664
+ :else => else_statement
665
+ )
666
+ end
667
+
668
+ ##
669
+ # Called when an else statement is found.
670
+ #
671
+ # @param [Array] value The value of the statement.
672
+ # @return [Rlint::Token::StatementToken]
673
+ #
674
+ def on_else(value)
675
+ return Token::StatementToken.new(
676
+ :type => :else,
677
+ :value => value,
678
+ :column => column,
679
+ :line => lineno,
680
+ :code => code(lineno)
681
+ )
682
+ end
683
+
684
+ ##
685
+ # Called when an elsif statement is found.
686
+ #
687
+ # @param [Rlint::Token::Token] statement The statement to evaluate.
688
+ # @param [Array] value The value of the elsif statement.
689
+ # @param [Array|Rlint::Token::Token] list A list of else and elsif
690
+ # statements.
691
+ # @return [Array]
692
+ #
693
+ def on_elsif(statement, value, list)
694
+ source = code(statement.line)
695
+ col = calculate_column(source, 'elsif')
696
+
697
+ token = Token::StatementToken.new(
698
+ :type => :elsif,
699
+ :statement => statement,
700
+ :value => value,
701
+ :line => statement.line,
702
+ :column => col,
703
+ :code => source
704
+ )
705
+
706
+ unless list.is_a?(Array)
707
+ list = [list]
708
+ end
709
+
710
+ list << token
711
+ end
712
+
713
+ ##
714
+ # Called when the body of a block of Ruby code is found (e.g. a method
715
+ # definition or a begin/rescue block).
716
+ #
717
+ # @param [Array] value Array containing the tokens of the body/statement.
718
+ # @param [Array] rescues An array of rescue statements.
719
+ # @param [Rlint::Token::StatementToken] else_statement The else statement
720
+ # of the block.
721
+ # @param [Rlint::Token::StatementToken] ensure_statement The ensure
722
+ # statement of the block.
723
+ # @return [Rlint::Token::BeginRescueToken]
724
+ #
725
+ def on_bodystmt(value, rescues, else_statement, ensure_statement)
726
+ if rescues.nil? and else_statement.nil? and ensure_statement.nil?
727
+ return value
728
+ end
729
+
730
+ return Token::BeginRescueToken.new(
731
+ :type => :begin,
732
+ :value => value,
733
+ :rescue => (rescues || []).reverse.select { |t| !t.nil? },
734
+ :ensure => ensure_statement,
735
+ :else => else_statement,
736
+ :line => lineno,
737
+ :column => column,
738
+ :code => code(lineno)
739
+ )
740
+ end
741
+
742
+ ##
743
+ # Called when a rescue statement is found.
744
+ #
745
+ # @param [Array] exceptions An array of exceptions to catch.
746
+ # @param [Rlint::Token::Token] variable The variable in which to store
747
+ # the exception details.
748
+ # @param [Array] value The value of the rescue statement.
749
+ # @param [Array|Rlint::Token::Token] list A set of all the rescue tokens.
750
+ # @return [Rlint::Token::StatementToken]
751
+ #
752
+ def on_rescue(exceptions, variable, value, list)
753
+ source = code(lineno)
754
+ col = calculate_column(source, 'rescue')
755
+
756
+ token = Token::StatementToken.new(
757
+ :type => :rescue,
758
+ :statement => [exceptions, variable],
759
+ :line => lineno,
760
+ :column => col,
761
+ :value => value,
762
+ :code => source
763
+ )
764
+
765
+ unless list.is_a?(Array)
766
+ list = [list]
767
+ end
768
+
769
+ list << token
770
+ end
771
+
772
+ ##
773
+ # Called when a single line rescue statement (in the form of `[VALUE]
774
+ # rescue [RESCUE VALUE]`) is found.
775
+ #
776
+ # @param [Rlint::Token::Token|Array] value The body of the begin/rescue
777
+ # statement.
778
+ # @param [Rlint::Token::Token] statement The statement to evaluate when the
779
+ # data in `value` raised an exception.
780
+ # @return [Rlint::Token::BeginRescueToken]
781
+ #
782
+ def on_rescue_mod(value, statement)
783
+ value = [value] unless value.is_a?(Array)
784
+ statement = [statement] unless statement.is_a?(Array)
785
+
786
+ return Token::BeginRescueToken.new(
787
+ :type => :rescue,
788
+ :rescue => statement,
789
+ :value => value,
790
+ :line => lineno,
791
+ :column => column,
792
+ :code => code(lineno)
793
+ )
794
+ end
795
+
796
+ ##
797
+ # Called when an ensure statement is found.
798
+ #
799
+ # @param [Array] value The value of the statement.
800
+ # @return [Rlint::Token::StatementToken]
801
+ #
802
+ def on_ensure(value)
803
+ return Token::StatementToken.new(
804
+ :type => :ensure,
805
+ :value => value,
806
+ :line => lineno,
807
+ :column => column,
808
+ :code => code(lineno)
809
+ )
810
+ end
811
+
812
+ ##
813
+ # Called for an entire case/when/else block.
814
+ #
815
+ # @param [Rlint::Token::Token] statement The statement of the `case`
816
+ # statement itself.
817
+ # @param [Array] list Array containing the various when statements and
818
+ # optionally an else statement.
819
+ # @return [Rlint::Token::CaseToken]
820
+ #
821
+ def on_case(statement, list)
822
+ when_statements = []
823
+ else_statement = nil
824
+ source = code(statement.line)
825
+ col = calculate_column(source, 'case')
826
+
827
+ if list and list.respond_to?(:each)
828
+ list.each do |token|
829
+ if token.type == :when
830
+ when_statements << token
831
+ else
832
+ else_statement = token
833
+ end
834
+ end
835
+ end
836
+
837
+ return Token::CaseToken.new(
838
+ :type => :case,
839
+ :statement => statement,
840
+ :else => else_statement,
841
+ :when => when_statements.reverse,
842
+ :line => statement.line,
843
+ :column => col,
844
+ :code => source
845
+ )
846
+ end
847
+
848
+ ##
849
+ # Called when a `when` statement is found.
850
+ #
851
+ # @param [Array] statement Array of statements to evaluate.
852
+ # @param [Array] body Array containing the tokens of the statement's body.
853
+ # @param [Array] list The list of `when` tokens.
854
+ # @return [Array]
855
+ #
856
+ def on_when(statement, body, list)
857
+ source = code(statement[0].line)
858
+ col = calculate_column(source, 'when')
859
+
860
+ token = Token::StatementToken.new(
861
+ :type => :when,
862
+ :statement => statement,
863
+ :value => body,
864
+ :line => statement[0].line,
865
+ :column => col,
866
+ :code => source
867
+ )
868
+
869
+ unless list.is_a?(Array)
870
+ list = [list]
871
+ end
872
+
873
+ list << token
874
+ end
875
+
876
+ ##
877
+ # Called when a unless statement is found.
878
+ #
879
+ # @param [Rlint::Token::Token] statement The statement to evaluate.
880
+ # @param [Array] body The body of the unless statement.
881
+ # @param [Rlint::Token::StatementToken] else_token An optional else
882
+ # statement.
883
+ # @return [Rlint::Token::StatementToken]
884
+ #
885
+ def on_unless(statement, body, else_token)
886
+ source = code(statement.line)
887
+ col = calculate_column(source, 'unless')
888
+
889
+ return Token::StatementToken.new(
890
+ :type => :unless,
891
+ :statement => statement,
892
+ :else => else_token,
893
+ :value => body,
894
+ :line => statement.line,
895
+ :column => col,
896
+ :code => source
897
+ )
898
+ end
899
+
900
+ ##
901
+ # Called when an until statement is found.
902
+ #
903
+ # @see Rlint::Parser#on_unless
904
+ #
905
+ def on_until(statement, body)
906
+ source = code(statement.line)
907
+ col = calculate_column(source, 'until')
908
+
909
+ return Token::StatementToken.new(
910
+ :type => :until,
911
+ :statement => statement,
912
+ :value => body,
913
+ :line => statement.line,
914
+ :column => col,
915
+ :code => source
916
+ )
917
+ end
918
+
919
+ ##
920
+ # Called when a variable is referenced.
921
+ #
922
+ # @param [Rlint::Token::Token] variable The variable that was referenced.
923
+ # @return [Rlint::Token::VariableToken]
924
+ #
925
+ def on_var_ref(variable)
926
+ return Token::VariableToken.new(
927
+ :line => variable.line,
928
+ :column => variable.column,
929
+ :name => variable.value,
930
+ :type => variable.type,
931
+ :code => code(variable.line)
932
+ )
933
+ end
934
+
935
+ ##
936
+ # Called when a constant path reference is found.
937
+ #
938
+ # @param [Array] segments The path segments.
939
+ # @return [Array]
940
+ #
941
+ def on_const_path_ref(*segments)
942
+ return Token::VariableToken.new(
943
+ :name => segments.map { |t| t.name }.flatten,
944
+ :type => :constant_path,
945
+ :line => segments[0].line,
946
+ :column => segments[0].column,
947
+ :code => code(segments[-1].line)
948
+ )
949
+ end
950
+
951
+ ##
952
+ # Called when a constant path is assigned.
953
+ #
954
+ # @see Rlint::Parser#on_const_path_ref
955
+ #
956
+ def on_const_path_field(*segments)
957
+ return on_const_path_ref(*segments)
958
+ end
959
+
960
+ ##
961
+ # Called when a new method is defined.
962
+ #
963
+ # @param [Rlint::Token::Token] name Token containing details about the
964
+ # method name.
965
+ # @param [Rlint::Token::ParametersToken] params Token containing details
966
+ # about the method parameters.
967
+ # @param [Array] body Array containing the tokens of the method's body.
968
+ # @return [Rlint::Token::MethodDefinitionToken]
969
+ #
970
+ def on_def(name, params, body)
971
+ return Token::MethodDefinitionToken.new(
972
+ :name => name.value,
973
+ :line => name.line,
974
+ :column => name.column,
975
+ :parameters => params,
976
+ :visibility => @visibility,
977
+ :value => body.select { |t| !t.nil? },
978
+ :code => code(name.line)
979
+ )
980
+ end
981
+
982
+ ##
983
+ # Called when a method is defined on a specific constant/location
984
+ # (e.g. `self`).
985
+ #
986
+ # @param [Rlint::Token::Token] receiver The object that the method was
987
+ # defined on.
988
+ # @param [Rlint::Token::Token] operator The operator that was used to
989
+ # separate the receiver and method name.
990
+ # @param [Rlint::Token::Token] name The name of the method.
991
+ # @param [Rlint::Token::ParametersToken] params The method parameters.
992
+ # @param [Array] body The body of the method.
993
+ # @return [Rlint::Token::MethodDefinitionToken]
994
+ #
995
+ def on_defs(receiver, operator, name, params, body)
996
+ token = on_def(name, params, body)
997
+ token.receiver = receiver
998
+ token.operator = operator
999
+
1000
+ return token
1001
+ end
1002
+
1003
+ ##
1004
+ # Called when a set of method parameters is found. The order of the `args`
1005
+ # parameter is the following:
1006
+ #
1007
+ # * 0: array of required parameters
1008
+ # * 1: array of optional parameters
1009
+ # * 2: the rest parameter (if any)
1010
+ # * 3: array of "more" parameters (parameters set after the rest parameter)
1011
+ # * 4: the block parameter (if any)
1012
+ #
1013
+ # @param [Array] args Array containing all the passed method parameters.
1014
+ # @return [Rlint::Token::ParametersToken]
1015
+ #
1016
+ def on_params(*args)
1017
+ # Convert all the arguments from regular Token instances to VariableToken
1018
+ # instances.
1019
+ args.each_with_index do |arg, index|
1020
+ # Required, optional and more parameters.
1021
+ if arg.is_a?(Array)
1022
+ args[index] = arg.map do |token|
1023
+ value = nil
1024
+
1025
+ if token.is_a?(Array)
1026
+ value = token[1]
1027
+ token = token[0]
1028
+ end
1029
+
1030
+ Token::VariableToken.new(
1031
+ :name => token.value,
1032
+ :value => value,
1033
+ :line => token.line,
1034
+ :column => token.column,
1035
+ :type => token.type,
1036
+ :code => code(token.line)
1037
+ )
1038
+ end
1039
+ # Rest and block parameters.
1040
+ elsif !arg.nil?
1041
+ args[index] = Token::VariableToken.new(
1042
+ :name => arg.value,
1043
+ :line => arg.line,
1044
+ :column => arg.column,
1045
+ :type => arg.type,
1046
+ :code => code(arg.line)
1047
+ )
1048
+ end
1049
+ end
1050
+
1051
+ return Token::ParametersToken.new(
1052
+ :value => args[0],
1053
+ :optional => args[1],
1054
+ :rest => args[2],
1055
+ :more => args[3],
1056
+ :block => args[4]
1057
+ )
1058
+ end
1059
+
1060
+ ##
1061
+ # Called when a method call using parenthesis is found.
1062
+ #
1063
+ # @param [Rlint::Token::Token] name The name of the method that was called.
1064
+ # @param [Array] params The parameters of the method call.
1065
+ # @return [Rlint::Token::MethodToken]
1066
+ #
1067
+ def on_method_add_arg(name, params)
1068
+ if name.class == Rlint::Token::MethodToken
1069
+ name.parameters = params
1070
+
1071
+ return name
1072
+ end
1073
+
1074
+ return Token::MethodToken.new(
1075
+ :name => name.value,
1076
+ :parameters => params,
1077
+ :line => name.line,
1078
+ :column => name.column,
1079
+ :code => code(name.line)
1080
+ )
1081
+ end
1082
+
1083
+ ##
1084
+ # Called when a block is passed to a method call.
1085
+ #
1086
+ # @param [Rlint::Token::MethodToken] method Token class for the method that
1087
+ # the block is passed to.
1088
+ # @param [Rlint::Token::BlockToken] block The block that was passed to the
1089
+ # method.
1090
+ # @return [Rlint::Token::MethodToken]
1091
+ #
1092
+ def on_method_add_block(method, block)
1093
+ method.block = block
1094
+
1095
+ return method
1096
+ end
1097
+
1098
+ ##
1099
+ # Called when a class declaration is found.
1100
+ #
1101
+ # @param [Array|Rlint::Token::Token] name The name of the class.
1102
+ # @param [Array|NilClass] parent The name of the parent class.
1103
+ # @param [Array] body The body of the class.
1104
+ # @return [Rlint::Token::ClassToken]
1105
+ #
1106
+ def on_class(name, parent, body)
1107
+ name_segments = name.name.is_a?(Array) ? name.name : [name.name]
1108
+ parent_segments = []
1109
+
1110
+ if parent
1111
+ parent_segments = parent.name.is_a?(Array) ? parent.name : [parent.name]
1112
+ end
1113
+
1114
+ body = body.is_a?(Array) ? body.flatten.select { |t| !t.nil? } : []
1115
+
1116
+ @visibility = DEFAULT_VISIBILITY
1117
+
1118
+ return Token::ClassToken.new(
1119
+ :name => name_segments,
1120
+ :parent => parent_segments,
1121
+ :type => :class,
1122
+ :value => body,
1123
+ :line => name.line,
1124
+ :column => name.column,
1125
+ :code => name.code
1126
+ )
1127
+ end
1128
+
1129
+ ##
1130
+ # Called when a set of class methods are added using a `class << self`
1131
+ # block.
1132
+ #
1133
+ # @param [Rlint::Token::VariableToken] receiver The receiver for all the
1134
+ # methods.
1135
+ # @param [Array] value The body of the block.
1136
+ # @return [Array]
1137
+ #
1138
+ def on_sclass(receiver, value)
1139
+ value.each_with_index do |token, index|
1140
+ if token.respond_to?(:receiver)
1141
+ token.receiver = receiver
1142
+ end
1143
+
1144
+ value[index] = token
1145
+ end
1146
+
1147
+ return value
1148
+ end
1149
+
1150
+ ##
1151
+ # Called when a module definition is found.
1152
+ #
1153
+ # @param [Rlint::Token::Token|Array] name The name of the module, either a
1154
+ # single token class or an array of token classes.
1155
+ # @param [Array|NilClass] body The body of the module.
1156
+ # @return [Rlint::Token::Token]
1157
+ #
1158
+ def on_module(name, body)
1159
+ name_segments = name.name.is_a?(Array) ? name.name : [name.name]
1160
+
1161
+ return Token::Token.new(
1162
+ :type => :module,
1163
+ :name => name_segments,
1164
+ :value => body,
1165
+ :line => name.line,
1166
+ :column => name.column,
1167
+ :code => name.code
1168
+ )
1169
+ end
1170
+
1171
+ ##
1172
+ # Called when a method call without parenthesis was found.
1173
+ #
1174
+ # @see Rlint::Parser#on_method_add_arg
1175
+ #
1176
+ def on_command(name, params)
1177
+ return on_method_add_arg(name, params)
1178
+ end
1179
+
1180
+ ##
1181
+ # Called when a method was invoked on an object.
1182
+ #
1183
+ # @param [Rlint::Token::Token] receiver The object that the method was
1184
+ # invoked on.
1185
+ # @param [Symbol] operator The operator that was used to separate the
1186
+ # receiver and method name.
1187
+ # @param [Rlint::Token::Token] name Token containing details about the
1188
+ # method name.
1189
+ # @return [Rlint::Token::MethodToken]
1190
+ #
1191
+ def on_call(receiver, operator, name)
1192
+ return Token::MethodToken.new(
1193
+ :name => name.value,
1194
+ :line => name.line,
1195
+ :column => name.column,
1196
+ :receiver => receiver,
1197
+ :operator => operator,
1198
+ :code => code(name.line)
1199
+ )
1200
+ end
1201
+
1202
+ ##
1203
+ # Called when a method was invoked on an object without using parenthesis.
1204
+ #
1205
+ # @see Rlint::Parser#on_call
1206
+ # @param [Array] params The parameters passed to the method.
1207
+ # @return [Rlint::Token::MethodToken]
1208
+ #
1209
+ def on_command_call(receiver, operator, name, params)
1210
+ return Token::MethodToken.new(
1211
+ :name => name.value,
1212
+ :line => name.line,
1213
+ :column => name.column,
1214
+ :receiver => receiver,
1215
+ :operator => operator,
1216
+ :parameters => params,
1217
+ :code => code(name.line)
1218
+ )
1219
+ end
1220
+
1221
+ private
1222
+
1223
+ ##
1224
+ # Returns a string containing the code for the specified line number.
1225
+ #
1226
+ # @param [Fixnum|Bignum] line The line number.
1227
+ # @return [String]
1228
+ #
1229
+ def code(line)
1230
+ return @lines[line - 1]
1231
+ end
1232
+
1233
+ ##
1234
+ # Calculates the column position based on a given line and a stop string.
1235
+ #
1236
+ # @param [String] line The line of code to use for calculating the column
1237
+ # number.
1238
+ # @param [String] stop The string that indicates the start of the token.
1239
+ # @return [Fixnum]
1240
+ #
1241
+ def calculate_column(line, stop)
1242
+ matches = line.match(/^(.+)#{stop}/)
1243
+ number = 0
1244
+
1245
+ if matches and matches[1]
1246
+ number = matches[1].to_i
1247
+ end
1248
+
1249
+ return number
1250
+ end
1251
+ end # Parser
1252
+ end # Rlint