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,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe SCSSLint::LinterRegistry do
4
+ context 'when including the LinterRegistry module' do
5
+ after do
6
+ described_class.linters.delete(FakeLinter)
7
+ end
8
+
9
+ it 'adds the linter to the set of registered linters' do
10
+ expect do
11
+ class FakeLinter < SCSSLint::Linter
12
+ include SCSSLint::LinterRegistry
13
+ end
14
+ end.to change { described_class.linters.count }.by(1)
15
+ end
16
+ end
17
+
18
+ describe '.extract_linters_from' do
19
+ module SCSSLint
20
+ class Linter::SomeLinter < Linter; end
21
+ class Linter::SomeOtherLinter < Linter; end
22
+ end
23
+
24
+ let(:linters) do
25
+ [SCSSLint::Linter::SomeLinter, SCSSLint::Linter::SomeOtherLinter]
26
+ end
27
+
28
+ before do
29
+ described_class.stub(:linters).and_return(linters)
30
+ end
31
+
32
+ context 'when the linters exist' do
33
+ let(:linter_names) { %w[SomeLinter SomeOtherLinter] }
34
+
35
+ it 'returns the linters' do
36
+ subject.extract_linters_from(linter_names).should == linters
37
+ end
38
+ end
39
+
40
+ context "when the linters don't exist" do
41
+ let(:linter_names) { ['SomeRandomLinter'] }
42
+
43
+ it 'raises an error' do
44
+ expect do
45
+ subject.extract_linters_from(linter_names)
46
+ end.to raise_error(SCSSLint::NoSuchLinter)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe SCSSLint::Location do
4
+ let(:engine) { described_class.new(css) }
5
+
6
+ describe '#<=>' do
7
+ let(:locations) do
8
+ [
9
+ SCSSLint::Location.new(2, 2, 2),
10
+ SCSSLint::Location.new(2, 2, 1),
11
+ SCSSLint::Location.new(2, 1, 2),
12
+ SCSSLint::Location.new(2, 1, 1),
13
+ SCSSLint::Location.new(1, 2, 2),
14
+ SCSSLint::Location.new(1, 2, 1),
15
+ SCSSLint::Location.new(1, 1, 2),
16
+ SCSSLint::Location.new(1, 1, 1)
17
+ ]
18
+ end
19
+
20
+ it 'allows locations to be sorted' do
21
+ locations.sort.should == [
22
+ SCSSLint::Location.new(1, 1, 1),
23
+ SCSSLint::Location.new(1, 1, 2),
24
+ SCSSLint::Location.new(1, 2, 1),
25
+ SCSSLint::Location.new(1, 2, 2),
26
+ SCSSLint::Location.new(2, 1, 1),
27
+ SCSSLint::Location.new(2, 1, 2),
28
+ SCSSLint::Location.new(2, 2, 1),
29
+ SCSSLint::Location.new(2, 2, 2)
30
+ ]
31
+ end
32
+
33
+ context 'when the same location is passed' do
34
+ let(:location) { SCSSLint::Location.new(1, 1, 1) }
35
+ let(:other_location) { SCSSLint::Location.new(1, 1, 1) }
36
+
37
+ it 'returns 0' do
38
+ (location <=> other_location).should == 0
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe SCSSLint::Reporter::ConfigReporter do
4
+ subject { YAML.load(result) }
5
+ let(:result) { described_class.new(lints).report_lints }
6
+
7
+ describe '#report_lints' do
8
+ context 'when there are no lints' do
9
+ let(:lints) { [] }
10
+
11
+ it 'returns nil' do
12
+ result.should be_nil
13
+ end
14
+ end
15
+
16
+ context 'when there are lints' do
17
+ let(:linters) do
18
+ [SCSSLint::Linter::FinalNewline, SCSSLint::Linter::BorderZero,
19
+ SCSSLint::Linter::BorderZero, nil]
20
+ end
21
+ let(:lints) do
22
+ linters.each.map do |linter|
23
+ SCSSLint::Lint.new(linter ? linter.new : nil, '',
24
+ SCSSLint::Location.new, '')
25
+ end
26
+ end
27
+
28
+ it 'adds one entry per linter' do
29
+ subject['linters'].size.should eq 2
30
+ end
31
+
32
+ it 'sorts linters by name' do
33
+ subject['linters'].map(&:first).should eq %w[BorderZero FinalNewline]
34
+ end
35
+
36
+ it 'disables all found linters' do
37
+ subject['linters']['BorderZero']['enabled'].should eq false
38
+ subject['linters']['FinalNewline']['enabled'].should eq false
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe SCSSLint::Reporter::DefaultReporter do
4
+ subject { SCSSLint::Reporter::DefaultReporter.new(lints) }
5
+
6
+ describe '#report_lints' do
7
+ context 'when there are no lints' do
8
+ let(:lints) { [] }
9
+
10
+ it 'returns nil' do
11
+ subject.report_lints.should be_nil
12
+ end
13
+ end
14
+
15
+ context 'when there are lints' do
16
+ let(:filenames) { ['some-filename.scss', 'other-filename.scss'] }
17
+ let(:lines) { [502, 724] }
18
+ let(:descriptions) { ['Description of lint 1', 'Description of lint 2'] }
19
+ let(:severities) { [:warning] * 2 }
20
+ let(:lints) do
21
+ filenames.each_with_index.map do |filename, index|
22
+ location = SCSSLint::Location.new(lines[index])
23
+ SCSSLint::Lint.new(nil, filename, location, descriptions[index],
24
+ severities[index])
25
+ end
26
+ end
27
+
28
+ it 'prints each lint on its own line' do
29
+ subject.report_lints.count("\n").should == 2
30
+ end
31
+
32
+ it 'prints a trailing newline' do
33
+ subject.report_lints[-1].should == "\n"
34
+ end
35
+
36
+ it 'prints the filename for each lint' do
37
+ filenames.each do |filename|
38
+ subject.report_lints.scan(/#{filename}/).count.should == 1
39
+ end
40
+ end
41
+
42
+ it 'prints the line number for each lint' do
43
+ lines.each do |line|
44
+ subject.report_lints.scan(/#{line}/).count.should == 1
45
+ end
46
+ end
47
+
48
+ it 'prints the description for each lint' do
49
+ descriptions.each do |description|
50
+ subject.report_lints.scan(/#{description}/).count.should == 1
51
+ end
52
+ end
53
+
54
+ context 'when lints are warnings' do
55
+ it 'prints the warning severity code on each line' do
56
+ subject.report_lints.split("\n").each do |line|
57
+ line.scan(/\[W\]/).count.should == 1
58
+ end
59
+ end
60
+ end
61
+
62
+ context 'when lints are errors' do
63
+ let(:severities) { [:error] * 2 }
64
+
65
+ it 'prints the error severity code on each line' do
66
+ subject.report_lints.split("\n").each do |line|
67
+ line.scan(/\[E\]/).count.should == 1
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe SCSSLint::Reporter::FilesReporter do
4
+ subject { described_class.new(lints) }
5
+
6
+ describe '#report_lints' do
7
+ context 'when there are no lints' do
8
+ let(:lints) { [] }
9
+
10
+ it 'returns nil' do
11
+ subject.report_lints.should be_nil
12
+ end
13
+ end
14
+
15
+ context 'when there are lints' do
16
+ let(:filenames) { ['some-filename.scss', 'some-filename.scss', 'other-filename.scss'] }
17
+ let(:lints) do
18
+ filenames.map do |filename|
19
+ SCSSLint::Lint.new(nil, filename, SCSSLint::Location.new, '')
20
+ end
21
+ end
22
+
23
+ it 'prints each file on its own line' do
24
+ subject.report_lints.count("\n").should == 2
25
+ end
26
+
27
+ it 'prints a trailing newline' do
28
+ subject.report_lints[-1].should == "\n"
29
+ end
30
+
31
+ it 'prints the filename for each lint' do
32
+ filenames.each do |filename|
33
+ subject.report_lints.scan(/#{filename}/).count.should == 1
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe SCSSLint::Reporter::XMLReporter do
4
+ subject { SCSSLint::Reporter::XMLReporter.new(lints) }
5
+
6
+ describe '#report_lints' do
7
+ let(:xml) { Nokogiri::XML(subject.report_lints) }
8
+
9
+ shared_examples_for 'XML document' do
10
+ it 'has an encoding of UTF-8' do
11
+ xml.encoding.should == 'utf-8'
12
+ end
13
+
14
+ it 'has an XML version of 1.0' do
15
+ xml.version.should == '1.0'
16
+ end
17
+
18
+ it 'contains a <lint> root element' do
19
+ xml.root.name.should == 'lint'
20
+ end
21
+ end
22
+
23
+ context 'when there are no lints' do
24
+ let(:lints) { [] }
25
+
26
+ it_should_behave_like 'XML document'
27
+ end
28
+
29
+ context 'when there are lints' do
30
+ let(:filenames) { ['f1.scss', 'f2.scss', 'f1.scss'] }
31
+ # Include invalid XML characters in the third description to validate
32
+ # that escaping happens for preventing broken XML output
33
+ let(:descriptions) { ['lint 1', 'lint 2', 'lint 3 " \' < & >'] }
34
+ let(:severities) { [:warning] * 3 }
35
+
36
+ let(:locations) do
37
+ [
38
+ SCSSLint::Location.new(5, 2, 3),
39
+ SCSSLint::Location.new(7, 6, 2),
40
+ SCSSLint::Location.new(9, 10, 1)
41
+ ]
42
+ end
43
+
44
+ let(:lints) do
45
+ filenames.each_with_index.map do |filename, index|
46
+ SCSSLint::Lint.new(nil, filename, locations[index],
47
+ descriptions[index], severities[index])
48
+ end
49
+ end
50
+
51
+ it_should_behave_like 'XML document'
52
+
53
+ it 'contains an <issue> node for each lint' do
54
+ xml.xpath('//issue').count.should == 3
55
+ end
56
+
57
+ it 'contains a <file> node for each file' do
58
+ xml.xpath('//file').map { |node| node[:name] }
59
+ .should =~ filenames.uniq
60
+ end
61
+
62
+ it 'contains <issue> nodes grouped by <file>' do
63
+ xml.xpath('//file').map do |file_node|
64
+ file_node.xpath('./issue').count
65
+ end.should =~ [1, 2]
66
+ end
67
+
68
+ it 'marks each issue with a line number' do
69
+ xml.xpath('//issue[@line]').map { |node| node[:line] }
70
+ .should =~ locations.map { |location| location.line.to_s }
71
+ end
72
+
73
+ it 'marks each issue with a column number' do
74
+ xml.xpath('//issue[@column]').map { |node| node[:column] }
75
+ .should =~ locations.map { |location| location.column.to_s }
76
+ end
77
+
78
+ it 'marks each issue with a length' do
79
+ xml.xpath('//issue[@length]').map { |node| node[:length] }
80
+ .should =~ locations.map { |location| location.length.to_s }
81
+ end
82
+
83
+ it 'marks each issue with a reason containing the lint description' do
84
+ xml.xpath('//issue[@reason]').map { |node| node[:reason] }
85
+ .should =~ descriptions
86
+ end
87
+
88
+ context 'when lints are warnings' do
89
+ it 'marks each issue with a severity of "warning"' do
90
+ xml.xpath("//issue[@severity='warning']").count == 3
91
+ end
92
+ end
93
+
94
+ context 'when lints are errors' do
95
+ let(:severities) { [:error] * 3 }
96
+
97
+ it 'marks each issue with a severity of "error"' do
98
+ xml.xpath("//issue[@severity='error']").count == 3
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe SCSSLint::Reporter do
4
+ class SCSSLint::Reporter::FakeReporter < SCSSLint::Reporter; end
5
+
6
+ describe '#descendants' do
7
+ it 'contains FakeReporter' do
8
+ SCSSLint::Reporter.descendants.should include(SCSSLint::Reporter::FakeReporter)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe SCSSLint::Runner do
4
+ let(:config_options) do
5
+ {
6
+ 'linters' => {
7
+ 'FakeLinter1' => { 'enabled' => true },
8
+ 'FakeLinter2' => { 'enabled' => false },
9
+ },
10
+ }
11
+ end
12
+
13
+ let(:config) { SCSSLint::Config.new(config_options) }
14
+ let(:runner) { described_class.new(config) }
15
+
16
+ before do
17
+ SCSSLint::LinterRegistry.stub(:linters)
18
+ .and_return([SCSSLint::Linter::FakeLinter1,
19
+ SCSSLint::Linter::FakeLinter2])
20
+ SCSSLint::Config.stub(:for_file)
21
+ end
22
+
23
+ class SCSSLint::Linter::FakeLinter1 < SCSSLint::Linter; end
24
+ class SCSSLint::Linter::FakeLinter2 < SCSSLint::Linter; end
25
+
26
+ describe '#run' do
27
+ let(:files) { ['dummy1.scss', 'dummy2.scss'] }
28
+ subject { runner.run(files) }
29
+
30
+ before do
31
+ SCSSLint::Engine.stub(:new)
32
+ SCSSLint::Linter.any_instance.stub(:run)
33
+ end
34
+
35
+ it 'searches for lints in each file' do
36
+ runner.should_receive(:find_lints).exactly(files.size).times
37
+ subject
38
+ end
39
+
40
+ context 'when no files are given' do
41
+ let(:files) { [] }
42
+
43
+ it 'raises an error' do
44
+ expect { subject }.to raise_error SCSSLint::NoFilesError
45
+ end
46
+ end
47
+
48
+ context 'when all linters are disabled' do
49
+ let(:config_options) do
50
+ {
51
+ 'linters' => {
52
+ 'FakeLinter1' => { 'enabled' => false },
53
+ 'FakeLinter2' => { 'enabled' => false },
54
+ },
55
+ }
56
+ end
57
+
58
+ before do
59
+ SCSSLint::Linter.any_instance
60
+ .stub(:run)
61
+ .and_raise(RuntimeError.new('Linter#run was called'))
62
+ end
63
+
64
+ it 'never runs a linter' do
65
+ expect { subject }.to_not raise_error
66
+ end
67
+ end
68
+
69
+ context 'when the engine raises a FileEncodingError' do
70
+ let(:error) do
71
+ SCSSLint::FileEncodingError.new('Some error message')
72
+ end
73
+
74
+ before do
75
+ SCSSLint::Engine.stub(:new).with(files.last).and_raise(error)
76
+ end
77
+
78
+ it 'records the error as a lint' do
79
+ expect { subject }.to change { runner.lints.count }.by(1)
80
+ end
81
+ end
82
+
83
+ context 'when files ere excluded for one linter' do
84
+ let(:config_options) do
85
+ {
86
+ 'linters' => {
87
+ 'FakeLinter1' => { 'enabled' => true,
88
+ 'exclude' => [File.expand_path('dummy1.scss'),
89
+ File.expand_path('dummy2.scss')] },
90
+ 'FakeLinter2' => { 'enabled' => false },
91
+ },
92
+ }
93
+ end
94
+
95
+ before do
96
+ SCSSLint::Linter::FakeLinter1.any_instance
97
+ .stub(:run)
98
+ .and_raise(RuntimeError.new('FakeLinter1#run was called'))
99
+ end
100
+
101
+ it 'does not run the linter for the disabled files' do
102
+ expect { subject }.to_not raise_error
103
+ end
104
+ end
105
+
106
+ context 'when a linter raises an error' do
107
+ let(:backtrace) { %w[file.rb:1 file.rb:2] }
108
+
109
+ let(:error) do
110
+ StandardError.new('Some error message').tap do |e|
111
+ e.set_backtrace(backtrace)
112
+ end
113
+ end
114
+
115
+ before do
116
+ runner.stub(:run_linter).and_raise(error)
117
+ end
118
+
119
+ it 'raises a LinterError' do
120
+ expect { subject }.to raise_error(SCSSLint::Exceptions::LinterError)
121
+ end
122
+
123
+ it 'has the name of the file the linter was checking' do
124
+ expect { subject }.to raise_error { |e| e.message.should include files.first }
125
+ end
126
+
127
+ it 'has the same backtrace as the original error' do
128
+ expect { subject }.to raise_error { |e| e.backtrace.should == backtrace }
129
+ end
130
+ end
131
+ end
132
+ end