puppet-lint-halyard 1.1.0.1

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 (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 +14 -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