ruby-lint 0.0.1a

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