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,28 @@
1
+ require File.expand_path('../../helper', __FILE__)
2
+
3
+ describe 'Rlint::Callback' do
4
+ it 'Add an error to a report using Rlint::Callback' do
5
+ report = Rlint::Report.new
6
+ callback = Rlint::Callback.new(report)
7
+
8
+ report.levels.each do |level|
9
+ report.messages[level].nil?.should == true
10
+
11
+ callback.send(level, "test #{level}", 1, 1)
12
+
13
+ report.messages[level].class.should == Array
14
+ report.messages[level].length.should == 1
15
+
16
+ report.messages[level][0][:message].should == "test #{level}"
17
+ end
18
+ end
19
+
20
+ it 'Ignore errors (when disabled) when adding one using Rlint::Callback' do
21
+ report = Rlint::Report.new('(rlint)', [:warning])
22
+ callback = Rlint::Callback.new(report)
23
+
24
+ callback.send(:error, 'test error', 1, 1)
25
+
26
+ report.messages[:error].nil?.should == true
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path('../../helper', __FILE__)
2
+
3
+ describe 'Rlint::ConstantImporter' do
4
+ it 'Import methods from the Kernel constant' do
5
+ imported = Rlint::ConstantImporter.import([:Kernel])
6
+
7
+ imported.key?('Kernel').should == true
8
+
9
+ found = imported['Kernel'].lookup(:method, 'puts')
10
+
11
+ found.class.should == Rlint::Definition
12
+
13
+ found.token.class.should == Rlint::Token::MethodDefinitionToken
14
+ found.token.name.should == 'puts'
15
+ found.token.visibility.should == :public
16
+
17
+ found.token.parameters.class.should == Rlint::Token::ParametersToken
18
+
19
+ found.token.parameters.value.class.should == Array
20
+ found.token.parameters.value.length.should == 0
21
+
22
+ found.token.parameters.rest.class.should == Rlint::Token::VariableToken
23
+ found.token.parameters.rest.name.should == ''
24
+ found.token.parameters.rest.type.should == :local_variable
25
+ found.token.parameters.rest.event.should == :local_variable
26
+ end
27
+ end
@@ -0,0 +1,96 @@
1
+ require File.expand_path('../../helper', __FILE__)
2
+
3
+ describe 'Rlint::Definition' do
4
+ it 'Look up a symbol in a scope' do
5
+ scope = Rlint::Definition.new
6
+
7
+ scope.lookup(:local_variable, 'number').should == nil
8
+
9
+ scope.add(:local_variable, 'number', 10)
10
+
11
+ scope.lookup(:local_variable, 'number').should == 10
12
+ end
13
+
14
+ it 'Look up a symbol in a parent scope' do
15
+ parent = Rlint::Definition.new
16
+ child = Rlint::Definition.new(parent)
17
+
18
+ parent.add(:local_variable, 'number', 10)
19
+ parent.add(:global_variable, '$number', 20)
20
+
21
+ parent.lookup(:local_variable, 'number').should == 10
22
+ parent.lookup(:global_variable, '$number').should == 20
23
+
24
+ child.lookup(:local_variable, 'number').should == nil
25
+ child.lookup(:global_variable, '$number').should == 20
26
+ end
27
+
28
+ it 'Create a scope with default Ruby constants and methods' do
29
+ scope = Rlint::Definition.new(nil, :lazy => true, :kernel => true)
30
+ found = scope.lookup(:constant, 'Kernel')
31
+
32
+ found.class.should == Rlint::Definition
33
+
34
+ method = found.lookup(:method, 'warn')
35
+
36
+ method.class.should == Rlint::Definition
37
+
38
+ method.token.class.should == Rlint::Token::MethodDefinitionToken
39
+ method.token.name.should == 'warn'
40
+
41
+ method.token.parameters.value.class.should == Array
42
+ method.token.parameters.value.length.should == 1
43
+
44
+ param = method.token.parameters.value[0]
45
+
46
+ param.class.should == Rlint::Token::VariableToken
47
+ param.name.should == ''
48
+ param.type.should == :local_variable
49
+ end
50
+
51
+ it 'Lazy import the Time class' do
52
+ scope = Rlint::Definition.new
53
+
54
+ scope.lookup(:constant, 'Time').nil?.should == true
55
+
56
+ scope = Rlint::Definition.new(nil, :lazy => true)
57
+
58
+ scope.lookup(:constant, 'Time').class.should == Rlint::Definition
59
+ end
60
+
61
+ it 'Lazy import the Encoding class and a child constant' do
62
+ scope = Rlint::Definition.new(nil, :lazy => true)
63
+ enc = scope.lookup(:constant, 'Encoding')
64
+
65
+ enc.class.should == Rlint::Definition
66
+
67
+ enc.lookup(:constant, 'BINARY').class.should == Rlint::Definition
68
+ end
69
+
70
+ it 'Lazy import Rlint::Definition' do
71
+ scope = Rlint::Definition.new(nil, :lazy => true)
72
+
73
+ rlint = scope.lookup(:constant, 'Rlint')
74
+
75
+ rlint.class.should == Rlint::Definition
76
+
77
+ rlint_scope = rlint.lookup(:constant, 'Definition')
78
+
79
+ rlint_scope.class.should == Rlint::Definition
80
+
81
+ rlint_scope.lookup(:constant, 'LOOKUP_PARENT').class.should == Rlint::Definition
82
+ end
83
+
84
+ it 'Create a scope with multiple parent scopes' do
85
+ scope_1 = Rlint::Definition.new
86
+ scope_2 = Rlint::Definition.new
87
+
88
+ scope_1.add(:method, 'method_1', true)
89
+ scope_2.add(:method, 'method_2', true)
90
+
91
+ scope_3 = Rlint::Definition.new([scope_1, scope_2])
92
+
93
+ scope_3.lookup(:method, 'method_1').should == true
94
+ scope_3.lookup(:method, 'method_2').should == true
95
+ end
96
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path('../../../helper', __FILE__)
2
+
3
+ describe 'Rlint::Formatter::Text' do
4
+ it 'Format a report in plain text' do
5
+ report_a = Rlint::Report.new('a.rb')
6
+ report_b = Rlint::Report.new('b.rb')
7
+ formatter = Rlint::Formatter::Text.new
8
+
9
+ report_a.add(:info, 'test info', 2, 1)
10
+
11
+ report_b.add(:error, 'test error', 1, 1)
12
+ report_b.add(:info, 'test info b.rb', 3, 1)
13
+
14
+ output_a = 'a.rb: info: line 2, column 1: test info'
15
+ output_b = "b.rb: error: line 1, column 1: test error\n" \
16
+ 'b.rb: info: line 3, column 1: test info b.rb'
17
+
18
+ formatter.format(report_a).should == output_a
19
+ formatter.format(report_b).should == output_b
20
+ end
21
+ end
@@ -0,0 +1,452 @@
1
+ require File.expand_path('../../helper', __FILE__)
2
+
3
+ describe 'Rlint::Iterator' do
4
+ it 'Iterate over a simple AST' do
5
+ code = <<-CODE
6
+ number = 10
7
+ number
8
+ CODE
9
+
10
+ tokens = Rlint::Parser.new(code).parse
11
+ iterator = Rlint::Iterator.new
12
+
13
+ callback = Class.new(Rlint::Callback) do
14
+ attr_reader :assigned
15
+ attr_reader :referenced
16
+
17
+ def on_assignment(token)
18
+ @assigned = true
19
+ end
20
+
21
+ def on_local_variable(token)
22
+ @referenced = true
23
+ end
24
+ end
25
+
26
+ iterator.bind(callback)
27
+
28
+ iterator.run(tokens)
29
+
30
+ iterator.callbacks[0].assigned.should == true
31
+ iterator.callbacks[0].referenced.should == true
32
+ end
33
+
34
+ it 'Iterate over a multi dimensional AST' do
35
+ code = <<-CODE
36
+ class Foo
37
+ def initialize
38
+ @number = 10
39
+ end
40
+ end
41
+ CODE
42
+
43
+ tokens = Rlint::Parser.new(code).parse
44
+ iterator = Rlint::Iterator.new
45
+ callback = Class.new(Rlint::Callback) do
46
+ attr_reader :class_name
47
+ attr_reader :method_name
48
+ attr_reader :assigned
49
+
50
+ def on_class(token)
51
+ @class_name = token.name[0]
52
+ end
53
+
54
+ def on_method_definition(token)
55
+ @method_name = token.name
56
+ end
57
+
58
+ def on_assignment(token)
59
+ @assigned = true
60
+ end
61
+ end
62
+
63
+ iterator.bind(callback)
64
+
65
+ iterator.run(tokens)
66
+
67
+ iterator.callbacks[0].class_name.should == 'Foo'
68
+ iterator.callbacks[0].method_name.should == 'initialize'
69
+ iterator.callbacks[0].assigned.should == true
70
+ end
71
+
72
+ it 'Call an event after iterating over a node' do
73
+ code = <<-CODE
74
+ def some_method
75
+ return 10
76
+ end
77
+ CODE
78
+
79
+ tokens = Rlint::Parser.new(code).parse
80
+ iterator = Rlint::Iterator.new
81
+ callback = Class.new(Rlint::Callback) do
82
+ attr_reader :before
83
+ attr_reader :after
84
+
85
+ def on_method_definition(token)
86
+ @before = token.name
87
+ end
88
+
89
+ def after_method_definition(token)
90
+ @after = token.name
91
+ end
92
+ end
93
+
94
+ iterator.bind(callback)
95
+ iterator.run(tokens)
96
+
97
+ iterator.callbacks[0].before.should == 'some_method'
98
+ iterator.callbacks[0].after.should == 'some_method'
99
+ end
100
+
101
+ it 'Iterate over a begin/rescue statement' do
102
+ code = <<-CODE
103
+ begin
104
+ raise
105
+ rescue RuntimeError => e
106
+ puts 'runtime error!'
107
+ end
108
+ CODE
109
+
110
+ tokens = Rlint::Parser.new(code).parse
111
+ iterator = Rlint::Iterator.new
112
+ callback = Class.new(Rlint::Callback) do
113
+ attr_reader :method_names
114
+ attr_reader :exception
115
+ attr_reader :exception_var
116
+
117
+ def initialize(*args)
118
+ super
119
+
120
+ @method_names = []
121
+ end
122
+
123
+ def on_method(token)
124
+ @method_names << token.name
125
+ end
126
+
127
+ def on_constant(token)
128
+ @exception = token.name
129
+ end
130
+
131
+ def on_identifier(token)
132
+ @exception_var = token.name
133
+ end
134
+ end
135
+
136
+ iterator.bind(callback)
137
+ iterator.run(tokens)
138
+
139
+ obj = iterator.callbacks[0]
140
+
141
+ obj.method_names.should == ['raise', 'puts']
142
+ obj.exception.should == 'RuntimeError'
143
+ obj.exception_var.should == 'e'
144
+ end
145
+
146
+ it 'Iterate over an if statement' do
147
+ code = <<-CODE
148
+ a, b, c, d, e, f = [1, 2, 3, 4, 5, 6]
149
+
150
+ if a == b
151
+ first
152
+ elsif c == d
153
+ second
154
+ elsif e == f
155
+ third
156
+ else
157
+ fourth
158
+ end
159
+ CODE
160
+
161
+ tokens = Rlint::Parser.new(code).parse
162
+ iterator = Rlint::Iterator.new
163
+ callback = Class.new(Rlint::Callback) do
164
+ attr_reader :variables
165
+ attr_reader :methods
166
+
167
+ def initialize(*args)
168
+ super
169
+
170
+ @variables = []
171
+ @methods = []
172
+ end
173
+
174
+ def on_local_variable(token)
175
+ @variables << token.name
176
+ end
177
+
178
+ def on_method(token)
179
+ @methods << token.name
180
+ end
181
+ end
182
+
183
+ iterator.bind(callback)
184
+ iterator.run(tokens)
185
+
186
+ obj = iterator.callbacks[0]
187
+
188
+ obj.variables.should == %w{a b c d e f}
189
+ obj.methods.should == %w{first second third fourth}
190
+ end
191
+
192
+ it 'Iterate over a case statement' do
193
+ code = <<-CODE
194
+ case number
195
+ when 10
196
+ first
197
+ when 20
198
+ second
199
+ when 30
200
+ third
201
+ else
202
+ fourth
203
+ end
204
+ CODE
205
+
206
+ tokens = Rlint::Parser.new(code).parse
207
+ iterator = Rlint::Iterator.new
208
+ callback = Class.new(Rlint::Callback) do
209
+ attr_reader :methods
210
+ attr_reader :numbers
211
+
212
+ def initialize(*args)
213
+ super
214
+
215
+ @methods = []
216
+ @numbers = []
217
+ end
218
+
219
+ def on_method(token)
220
+ @methods << token.name
221
+ end
222
+
223
+ def on_integer(token)
224
+ @numbers << token.value
225
+ end
226
+ end
227
+
228
+ iterator.bind(callback)
229
+ iterator.run(tokens)
230
+
231
+ obj = iterator.callbacks[0]
232
+
233
+ obj.numbers.should == %w{10 20 30}
234
+ obj.methods.should == %w{number first second third fourth}
235
+ end
236
+
237
+ it 'Iterate over a for loop' do
238
+ code = <<-CODE
239
+ for key, value in {:name => 'Ruby'}
240
+ puts key
241
+ end
242
+ CODE
243
+
244
+ tokens = Rlint::Parser.new(code).parse
245
+ iterator = Rlint::Iterator.new
246
+ callback = Class.new(Rlint::Callback) do
247
+ attr_reader :symbol
248
+ attr_reader :string
249
+ attr_reader :names
250
+
251
+ def initialize(*args)
252
+ super
253
+
254
+ @names = []
255
+ end
256
+
257
+ def on_local_variable(token)
258
+ @names << token.name
259
+ end
260
+
261
+ def on_identifier(token)
262
+ @names << token.name
263
+ end
264
+
265
+ def on_symbol(token)
266
+ @symbol = token.name
267
+ end
268
+
269
+ def on_string(token)
270
+ @string = token.value
271
+ end
272
+ end
273
+
274
+ iterator.bind(callback)
275
+ iterator.run(tokens)
276
+
277
+ obj = iterator.callbacks[0]
278
+
279
+ obj.symbol.should == 'name'
280
+ obj.string.should == 'Ruby'
281
+ obj.names.should == %w{key value key}
282
+ end
283
+
284
+ it 'Iterate over a while statement' do
285
+ code = <<-CODE
286
+ while foo == 10
287
+ puts 20
288
+ end
289
+ CODE
290
+
291
+ tokens = Rlint::Parser.new(code).parse
292
+ iterator = Rlint::Iterator.new
293
+ callback = Class.new(Rlint::Callback) do
294
+ attr_reader :numbers
295
+ attr_reader :methods
296
+
297
+ def initialize(*args)
298
+ super
299
+
300
+ @numbers = []
301
+ @methods = []
302
+ end
303
+
304
+ def on_identifier(token)
305
+ @methods << token.name
306
+ end
307
+
308
+ def on_method(token)
309
+ @methods << token.name
310
+ end
311
+
312
+ def on_integer(token)
313
+ @numbers << token.value
314
+ end
315
+ end
316
+
317
+ iterator.bind(callback)
318
+ iterator.run(tokens)
319
+
320
+ obj = iterator.callbacks[0]
321
+
322
+ obj.methods.should == %w{foo puts}
323
+ obj.numbers.should == %w{10 20}
324
+ end
325
+
326
+ it 'Iterate over a method definition' do
327
+ code = <<-CODE
328
+ def foobar(required, optional = 10, *rest, more, &block)
329
+ return 20
330
+ end
331
+ CODE
332
+
333
+ tokens = Rlint::Parser.new(code).parse
334
+ iterator = Rlint::Iterator.new
335
+ callback = Class.new(Rlint::Callback) do
336
+ attr_reader :variables
337
+ attr_reader :numbers
338
+
339
+ def initialize(*args)
340
+ super
341
+
342
+ @variables = []
343
+ @numbers = []
344
+ end
345
+
346
+ def on_local_variable(token)
347
+ @variables << token.name
348
+ end
349
+
350
+ def on_integer(token)
351
+ @numbers << token.value
352
+ end
353
+ end
354
+
355
+ iterator.bind(callback)
356
+ iterator.run(tokens)
357
+
358
+ obj = iterator.callbacks[0]
359
+
360
+ obj.numbers.should == %w{10 20}
361
+ obj.variables.should == %w{required optional rest more block}
362
+ end
363
+
364
+ it 'Iterate over a method call' do
365
+ tokens = Rlint::Parser.new('puts "Foo", "Bar"').parse
366
+ iterator = Rlint::Iterator.new
367
+ callback = Class.new(Rlint::Callback) do
368
+ attr_reader :params
369
+
370
+ def initialize(*args)
371
+ @params = []
372
+ end
373
+
374
+ def on_string(token)
375
+ @params << token.value
376
+ end
377
+ end
378
+
379
+ iterator.bind(callback)
380
+ iterator.run(tokens)
381
+
382
+ obj = iterator.callbacks[0]
383
+
384
+ obj.params.should == %w{Foo Bar}
385
+ end
386
+
387
+ it 'Call an event before and after iterating over all nodes' do
388
+ code = <<-CODE
389
+ class Person
390
+ def initialize
391
+ @name = 'Matz'
392
+ end
393
+ end
394
+ CODE
395
+
396
+ tokens = Rlint::Parser.new(code).parse
397
+ iterator = Rlint::Iterator.new
398
+ callback = Class.new(Rlint::Callback) do
399
+ attr_reader :start
400
+ attr_reader :finish
401
+
402
+ def initialize(*args)
403
+ super
404
+
405
+ @start = 0
406
+ @finish = 0
407
+ end
408
+
409
+ def on_start
410
+ @start += 1
411
+ end
412
+
413
+ def on_finish
414
+ @finish += 1
415
+ end
416
+ end
417
+
418
+ iterator.bind(callback)
419
+ iterator.run(tokens)
420
+
421
+ iterator.callbacks[0].start.should == 1
422
+ iterator.callbacks[0].finish.should == 1
423
+ end
424
+
425
+ it 'Share data between callback classes' do
426
+ code = <<-CODE
427
+ def example_method
428
+ return 10
429
+ end
430
+ CODE
431
+
432
+ tokens = Rlint::Parser.new(code).parse
433
+ iterator = Rlint::Iterator.new
434
+
435
+ setter = Class.new(Rlint::Callback) do
436
+ def on_method_definition(token)
437
+ @storage[:method_name] = token.name
438
+ end
439
+ end
440
+
441
+ getter = Class.new(Rlint::Callback) do
442
+ attr_reader :storage
443
+ end
444
+
445
+ iterator.bind(setter)
446
+ iterator.bind(getter)
447
+ iterator.run(tokens)
448
+
449
+ iterator.callbacks[1].storage.class.should == Hash
450
+ iterator.callbacks[1].storage[:method_name].should == 'example_method'
451
+ end
452
+ end