puppet-lint 0.2.0.pre1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +1 -14
  3. data/Gemfile +2 -0
  4. data/Rakefile +5 -0
  5. data/bin/puppet-lint +1 -99
  6. data/lib/puppet-lint.rb +4 -12
  7. data/lib/puppet-lint/bin.rb +115 -0
  8. data/lib/puppet-lint/configuration.rb +6 -2
  9. data/lib/puppet-lint/lexer.rb +135 -83
  10. data/lib/puppet-lint/lexer/token.rb +62 -0
  11. data/lib/puppet-lint/plugin.rb +57 -51
  12. data/lib/puppet-lint/plugins.rb +2 -0
  13. data/lib/puppet-lint/plugins/check_classes.rb +161 -45
  14. data/lib/puppet-lint/plugins/check_comments.rb +33 -0
  15. data/lib/puppet-lint/plugins/check_conditionals.rb +8 -10
  16. data/lib/puppet-lint/plugins/check_documentation.rb +41 -0
  17. data/lib/puppet-lint/plugins/check_resources.rb +28 -2
  18. data/lib/puppet-lint/plugins/check_strings.rb +6 -4
  19. data/lib/puppet-lint/plugins/check_variables.rb +1 -1
  20. data/lib/puppet-lint/plugins/check_whitespace.rb +26 -49
  21. data/lib/puppet-lint/tasks/puppet-lint.rb +2 -1
  22. data/lib/puppet-lint/version.rb +1 -1
  23. data/puppet-lint.gemspec +1 -0
  24. data/spec/fixtures/test/manifests/fail.pp +2 -0
  25. data/spec/fixtures/test/manifests/init.pp +3 -0
  26. data/spec/fixtures/test/manifests/warning.pp +2 -0
  27. data/spec/puppet-lint/bin_spec.rb +266 -0
  28. data/spec/puppet-lint/configuration_spec.rb +51 -0
  29. data/spec/puppet-lint/lexer/token_spec.rb +18 -0
  30. data/spec/puppet-lint/lexer_spec.rb +738 -0
  31. data/spec/puppet-lint/{check_classes_spec.rb → plugins/check_classes_spec.rb} +74 -7
  32. data/spec/puppet-lint/plugins/check_comments_spec.rb +40 -0
  33. data/spec/puppet-lint/{check_conditionals_spec.rb → plugins/check_conditionals_spec.rb} +19 -0
  34. data/spec/puppet-lint/plugins/check_documentation_spec.rb +55 -0
  35. data/spec/puppet-lint/{check_resources_spec.rb → plugins/check_resources_spec.rb} +65 -0
  36. data/spec/puppet-lint/{check_strings_spec.rb → plugins/check_strings_spec.rb} +18 -1
  37. data/spec/puppet-lint/{check_variables_spec.rb → plugins/check_variables_spec.rb} +0 -0
  38. data/spec/puppet-lint/plugins/check_whitespace_spec.rb +291 -0
  39. data/spec/puppet-lint_spec.rb +10 -0
  40. data/spec/spec_helper.rb +5 -0
  41. metadata +58 -24
  42. data/spec/puppet-lint/check_whitespace_spec.rb +0 -120
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe PuppetLint::Configuration do
4
+ subject { PuppetLint::Configuration.new }
5
+
6
+ it 'should create check methods on the fly' do
7
+ subject.add_check('foo')
8
+
9
+ subject.should respond_to(:foo_enabled?)
10
+ subject.should_not respond_to(:bar_enabled?)
11
+ subject.should respond_to(:enable_foo)
12
+ subject.should respond_to(:disable_foo)
13
+
14
+ subject.disable_foo
15
+ subject.settings['foo_disabled'].should == true
16
+ subject.foo_enabled?.should == false
17
+
18
+ subject.enable_foo
19
+ subject.settings['foo_disabled'].should == false
20
+ subject.foo_enabled?.should == true
21
+ end
22
+
23
+ it 'should know what checks have been added' do
24
+ subject.checks.should include('foo')
25
+ end
26
+
27
+ it 'should respond nil to unknown config options' do
28
+ subject.foobarbaz.should == nil
29
+ end
30
+
31
+ it 'should create options on the fly' do
32
+ subject.add_option('bar')
33
+
34
+ subject.bar.should == nil
35
+
36
+ subject.bar = 'aoeui'
37
+ subject.bar.should == 'aoeui'
38
+ end
39
+
40
+ it 'should be able to set sane defaults' do
41
+ subject.defaults
42
+
43
+ subject.settings.should == {
44
+ 'with_filename' => false,
45
+ 'fail_on_warnings' => false,
46
+ 'error_level' => :all,
47
+ 'log_format' => '',
48
+ }
49
+ end
50
+ end
51
+
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe PuppetLint::Lexer::Token do
4
+ subject do
5
+ PuppetLint::Lexer::Token.new(:NAME, 'foo', 1, 2)
6
+ end
7
+
8
+ it { should respond_to(:type) }
9
+ it { should respond_to(:value) }
10
+ it { should respond_to(:line) }
11
+ it { should respond_to(:column) }
12
+
13
+ its(:type) { should == :NAME }
14
+ its(:value) { should == 'foo' }
15
+ its(:line) { should == 1 }
16
+ its(:column) { should == 2 }
17
+ its(:inspect) { should == "<Token :NAME (foo) @1:2>" }
18
+ end
@@ -0,0 +1,738 @@
1
+ require 'spec_helper'
2
+
3
+ describe PuppetLint::Lexer do
4
+ before do
5
+ @lexer = PuppetLint::Lexer.new
6
+ end
7
+
8
+ context 'invalid code' do
9
+ it 'should bork' do
10
+ expect { @lexer.tokenise('%') }.to raise_error(PuppetLint::LexerError)
11
+ end
12
+ end
13
+
14
+ context '#new_token' do
15
+ it 'should calculate the line number for an empty string' do
16
+ token = @lexer.new_token(:TEST, 'test', :chunk => '')
17
+ token.line.should == 1
18
+ end
19
+
20
+ it 'should calculate the line number for a multi line string' do
21
+ token = @lexer.new_token(:TEST, 'test', :chunk => "foo\nbar")
22
+ token.line.should == 2
23
+ end
24
+
25
+ it 'should calculate the column number for an empty string' do
26
+ token = @lexer.new_token(:TEST, 'test', :chunk => '')
27
+ token.column.should == 1
28
+ end
29
+
30
+ it 'should calculate the column number for a single line string' do
31
+ token = @lexer.new_token(:TEST, 'test', :chunk => 'this is a test')
32
+ token.column.should == 14
33
+ end
34
+
35
+ it 'should calculate the column number for a multi line string' do
36
+ token = @lexer.new_token(:TEST, 'test', :chunk => "foo\nbar\nbaz\ngronk")
37
+ token.column.should == 5
38
+ end
39
+ end
40
+
41
+ context '#get_string_segment' do
42
+ it 'should get a segment with a single terminator' do
43
+ data = StringScanner.new('foo"bar')
44
+ value, terminator = @lexer.get_string_segment(data, '"')
45
+ value.should == 'foo'
46
+ terminator.should == '"'
47
+ end
48
+
49
+ it 'should get a segment with multiple terminators' do
50
+ data = StringScanner.new('foo"bar$baz')
51
+ value, terminator = @lexer.get_string_segment(data, "'$")
52
+ value.should == 'foo"bar'
53
+ terminator.should == '$'
54
+ end
55
+
56
+ it 'should not get a segment with an escaped terminator' do
57
+ data = StringScanner.new('foo"bar')
58
+ value, terminator = @lexer.get_string_segment(data, '$')
59
+ value.should be_nil
60
+ terminator.should be_nil
61
+ end
62
+ end
63
+
64
+ context '#interpolate_string' do
65
+ it 'should handle a string with no variables' do
66
+ @lexer.interpolate_string('foo bar baz"',1, 1)
67
+ token = @lexer.tokens.first
68
+
69
+ @lexer.tokens.length.should == 1
70
+ token.type.should == :STRING
71
+ token.value.should == 'foo bar baz'
72
+ token.line.should == 1
73
+ token.column.should == 1
74
+ end
75
+
76
+ it 'should handle a string with a newline' do
77
+ @lexer.interpolate_string(%{foo\nbar"}, 1, 1)
78
+ token = @lexer.tokens.first
79
+
80
+ @lexer.tokens.length.should == 1
81
+ token.type.should == :STRING
82
+ token.value.should == "foo\nbar"
83
+ token.line.should == 1
84
+ token.column.should == 1
85
+ end
86
+
87
+ it 'should handle a string with a single variable and suffix' do
88
+ @lexer.interpolate_string('${foo}bar"', 1, 1)
89
+ tokens = @lexer.tokens
90
+
91
+ tokens.length.should == 3
92
+
93
+ tokens[0].type.should == :DQPRE
94
+ tokens[0].value.should == ''
95
+ tokens[0].line.should == 1
96
+ tokens[0].column.should == 1
97
+
98
+ tokens[1].type.should == :VARIABLE
99
+ tokens[1].value.should == 'foo'
100
+ tokens[1].line.should == 1
101
+ tokens[1].column.should == 3
102
+
103
+ tokens[2].type.should == :DQPOST
104
+ tokens[2].value.should == 'bar'
105
+ tokens[2].line.should == 1
106
+ tokens[2].column.should == 8
107
+ end
108
+
109
+ it 'should handle a string with a single variable and surrounding text' do
110
+ @lexer.interpolate_string('foo${bar}baz"', 1, 1)
111
+ tokens = @lexer.tokens
112
+
113
+ tokens.length.should == 3
114
+
115
+ tokens[0].type.should == :DQPRE
116
+ tokens[0].value.should == 'foo'
117
+ tokens[0].line.should == 1
118
+ tokens[0].column.should == 1
119
+
120
+ tokens[1].type.should == :VARIABLE
121
+ tokens[1].value.should == 'bar'
122
+ tokens[1].line.should == 1
123
+ tokens[1].column.should == 6
124
+
125
+ tokens[2].type.should == :DQPOST
126
+ tokens[2].value.should == 'baz'
127
+ tokens[2].line.should == 1
128
+ tokens[2].column.should == 11
129
+ end
130
+
131
+ it 'should handle a string with multiple variables and surrounding text' do
132
+ @lexer.interpolate_string('foo${bar}baz${gronk}meh"', 1, 1)
133
+ tokens = @lexer.tokens
134
+
135
+ tokens.length.should == 5
136
+
137
+ tokens[0].type.should == :DQPRE
138
+ tokens[0].value.should == 'foo'
139
+ tokens[0].line.should == 1
140
+ tokens[0].column.should == 1
141
+
142
+ tokens[1].type.should == :VARIABLE
143
+ tokens[1].value.should == 'bar'
144
+ tokens[1].line.should == 1
145
+ tokens[1].column.should == 6
146
+
147
+ tokens[2].type.should == :DQMID
148
+ tokens[2].value.should == 'baz'
149
+ tokens[2].line.should == 1
150
+ tokens[2].column.should == 11
151
+
152
+ tokens[3].type.should == :VARIABLE
153
+ tokens[3].value.should == 'gronk'
154
+ tokens[3].line.should == 1
155
+ tokens[3].column.should == 15
156
+
157
+ tokens[4].type.should == :DQPOST
158
+ tokens[4].value.should == 'meh'
159
+ tokens[4].line.should == 1
160
+ tokens[4].column.should == 22
161
+ end
162
+
163
+ it 'should handle a string with only a single variable' do
164
+ @lexer.interpolate_string('${bar}"', 1, 1)
165
+ tokens = @lexer.tokens
166
+
167
+ tokens.length.should == 3
168
+
169
+ tokens[0].type.should == :DQPRE
170
+ tokens[0].value.should == ''
171
+ tokens[0].line.should == 1
172
+ tokens[0].column.should == 1
173
+
174
+ tokens[1].type.should == :VARIABLE
175
+ tokens[1].value.should == 'bar'
176
+ tokens[1].line.should == 1
177
+ tokens[1].column.should == 3
178
+
179
+ tokens[2].type.should == :DQPOST
180
+ tokens[2].value.should == ''
181
+ tokens[2].line.should == 1
182
+ tokens[2].column.should == 8
183
+ end
184
+
185
+ it 'should handle a string with only many variables' do
186
+ @lexer.interpolate_string('${bar}${gronk}"', 1, 1)
187
+ tokens = @lexer.tokens
188
+
189
+ tokens.length.should == 5
190
+
191
+ tokens[0].type.should == :DQPRE
192
+ tokens[0].value.should == ''
193
+ tokens[0].line.should == 1
194
+ tokens[0].column.should == 1
195
+
196
+ tokens[1].type.should == :VARIABLE
197
+ tokens[1].value.should == 'bar'
198
+ tokens[1].line.should == 1
199
+ tokens[1].column.should == 3
200
+
201
+ tokens[2].type.should == :DQMID
202
+ tokens[2].value.should == ''
203
+ tokens[2].line.should == 1
204
+ tokens[2].column.should == 8
205
+
206
+ tokens[3].type.should == :VARIABLE
207
+ tokens[3].value.should == 'gronk'
208
+ tokens[3].line.should == 1
209
+ tokens[3].column.should == 9
210
+
211
+ tokens[4].type.should == :DQPOST
212
+ tokens[4].value.should == ''
213
+ tokens[4].line.should == 1
214
+ tokens[4].column.should == 16
215
+ end
216
+
217
+ it 'should handle a string with only an unenclosed variable' do
218
+ @lexer.interpolate_string('$foo"', 1, 1)
219
+ tokens = @lexer.tokens
220
+
221
+ tokens.length.should == 3
222
+
223
+ tokens[0].type.should == :DQPRE
224
+ tokens[0].value.should == ''
225
+ tokens[0].line.should == 1
226
+ tokens[0].column.should == 1
227
+
228
+ tokens[1].type.should == :UNENC_VARIABLE
229
+ tokens[1].value.should == 'foo'
230
+ tokens[1].line.should == 1
231
+ tokens[1].column.should == 2
232
+
233
+ tokens[2].type.should == :DQPOST
234
+ tokens[2].value.should == ''
235
+ tokens[2].line.should == 1
236
+ tokens[2].column.should == 6
237
+ end
238
+
239
+ it 'should handle a string with a nested string inside it' do
240
+ @lexer.interpolate_string(%q{string with ${'a nested single quoted string'} inside it"}, 1, 1)
241
+ tokens = @lexer.tokens
242
+
243
+ tokens.length.should == 3
244
+
245
+ tokens[0].type.should == :DQPRE
246
+ tokens[0].value.should == 'string with '
247
+ tokens[0].line.should == 1
248
+ tokens[0].column.should == 1
249
+
250
+ tokens[1].type.should == :SSTRING
251
+ tokens[1].value.should == 'a nested single quoted string'
252
+ tokens[1].line.should == 1
253
+ tokens[1].column.should == 16
254
+
255
+ tokens[2].type.should == :DQPOST
256
+ tokens[2].value.should == ' inside it'
257
+ tokens[2].line.should == 1
258
+ tokens[2].column.should == 48
259
+ end
260
+
261
+ it 'should handle a string with nested math' do
262
+ @lexer.interpolate_string(%q{string with ${(3+5)/4} nested math"}, 1, 1)
263
+ tokens = @lexer.tokens
264
+
265
+ tokens.length.should == 9
266
+
267
+ tokens[0].type.should == :DQPRE
268
+ tokens[0].value.should == 'string with '
269
+ tokens[0].line.should == 1
270
+ tokens[0].column.should == 1
271
+
272
+ tokens[1].type.should == :LPAREN
273
+ tokens[1].line.should == 1
274
+ tokens[1].column.should == 16
275
+
276
+ tokens[2].type.should == :NUMBER
277
+ tokens[2].value.should == '3'
278
+ tokens[2].line.should == 1
279
+ tokens[2].column.should == 17
280
+
281
+ tokens[3].type.should == :PLUS
282
+ tokens[3].line.should == 1
283
+ tokens[3].column.should == 18
284
+
285
+ tokens[4].type.should == :NUMBER
286
+ tokens[4].value.should == '5'
287
+ tokens[4].line.should == 1
288
+ tokens[4].column.should == 19
289
+
290
+ tokens[5].type.should == :RPAREN
291
+ tokens[5].line.should == 1
292
+ tokens[5].column.should == 20
293
+
294
+ tokens[6].type.should == :DIV
295
+ tokens[6].line.should == 1
296
+ tokens[6].column.should == 21
297
+
298
+ tokens[7].type.should == :NUMBER
299
+ tokens[7].value.should == '4'
300
+ tokens[7].line.should == 1
301
+ tokens[7].column.should == 22
302
+
303
+ tokens[8].type.should == :DQPOST
304
+ tokens[8].value.should == ' nested math'
305
+ tokens[8].line.should == 1
306
+ tokens[8].column.should == 24
307
+ end
308
+
309
+ it 'should handle a string with a nested array' do
310
+ @lexer.interpolate_string(%q{string with ${['an array ', $v2]} in it"}, 1, 1)
311
+ tokens = @lexer.tokens
312
+
313
+ tokens.length.should == 8
314
+
315
+ tokens[0].type.should == :DQPRE
316
+ tokens[0].value.should == 'string with '
317
+ tokens[0].line.should == 1
318
+ tokens[0].column.should == 1
319
+
320
+ tokens[1].type.should == :LBRACK
321
+ tokens[1].line.should == 1
322
+ tokens[1].column.should == 16
323
+
324
+ tokens[2].type.should == :SSTRING
325
+ tokens[2].value.should == 'an array '
326
+ tokens[2].line.should == 1
327
+ tokens[2].column.should == 17
328
+
329
+ tokens[3].type.should == :COMMA
330
+ tokens[3].line.should == 1
331
+ tokens[3].column.should == 28
332
+
333
+ tokens[4].type.should == :WHITESPACE
334
+ tokens[4].value.should == ' '
335
+ tokens[4].line.should == 1
336
+ tokens[4].column.should == 29
337
+
338
+ tokens[5].type.should == :VARIABLE
339
+ tokens[5].value.should == 'v2'
340
+ tokens[5].line.should == 1
341
+ tokens[5].column.should == 30
342
+
343
+ tokens[6].type.should == :RBRACK
344
+ tokens[6].line.should == 1
345
+ tokens[6].column.should == 33
346
+
347
+ tokens[7].type.should == :DQPOST
348
+ tokens[7].value.should == ' in it'
349
+ tokens[7].line.should == 1
350
+ tokens[7].column.should == 35
351
+ end
352
+
353
+ it 'should handle a string of $s' do
354
+ @lexer.interpolate_string(%q{$$$$"}, 1, 1)
355
+ tokens = @lexer.tokens
356
+
357
+ tokens.length.should == 1
358
+
359
+ tokens[0].type.should == :STRING
360
+ tokens[0].value.should == '$$$$'
361
+ tokens[0].line.should == 1
362
+ tokens[0].column.should == 1
363
+ end
364
+
365
+ it 'should handle "$foo$bar"' do
366
+ @lexer.interpolate_string(%q{$foo$bar"}, 1, 1)
367
+ tokens = @lexer.tokens
368
+
369
+ tokens.length.should == 5
370
+
371
+ tokens[0].type.should == :DQPRE
372
+ tokens[0].value.should == ''
373
+ tokens[0].line.should == 1
374
+ tokens[0].column.should == 1
375
+
376
+ tokens[1].type.should == :UNENC_VARIABLE
377
+ tokens[1].value.should == 'foo'
378
+ tokens[1].line.should == 1
379
+ tokens[1].column.should == 2
380
+
381
+ tokens[2].type.should == :DQMID
382
+ tokens[2].value.should == ''
383
+ tokens[2].line.should == 1
384
+ tokens[2].column.should == 6
385
+
386
+ tokens[3].type.should == :UNENC_VARIABLE
387
+ tokens[3].value.should == 'bar'
388
+ tokens[3].line.should == 1
389
+ tokens[3].column.should == 6
390
+
391
+ tokens[4].type.should == :DQPOST
392
+ tokens[4].value.should == ''
393
+ tokens[4].line.should == 1
394
+ tokens[4].column.should == 10
395
+ end
396
+
397
+ it 'should handle "foo$bar$"' do
398
+ @lexer.interpolate_string(%q{foo$bar$"}, 1, 1)
399
+ tokens = @lexer.tokens
400
+
401
+ tokens.length.should == 3
402
+
403
+ tokens[0].type.should == :DQPRE
404
+ tokens[0].value.should == 'foo'
405
+ tokens[0].line.should == 1
406
+ tokens[0].column.should == 1
407
+
408
+ tokens[1].type.should == :UNENC_VARIABLE
409
+ tokens[1].value.should == 'bar'
410
+ tokens[1].line.should == 1
411
+ tokens[1].column.should == 5
412
+
413
+ tokens[2].type.should == :DQPOST
414
+ tokens[2].value.should == '$'
415
+ tokens[2].line.should == 1
416
+ tokens[2].column.should == 9
417
+ end
418
+
419
+ it 'should handle "foo$$bar"' do
420
+ @lexer.interpolate_string(%q{foo$$bar"}, 1, 1)
421
+ tokens = @lexer.tokens
422
+
423
+ tokens.length.should == 3
424
+
425
+ tokens[0].type.should == :DQPRE
426
+ tokens[0].value.should == 'foo$'
427
+ tokens[0].line.should == 1
428
+ tokens[0].column.should == 1
429
+
430
+ tokens[1].type.should == :UNENC_VARIABLE
431
+ tokens[1].value.should == 'bar'
432
+ tokens[1].line.should == 1
433
+ tokens[1].column.should == 6
434
+
435
+ tokens[2].type.should == :DQPOST
436
+ tokens[2].value.should == ''
437
+ tokens[2].line.should == 1
438
+ tokens[2].column.should == 10
439
+ end
440
+
441
+ it 'should handle an empty string' do
442
+ @lexer.interpolate_string(%q{"}, 1, 1)
443
+ tokens = @lexer.tokens
444
+
445
+ tokens.length.should == 1
446
+
447
+ tokens[0].type.should == :STRING
448
+ tokens[0].value.should == ''
449
+ tokens[0].line.should == 1
450
+ tokens[0].column.should == 1
451
+ end
452
+
453
+ it 'should handle "$foo::::bar"' do
454
+ @lexer.interpolate_string(%q{$foo::::bar"}, 1, 1)
455
+ tokens = @lexer.tokens
456
+
457
+ tokens.length.should == 3
458
+
459
+ tokens[0].type.should == :DQPRE
460
+ tokens[0].value.should == ''
461
+ tokens[0].line.should == 1
462
+ tokens[0].column.should == 1
463
+
464
+ tokens[1].type.should == :UNENC_VARIABLE
465
+ tokens[1].value.should == 'foo'
466
+ tokens[1].line.should == 1
467
+ tokens[1].column.should == 2
468
+
469
+ tokens[2].type.should == :DQPOST
470
+ tokens[2].value.should == '::::bar'
471
+ tokens[2].line.should == 1
472
+ tokens[2].column.should == 6
473
+ end
474
+ end
475
+
476
+ [
477
+ 'case',
478
+ 'class',
479
+ 'default',
480
+ 'define',
481
+ 'import',
482
+ 'if',
483
+ 'elsif',
484
+ 'else',
485
+ 'inherits',
486
+ 'node',
487
+ 'and',
488
+ 'or',
489
+ 'undef',
490
+ 'true',
491
+ 'false',
492
+ 'in',
493
+ 'unless',
494
+ ].each do |keyword|
495
+ it "should handle '#{keyword}' as a keyword" do
496
+ token = @lexer.tokenise(keyword).first
497
+ token.type.should == keyword.upcase.to_sym
498
+ token.value.should == keyword
499
+ end
500
+ end
501
+
502
+ [
503
+ [:LBRACK, '['],
504
+ [:RBRACK, ']'],
505
+ [:LBRACE, '{'],
506
+ [:RBRACE, '}'],
507
+ [:LPAREN, '('],
508
+ [:RPAREN, ')'],
509
+ [:EQUALS, '='],
510
+ [:ISEQUAL, '=='],
511
+ [:GREATEREQUAL, '>='],
512
+ [:GREATERTHAN, '>'],
513
+ [:LESSTHAN, '<'],
514
+ [:LESSEQUAL, '<='],
515
+ [:NOTEQUAL, '!='],
516
+ [:NOT, '!'],
517
+ [:COMMA, ','],
518
+ [:DOT, '.'],
519
+ [:COLON, ':'],
520
+ [:AT, '@'],
521
+ [:LLCOLLECT, '<<|'],
522
+ [:RRCOLLECT, '|>>'],
523
+ [:LCOLLECT, '<|'],
524
+ [:RCOLLECT, '|>'],
525
+ [:SEMIC, ';'],
526
+ [:QMARK, '?'],
527
+ [:BACKSLASH, '\\'],
528
+ [:FARROW, '=>'],
529
+ [:PARROW, '+>'],
530
+ [:APPENDS, '+='],
531
+ [:PLUS, '+'],
532
+ [:MINUS, '-'],
533
+ [:DIV, '/'],
534
+ [:TIMES, '*'],
535
+ [:LSHIFT, '<<'],
536
+ [:RSHIFT, '>>'],
537
+ [:MATCH, '=~'],
538
+ [:NOMATCH, '!~'],
539
+ [:IN_EDGE, '->'],
540
+ [:OUT_EDGE, '<-'],
541
+ [:IN_EDGE_SUB, '~>'],
542
+ [:OUT_EDGE_SUB, '<~'],
543
+ ].each do |name, string|
544
+ it "should have a token named '#{name.to_s}'" do
545
+ token = @lexer.tokenise(string).first
546
+ token.type.should == name
547
+ token.value.should == string
548
+ end
549
+ end
550
+
551
+ context ':CLASSREF' do
552
+ it 'should match single capitalised alphanumeric term' do
553
+ token = @lexer.tokenise('One').first
554
+ token.type.should == :CLASSREF
555
+ token.value.should == 'One'
556
+ end
557
+
558
+ it 'should match two capitalised alphanumeric terms sep by ::' do
559
+ token = @lexer.tokenise('One::Two').first
560
+ token.type.should == :CLASSREF
561
+ token.value.should == 'One::Two'
562
+ end
563
+
564
+ it 'should match many capitalised alphanumeric terms sep by ::' do
565
+ token = @lexer.tokenise('One::Two::Three::Four::Five').first
566
+ token.type.should == :CLASSREF
567
+ token.value.should == 'One::Two::Three::Four::Five'
568
+ end
569
+
570
+ it 'should match capitalised terms prefixed by ::' do
571
+ token = @lexer.tokenise('::One').first
572
+ token.type.should == :CLASSREF
573
+ token.value.should == '::One'
574
+ end
575
+ end
576
+
577
+ context ':NAME' do
578
+ it 'should match lowercase alphanumeric terms' do
579
+ token = @lexer.tokenise('one-two').first
580
+ token.type.should == :NAME
581
+ token.value.should == 'one-two'
582
+ end
583
+
584
+ it 'should match lowercase alphanumeric terms sep by ::' do
585
+ token = @lexer.tokenise('one::two').first
586
+ token.type.should == :NAME
587
+ token.value.should == 'one::two'
588
+ end
589
+
590
+ it 'should match many lowercase alphanumeric terms sep by ::' do
591
+ token = @lexer.tokenise('one::two::three::four::five').first
592
+ token.type.should == :NAME
593
+ token.value.should == 'one::two::three::four::five'
594
+ end
595
+
596
+ it 'should match lowercase alphanumeric terms prefixed by ::' do
597
+ token = @lexer.tokenise('::1one::2two::3three').first
598
+ token.type.should == :NAME
599
+ token.value.should == '::1one::2two::3three'
600
+ end
601
+ end
602
+
603
+ context ':NUMBER' do
604
+ it 'should match numeric terms' do
605
+ token = @lexer.tokenise('1234567890').first
606
+ token.type.should == :NUMBER
607
+ token.value.should == '1234567890'
608
+ end
609
+
610
+ it 'should match float terms' do
611
+ token = @lexer.tokenise('12345.6789').first
612
+ token.type.should == :NUMBER
613
+ token.value.should == '12345.6789'
614
+ end
615
+
616
+ it 'should match hexadecimal terms' do
617
+ token = @lexer.tokenise('0xCAFE1029').first
618
+ token.type.should == :NUMBER
619
+ token.value.should == '0xCAFE1029'
620
+ end
621
+
622
+ it 'should match float with exponent terms' do
623
+ token = @lexer.tokenise('10e23').first
624
+ token.type.should == :NUMBER
625
+ token.value.should == '10e23'
626
+ end
627
+
628
+ it 'should match float with negative exponent terms' do
629
+ token = @lexer.tokenise('10e-23').first
630
+ token.type.should == :NUMBER
631
+ token.value.should == '10e-23'
632
+ end
633
+
634
+ it 'should match float with exponent terms' do
635
+ token = @lexer.tokenise('1.234e5').first
636
+ token.type.should == :NUMBER
637
+ token.value.should == '1.234e5'
638
+ end
639
+ end
640
+
641
+ context ':COMMENT' do
642
+ it 'should match everything on a line after #' do
643
+ token = @lexer.tokenise('foo # bar baz')[2]
644
+ token.type.should == :COMMENT
645
+ token.value.should == 'bar baz'
646
+ end
647
+ end
648
+
649
+ context ':MLCOMMENT' do
650
+ it 'should match comments on a single line' do
651
+ token = @lexer.tokenise('/* foo bar */').first
652
+ token.type.should == :MLCOMMENT
653
+ token.value.should == 'foo bar'
654
+ end
655
+
656
+ it 'should match comments on multiple lines' do
657
+ token = @lexer.tokenise("/*\n * foo bar\n*/").first
658
+ token.type.should == :MLCOMMENT
659
+ token.value.should == 'foo bar'
660
+ end
661
+ end
662
+
663
+ context ':SLASH_COMMENT' do
664
+ it 'should match everyone on a line after //' do
665
+ token = @lexer.tokenise('foo // bar baz')[2]
666
+ token.type.should == :SLASH_COMMENT
667
+ token.value.should == 'bar baz'
668
+ end
669
+ end
670
+
671
+ context ':SSTRING' do
672
+ it 'should match a single quoted string' do
673
+ token = @lexer.tokenise("'single quoted string'").first
674
+ token.type.should == :SSTRING
675
+ token.value.should == 'single quoted string'
676
+ end
677
+
678
+ it "should match a single quoted string with an escaped '" do
679
+ token = @lexer.tokenise(%q{'single quoted string with "\\'"'}).first
680
+ token.type.should == :SSTRING
681
+ token.value.should == 'single quoted string with "\\\'"'
682
+ end
683
+
684
+ it "should match a single quoted string with an escaped $" do
685
+ token = @lexer.tokenise(%q{'single quoted string with "\$"'}).first
686
+ token.type.should == :SSTRING
687
+ token.value.should == 'single quoted string with "\\$"'
688
+ end
689
+
690
+ it "should match a single quoted string with an escaped ." do
691
+ token = @lexer.tokenise(%q{'single quoted string with "\."'}).first
692
+ token.type.should == :SSTRING
693
+ token.value.should == 'single quoted string with "\\."'
694
+ end
695
+
696
+ it "should match a single quoted string with an escaped \\n" do
697
+ token = @lexer.tokenise(%q{'single quoted string with "\n"'}).first
698
+ token.type.should == :SSTRING
699
+ token.value.should == 'single quoted string with "\\n"'
700
+ end
701
+
702
+ it "should match a single quoted string with an escaped \\" do
703
+ token = @lexer.tokenise(%q{'single quoted string with "\\\\"'}).first
704
+ token.type.should == :SSTRING
705
+ token.value.should == 'single quoted string with "\\\\"'
706
+ end
707
+
708
+ it "should match an empty string" do
709
+ token = @lexer.tokenise("''").first
710
+ token.type.should == :SSTRING
711
+ token.value.should == ''
712
+ end
713
+ end
714
+
715
+ context ':REGEX' do
716
+ it 'should match anything enclosed in //' do
717
+ token = @lexer.tokenise('/this is a regex/').first
718
+ token.type.should == :REGEX
719
+ token.value.should == 'this is a regex'
720
+ end
721
+
722
+ it 'should not match if there is \n in the regex' do
723
+ token = @lexer.tokenise("/this is \n a regex/").first
724
+ token.type.should_not == :REGEX
725
+ end
726
+
727
+ it 'should not consider \/ to be the end of the regex' do
728
+ token = @lexer.tokenise('/this is \/ a regex/').first
729
+ token.type.should == :REGEX
730
+ token.value.should == 'this is \\/ a regex'
731
+ end
732
+
733
+ it 'should not match chained division' do
734
+ tokens = @lexer.tokenise('$x = $a/$b/$c')
735
+ tokens.select { |r| r.type == :REGEX }.should == []
736
+ end
737
+ end
738
+ end