scss-lint 0.25.1 → 0.26.0

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