halyard-puppet-lint 1.2.0

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