scss-lint 0.25.1 → 0.26.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +10 -1
  3. data/data/property-sort-orders/concentric.txt +99 -0
  4. data/lib/scss_lint.rb +1 -0
  5. data/lib/scss_lint/cli.rb +9 -3
  6. data/lib/scss_lint/exceptions.rb +4 -0
  7. data/lib/scss_lint/linter.rb +10 -1
  8. data/lib/scss_lint/linter/capitalization_in_selector.rb +14 -6
  9. data/lib/scss_lint/linter/compass/property_with_mixin.rb +9 -2
  10. data/lib/scss_lint/linter/indentation.rb +28 -6
  11. data/lib/scss_lint/linter/property_sort_order.rb +61 -9
  12. data/lib/scss_lint/linter/single_line_per_property.rb +53 -0
  13. data/lib/scss_lint/linter/single_line_per_selector.rb +6 -1
  14. data/lib/scss_lint/linter/space_after_comma.rb +27 -19
  15. data/lib/scss_lint/linter/space_before_brace.rb +5 -4
  16. data/lib/scss_lint/linter/trailing_semicolon.rb +53 -0
  17. data/lib/scss_lint/linter/unnecessary_parent_reference.rb +36 -0
  18. data/lib/scss_lint/reporter/default_reporter.rb +7 -2
  19. data/lib/scss_lint/reporter/xml_reporter.rb +2 -1
  20. data/lib/scss_lint/runner.rb +7 -3
  21. data/lib/scss_lint/version.rb +1 -1
  22. data/spec/scss_lint/cli_spec.rb +314 -0
  23. data/spec/scss_lint/config_spec.rb +439 -0
  24. data/spec/scss_lint/engine_spec.rb +24 -0
  25. data/spec/scss_lint/linter/border_zero_spec.rb +84 -0
  26. data/spec/scss_lint/linter/capitalization_in_selector_spec.rb +71 -0
  27. data/spec/scss_lint/linter/color_keyword_spec.rb +83 -0
  28. data/spec/scss_lint/linter/comment_spec.rb +55 -0
  29. data/spec/scss_lint/linter/compass/property_with_mixin_spec.rb +55 -0
  30. data/spec/scss_lint/linter/debug_statement_spec.rb +21 -0
  31. data/spec/scss_lint/linter/declaration_order_spec.rb +94 -0
  32. data/spec/scss_lint/linter/duplicate_property_spec.rb +176 -0
  33. data/spec/scss_lint/linter/else_placement_spec.rb +106 -0
  34. data/spec/scss_lint/linter/empty_line_between_blocks_spec.rb +263 -0
  35. data/spec/scss_lint/linter/empty_rule_spec.rb +27 -0
  36. data/spec/scss_lint/linter/final_newline_spec.rb +49 -0
  37. data/spec/scss_lint/linter/hex_length_spec.rb +104 -0
  38. data/spec/scss_lint/linter/hex_notation_spec.rb +104 -0
  39. data/spec/scss_lint/linter/hex_validation_spec.rb +36 -0
  40. data/spec/scss_lint/linter/id_with_extraneous_selector_spec.rb +139 -0
  41. data/spec/scss_lint/linter/indentation_spec.rb +242 -0
  42. data/spec/scss_lint/linter/leading_zero_spec.rb +233 -0
  43. data/spec/scss_lint/linter/mergeable_selector_spec.rb +283 -0
  44. data/spec/scss_lint/linter/name_format_spec.rb +206 -0
  45. data/spec/scss_lint/linter/placeholder_in_extend_spec.rb +63 -0
  46. data/spec/scss_lint/linter/property_sort_order_spec.rb +246 -0
  47. data/spec/scss_lint/linter/property_spelling_spec.rb +57 -0
  48. data/spec/scss_lint/linter/selector_depth_spec.rb +159 -0
  49. data/spec/scss_lint/linter/shorthand_spec.rb +172 -0
  50. data/spec/scss_lint/linter/single_line_per_property_spec.rb +73 -0
  51. data/spec/scss_lint/linter/single_line_per_selector_spec.rb +121 -0
  52. data/spec/scss_lint/linter/space_after_comma_spec.rb +315 -0
  53. data/spec/scss_lint/linter/space_after_property_colon_spec.rb +238 -0
  54. data/spec/scss_lint/linter/space_after_property_name_spec.rb +23 -0
  55. data/spec/scss_lint/linter/space_before_brace_spec.rb +447 -0
  56. data/spec/scss_lint/linter/space_between_parens_spec.rb +263 -0
  57. data/spec/scss_lint/linter/string_quotes_spec.rb +303 -0
  58. data/spec/scss_lint/linter/trailing_semicolon_spec.rb +188 -0
  59. data/spec/scss_lint/linter/unnecessary_mantissa_spec.rb +67 -0
  60. data/spec/scss_lint/linter/unnecessary_parent_reference_spec.rb +67 -0
  61. data/spec/scss_lint/linter/url_format_spec.rb +55 -0
  62. data/spec/scss_lint/linter/url_quotes_spec.rb +63 -0
  63. data/spec/scss_lint/linter/zero_unit_spec.rb +113 -0
  64. data/spec/scss_lint/linter_registry_spec.rb +50 -0
  65. data/spec/scss_lint/location_spec.rb +42 -0
  66. data/spec/scss_lint/reporter/config_reporter_spec.rb +42 -0
  67. data/spec/scss_lint/reporter/default_reporter_spec.rb +73 -0
  68. data/spec/scss_lint/reporter/files_reporter_spec.rb +38 -0
  69. data/spec/scss_lint/reporter/xml_reporter_spec.rb +103 -0
  70. data/spec/scss_lint/reporter_spec.rb +11 -0
  71. data/spec/scss_lint/runner_spec.rb +132 -0
  72. data/spec/scss_lint/selector_visitor_spec.rb +264 -0
  73. data/spec/spec_helper.rb +34 -0
  74. data/spec/support/isolated_environment.rb +25 -0
  75. data/spec/support/matchers/report_lint.rb +48 -0
  76. metadata +126 -8
  77. data/lib/scss_lint/linter/trailing_semicolon_after_property_value.rb +0 -40
@@ -0,0 +1,439 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+
4
+ describe SCSSLint::Config do
5
+ class SCSSLint::Linter::FakeConfigLinter < SCSSLint::Linter; end
6
+
7
+ module SCSSLint::Linter::SomeNamespace
8
+ class FakeLinter1 < SCSSLint::Linter; end
9
+ class FakeLinter2 < SCSSLint::Linter; end
10
+ end
11
+
12
+ let(:default_file) { File.open(described_class::DEFAULT_FILE).read }
13
+
14
+ # This complex stubbing bypasses the built-in caching of the methods, and at
15
+ # the same time gives us full control over the "default" configuration.
16
+ before do
17
+ described_class
18
+ .stub(:load_file_contents)
19
+ .with(described_class::DEFAULT_FILE)
20
+ .and_return(default_file)
21
+
22
+ described_class
23
+ .stub(:default_options_hash)
24
+ .and_return(described_class.send(:load_options_hash_from_file, described_class::DEFAULT_FILE))
25
+
26
+ described_class
27
+ .stub(:default)
28
+ .and_return(described_class.load(described_class::DEFAULT_FILE, merge_with_default: false))
29
+ end
30
+
31
+ describe '.default' do
32
+ subject { described_class.default }
33
+
34
+ it 'has a configuration defined for all registered linters' do
35
+ SCSSLint::LinterRegistry.linters.map(&:new).each do |linter|
36
+ subject.linter_options(linter).should_not be_nil
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '.load' do
42
+ let(:config_dir) { '/path/to' }
43
+ let(:file_name) { "/#{config_dir}/config.yml" }
44
+
45
+ let(:default_file) { <<-FILE }
46
+ linters:
47
+ FakeConfigLinter:
48
+ enabled: true
49
+ OtherFakeConfigLinter:
50
+ enabled: false
51
+ FILE
52
+
53
+ subject { described_class.load(file_name) }
54
+
55
+ before do
56
+ described_class.stub(:load_file_contents)
57
+ .with(file_name)
58
+ .and_return(config_file)
59
+ end
60
+
61
+ context 'with an empty config file' do
62
+ let(:config_file) { '' }
63
+
64
+ it 'returns the default configuration' do
65
+ subject.should == described_class.default
66
+ end
67
+ end
68
+
69
+ context 'with a config file containing only comments' do
70
+ let(:config_file) { '# This is a comment' }
71
+
72
+ it 'returns the default configuration' do
73
+ subject.should == described_class.default
74
+ end
75
+ end
76
+
77
+ context 'with a file configuring an unknown linter' do
78
+ let(:config_file) { 'linters: { MadeUpLinterName: { enabled: true } }' }
79
+
80
+ it 'stores a warning for the unknown linter' do
81
+ subject.warnings
82
+ .any? { |warning| warning.include?('MadeUpLinterName') }
83
+ .should be true
84
+ end
85
+ end
86
+
87
+ context 'with a config file setting the same configuration as the default' do
88
+ let(:config_file) { default_file }
89
+
90
+ it 'returns a configuration equivalent to the default' do
91
+ subject.should == described_class.default
92
+ end
93
+ end
94
+
95
+ context 'with a config file setting the same subset of settings as the default' do
96
+ let(:config_file) { <<-FILE }
97
+ linters:
98
+ FakeConfigLinter:
99
+ enabled: true
100
+ FILE
101
+
102
+ it 'returns a configuration equivalent to the default' do
103
+ subject.should == described_class.default
104
+ end
105
+ end
106
+
107
+ context 'with a file that includes another configuration file' do
108
+ let(:included_file_path) { '../included_file.yml' }
109
+
110
+ let(:config_file) { <<-FILE }
111
+ inherit_from: #{included_file_path}
112
+
113
+ linters:
114
+ FakeConfigLinter:
115
+ enabled: true
116
+ some_other_option: some_other_value
117
+ FILE
118
+
119
+ before do
120
+ described_class.stub(:load_file_contents)
121
+ .with("#{config_dir}/" + included_file_path)
122
+ .and_return(included_file)
123
+ end
124
+
125
+ context 'and the included file has a different setting from the default' do
126
+ let(:included_file) { <<-FILE }
127
+ linters:
128
+ OtherFakeConfigLinter:
129
+ enabled: true
130
+ some_option: some_value
131
+ FILE
132
+
133
+ it 'reflects the different setting of the included file' do
134
+ subject.options['linters']['OtherFakeConfigLinter']
135
+ .should == { 'enabled' => true, 'some_option' => 'some_value' }
136
+ end
137
+
138
+ it 'reflects the different setting of the file that included the file' do
139
+ subject.options['linters']['FakeConfigLinter']
140
+ .should == { 'enabled' => true, 'some_other_option' => 'some_other_value' }
141
+ end
142
+ end
143
+
144
+ context 'and the included file has the same setting as the default' do
145
+ let(:included_file) { <<-FILE }
146
+ linters:
147
+ OtherFakeConfigLinter:
148
+ enabled: false
149
+ FILE
150
+
151
+ it 'does not alter the default configuration' do
152
+ subject.options['linters']['OtherFakeConfigLinter']
153
+ .should == { 'enabled' => false }
154
+ end
155
+
156
+ it 'reflects the different setting of the file that included the file' do
157
+ subject.options['linters']['FakeConfigLinter']
158
+ .should == { 'enabled' => true, 'some_other_option' => 'some_other_value' }
159
+ end
160
+ end
161
+
162
+ context 'and the included file includes another file' do
163
+ let(:other_included_file_path) { '/some/abs/other_included_file.yml' }
164
+
165
+ let(:other_included_file) { <<-FILE }
166
+ linters:
167
+ OtherFakeConfigLinter:
168
+ yet_another_option: yet_another_value
169
+ FILE
170
+
171
+ let(:included_file) { <<-FILE }
172
+ inherit_from: #{other_included_file_path}
173
+
174
+ linters:
175
+ OtherFakeConfigLinter:
176
+ enabled: true
177
+ some_option: some_value
178
+ FILE
179
+
180
+ before do
181
+ described_class.stub(:load_file_contents)
182
+ .with(other_included_file_path)
183
+ .and_return(other_included_file)
184
+ end
185
+
186
+ it "reflects the different setting of the included file's included file" do
187
+ subject.options['linters']['OtherFakeConfigLinter']
188
+ .should == {
189
+ 'enabled' => true,
190
+ 'some_option' => 'some_value',
191
+ 'yet_another_option' => 'yet_another_value',
192
+ }
193
+ end
194
+
195
+ it 'reflects the different setting of the file that included the file' do
196
+ subject.options['linters']['FakeConfigLinter']
197
+ .should == { 'enabled' => true, 'some_other_option' => 'some_other_value' }
198
+ end
199
+ end
200
+ end
201
+
202
+ context 'with a file that includes multiple configuration files' do
203
+ let(:included_file_path) { '../included_file.yml' }
204
+ let(:other_included_file_path) { '/some/dir/other_included_file.yml' }
205
+
206
+ let(:config_file) { <<-FILE }
207
+ inherit_from:
208
+ - #{included_file_path}
209
+ - #{other_included_file_path}
210
+
211
+ linters:
212
+ FakeConfigLinter:
213
+ enabled: true
214
+ some_other_option: some_other_value
215
+ FILE
216
+
217
+ before do
218
+ described_class.stub(:load_file_contents)
219
+ .with("#{config_dir}/" + included_file_path)
220
+ .and_return(included_file)
221
+
222
+ described_class.stub(:load_file_contents)
223
+ .with(other_included_file_path)
224
+ .and_return(other_included_file)
225
+ end
226
+
227
+ context 'and the included files have settings different from each other' do
228
+ let(:included_file) { <<-FILE }
229
+ linters:
230
+ OtherFakeConfigLinter:
231
+ enabled: true
232
+ some_option: earlier_value
233
+ some_other_option: value
234
+ FILE
235
+
236
+ let(:other_included_file) { <<-FILE }
237
+ linters:
238
+ OtherFakeConfigLinter:
239
+ enabled: true
240
+ some_option: later_value
241
+ FILE
242
+
243
+ it 'uses the value of the file that was included last' do
244
+ subject.options['linters']['OtherFakeConfigLinter']['some_option']
245
+ .should == 'later_value'
246
+ end
247
+
248
+ it 'loads settings from both included files' do
249
+ subject.options['linters']['OtherFakeConfigLinter']
250
+ .should == {
251
+ 'enabled' => true,
252
+ 'some_option' => 'later_value',
253
+ 'some_other_option' => 'value',
254
+ }
255
+ end
256
+ end
257
+ end
258
+
259
+ context 'when a wildcard is used for a namespaced linter' do
260
+ let(:default_file) { <<-FILE }
261
+ linters:
262
+ SomeNamespace::*:
263
+ enabled: false
264
+ FILE
265
+
266
+ let(:config_file) { <<-FILE }
267
+ linters:
268
+ SomeNamespace::*:
269
+ enabled: true
270
+ FILE
271
+
272
+ before do
273
+ SCSSLint::LinterRegistry.stub(:linters)
274
+ .and_return([SCSSLint::Linter::SomeNamespace::FakeLinter1,
275
+ SCSSLint::Linter::SomeNamespace::FakeLinter2])
276
+ end
277
+
278
+ it 'returns the same options for all linters under that namespace' do
279
+ subject.linter_options(SCSSLint::Linter::SomeNamespace::FakeLinter1)
280
+ .should eq('enabled' => true)
281
+ subject.linter_options(SCSSLint::Linter::SomeNamespace::FakeLinter2)
282
+ .should eq('enabled' => true)
283
+ end
284
+ end
285
+ end
286
+
287
+ describe '.for_file' do
288
+ include_context 'isolated environment'
289
+
290
+ let(:linted_file) { File.join('foo', 'bar', 'baz', 'file-being-linted.scss') }
291
+ subject { described_class.for_file(linted_file) }
292
+
293
+ before do
294
+ described_class.instance_variable_set(:@dir_to_config, nil) # Clear cache
295
+ FileUtils.mkpath(File.dirname(linted_file))
296
+ FileUtils.touch(linted_file)
297
+ end
298
+
299
+ context 'when there are no configuration files in the directory hierarchy' do
300
+ it { should be_nil }
301
+ end
302
+
303
+ context 'when there is a configuration file in the same directory' do
304
+ let(:config_file) { File.join('foo', 'bar', 'baz', '.scss-lint.yml') }
305
+ before { FileUtils.touch(config_file) }
306
+
307
+ it 'loads that configuration file' do
308
+ described_class.should_receive(:load).with(File.expand_path(config_file))
309
+ subject
310
+ end
311
+ end
312
+
313
+ context 'when there is a configuration file in the parent directory' do
314
+ let(:config_file) { File.join('foo', 'bar', '.scss-lint.yml') }
315
+ before { FileUtils.touch(config_file) }
316
+
317
+ it 'loads that configuration file' do
318
+ described_class.should_receive(:load).with(File.expand_path(config_file))
319
+ subject
320
+ end
321
+ end
322
+
323
+ context 'when there is a configuration file in some ancestor directory' do
324
+ let(:config_file) { File.join('foo', '.scss-lint.yml') }
325
+ before { FileUtils.touch(config_file) }
326
+
327
+ it 'loads that configuration file' do
328
+ described_class.should_receive(:load).with(File.expand_path(config_file))
329
+ subject
330
+ end
331
+ end
332
+ end
333
+
334
+ describe '#linter_options' do
335
+ let(:config) { described_class.new(options) }
336
+
337
+ let(:linter_options) do
338
+ {
339
+ 'enabled' => true,
340
+ 'some_option' => 'some_value',
341
+ }
342
+ end
343
+
344
+ let(:options) do
345
+ {
346
+ 'linters' => {
347
+ 'FakeConfigLinter' => linter_options
348
+ }
349
+ }
350
+ end
351
+
352
+ it 'returns the options for the specified linter' do
353
+ config.linter_options(SCSSLint::Linter::FakeConfigLinter.new)
354
+ .should == linter_options
355
+ end
356
+ end
357
+
358
+ describe '#excluded_file?' do
359
+ include_context 'isolated environment'
360
+
361
+ let(:config_dir) { 'path/to' }
362
+ let(:file_name) { "#{config_dir}/config.yml" }
363
+ let(:config) { described_class.load(file_name) }
364
+
365
+ before do
366
+ described_class.stub(:load_file_contents)
367
+ .with(file_name)
368
+ .and_return(config_file)
369
+ end
370
+
371
+ context 'when no exclusion is specified' do
372
+ let(:config_file) { 'linters: {}' }
373
+
374
+ it 'does not exclude any files' do
375
+ config.excluded_file?('anything/you/want.scss').should be false
376
+ end
377
+ end
378
+
379
+ context 'when an exclusion is specified' do
380
+ let(:config_file) { "exclude: 'foo/bar/baz/**'" }
381
+
382
+ it 'does not exclude anything not matching the glob' do
383
+ config.excluded_file?("#{config_dir}/foo/bar/something.scss").should be false
384
+ config.excluded_file?("#{config_dir}/other/something.scss").should be false
385
+ end
386
+
387
+ it 'excludes anything matching the glob' do
388
+ config.excluded_file?("#{config_dir}/foo/bar/baz/excluded.scss").should be true
389
+ config.excluded_file?("#{config_dir}/foo/bar/baz/dir/excluded.scss").should be true
390
+ end
391
+ end
392
+ end
393
+
394
+ describe '#excluded_file_for_linter?' do
395
+ include_context 'isolated environment'
396
+
397
+ let(:config_dir) { 'path/to' }
398
+ let(:file_name) { "#{config_dir}/config.yml" }
399
+ let(:config) { described_class.load(file_name) }
400
+
401
+ before do
402
+ described_class.stub(:load_file_contents)
403
+ .with(file_name)
404
+ .and_return(config_file)
405
+ end
406
+
407
+ context 'when no exclusion is specified in linter' do
408
+ let(:config_file) { <<-FILE }
409
+ linters:
410
+ FakeConfigLinter:
411
+ enabled: true
412
+ FILE
413
+
414
+ it 'does not exclude any files' do
415
+ config.excluded_file_for_linter?(
416
+ "#{config_dir}/anything/you/want.scss",
417
+ SCSSLint::Linter::FakeConfigLinter.new
418
+ ).should == false
419
+ end
420
+ end
421
+
422
+ context 'when an exclusion is specified in linter' do
423
+ let(:config_file) { <<-FILE }
424
+ linters:
425
+ FakeConfigLinter:
426
+ enabled: true
427
+ exclude:
428
+ - 'anything/you/want.scss'
429
+ FILE
430
+
431
+ it 'excludes file for the linter' do
432
+ config.excluded_file_for_linter?(
433
+ "#{config_dir}/anything/you/want.scss",
434
+ SCSSLint::Linter::FakeConfigLinter.new
435
+ ).should == true
436
+ end
437
+ end
438
+ end
439
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe SCSSLint::Engine do
4
+ let(:engine) { described_class.new(css) }
5
+
6
+ context 'when a @media directive is present' do
7
+ let(:css) { <<-CSS }
8
+ @media only screen {
9
+ }
10
+ CSS
11
+
12
+ it 'has a parse tree' do
13
+ engine.tree.should_not be_nil
14
+ end
15
+ end
16
+
17
+ context 'when the file being linted has an invalid byte sequence' do
18
+ let(:css) { "\xC0\u0001" }
19
+
20
+ it 'raises a SyntaxError' do
21
+ expect { engine }.to raise_error(SCSSLint::FileEncodingError)
22
+ end
23
+ end
24
+ end