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.
- checksums.yaml +4 -4
- data/config/default.yml +10 -1
- data/data/property-sort-orders/concentric.txt +99 -0
- data/lib/scss_lint.rb +1 -0
- data/lib/scss_lint/cli.rb +9 -3
- data/lib/scss_lint/exceptions.rb +4 -0
- data/lib/scss_lint/linter.rb +10 -1
- data/lib/scss_lint/linter/capitalization_in_selector.rb +14 -6
- data/lib/scss_lint/linter/compass/property_with_mixin.rb +9 -2
- data/lib/scss_lint/linter/indentation.rb +28 -6
- data/lib/scss_lint/linter/property_sort_order.rb +61 -9
- data/lib/scss_lint/linter/single_line_per_property.rb +53 -0
- data/lib/scss_lint/linter/single_line_per_selector.rb +6 -1
- data/lib/scss_lint/linter/space_after_comma.rb +27 -19
- data/lib/scss_lint/linter/space_before_brace.rb +5 -4
- data/lib/scss_lint/linter/trailing_semicolon.rb +53 -0
- data/lib/scss_lint/linter/unnecessary_parent_reference.rb +36 -0
- data/lib/scss_lint/reporter/default_reporter.rb +7 -2
- data/lib/scss_lint/reporter/xml_reporter.rb +2 -1
- data/lib/scss_lint/runner.rb +7 -3
- data/lib/scss_lint/version.rb +1 -1
- data/spec/scss_lint/cli_spec.rb +314 -0
- data/spec/scss_lint/config_spec.rb +439 -0
- data/spec/scss_lint/engine_spec.rb +24 -0
- data/spec/scss_lint/linter/border_zero_spec.rb +84 -0
- data/spec/scss_lint/linter/capitalization_in_selector_spec.rb +71 -0
- data/spec/scss_lint/linter/color_keyword_spec.rb +83 -0
- data/spec/scss_lint/linter/comment_spec.rb +55 -0
- data/spec/scss_lint/linter/compass/property_with_mixin_spec.rb +55 -0
- data/spec/scss_lint/linter/debug_statement_spec.rb +21 -0
- data/spec/scss_lint/linter/declaration_order_spec.rb +94 -0
- data/spec/scss_lint/linter/duplicate_property_spec.rb +176 -0
- data/spec/scss_lint/linter/else_placement_spec.rb +106 -0
- data/spec/scss_lint/linter/empty_line_between_blocks_spec.rb +263 -0
- data/spec/scss_lint/linter/empty_rule_spec.rb +27 -0
- data/spec/scss_lint/linter/final_newline_spec.rb +49 -0
- data/spec/scss_lint/linter/hex_length_spec.rb +104 -0
- data/spec/scss_lint/linter/hex_notation_spec.rb +104 -0
- data/spec/scss_lint/linter/hex_validation_spec.rb +36 -0
- data/spec/scss_lint/linter/id_with_extraneous_selector_spec.rb +139 -0
- data/spec/scss_lint/linter/indentation_spec.rb +242 -0
- data/spec/scss_lint/linter/leading_zero_spec.rb +233 -0
- data/spec/scss_lint/linter/mergeable_selector_spec.rb +283 -0
- data/spec/scss_lint/linter/name_format_spec.rb +206 -0
- data/spec/scss_lint/linter/placeholder_in_extend_spec.rb +63 -0
- data/spec/scss_lint/linter/property_sort_order_spec.rb +246 -0
- data/spec/scss_lint/linter/property_spelling_spec.rb +57 -0
- data/spec/scss_lint/linter/selector_depth_spec.rb +159 -0
- data/spec/scss_lint/linter/shorthand_spec.rb +172 -0
- data/spec/scss_lint/linter/single_line_per_property_spec.rb +73 -0
- data/spec/scss_lint/linter/single_line_per_selector_spec.rb +121 -0
- data/spec/scss_lint/linter/space_after_comma_spec.rb +315 -0
- data/spec/scss_lint/linter/space_after_property_colon_spec.rb +238 -0
- data/spec/scss_lint/linter/space_after_property_name_spec.rb +23 -0
- data/spec/scss_lint/linter/space_before_brace_spec.rb +447 -0
- data/spec/scss_lint/linter/space_between_parens_spec.rb +263 -0
- data/spec/scss_lint/linter/string_quotes_spec.rb +303 -0
- data/spec/scss_lint/linter/trailing_semicolon_spec.rb +188 -0
- data/spec/scss_lint/linter/unnecessary_mantissa_spec.rb +67 -0
- data/spec/scss_lint/linter/unnecessary_parent_reference_spec.rb +67 -0
- data/spec/scss_lint/linter/url_format_spec.rb +55 -0
- data/spec/scss_lint/linter/url_quotes_spec.rb +63 -0
- data/spec/scss_lint/linter/zero_unit_spec.rb +113 -0
- data/spec/scss_lint/linter_registry_spec.rb +50 -0
- data/spec/scss_lint/location_spec.rb +42 -0
- data/spec/scss_lint/reporter/config_reporter_spec.rb +42 -0
- data/spec/scss_lint/reporter/default_reporter_spec.rb +73 -0
- data/spec/scss_lint/reporter/files_reporter_spec.rb +38 -0
- data/spec/scss_lint/reporter/xml_reporter_spec.rb +103 -0
- data/spec/scss_lint/reporter_spec.rb +11 -0
- data/spec/scss_lint/runner_spec.rb +132 -0
- data/spec/scss_lint/selector_visitor_spec.rb +264 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/isolated_environment.rb +25 -0
- data/spec/support/matchers/report_lint.rb +48 -0
- metadata +126 -8
- data/lib/scss_lint/linter/trailing_semicolon_after_property_value.rb +0 -40
@@ -0,0 +1,53 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks that all properties in a rule set are on their own distinct lines.
|
3
|
+
class Linter::SingleLinePerProperty < Linter
|
4
|
+
include LinterRegistry
|
5
|
+
|
6
|
+
def visit_rule(node) # rubocop:disable CyclomaticComplexity
|
7
|
+
single_line = single_line_rule_set?(node)
|
8
|
+
return if single_line && config['allow_single_line_rule_sets']
|
9
|
+
|
10
|
+
properties = node.children.select { |child| child.is_a?(Sass::Tree::PropNode) }
|
11
|
+
return unless properties.any?
|
12
|
+
|
13
|
+
# Special case: if single line rule sets aren't allowed, we want to report
|
14
|
+
# when the first property isn't on a separate line from the selector
|
15
|
+
if single_line && !config['allow_single_line_rule_sets']
|
16
|
+
add_lint(properties.first,
|
17
|
+
"Property '#{properties.first.name.join}' should be placed " \
|
18
|
+
'on separate line from selector')
|
19
|
+
end
|
20
|
+
|
21
|
+
# Compare each property against the next property to see if they are on
|
22
|
+
# the same line
|
23
|
+
properties[0..-2].zip(properties[1..-1]).each do |first, second|
|
24
|
+
next unless first.line == second.line
|
25
|
+
|
26
|
+
add_lint(second, "Property '#{second.name.join}' should be placed on own line")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# Return whether this rule set occupies a single line.
|
33
|
+
#
|
34
|
+
# Note that this allows:
|
35
|
+
# a,
|
36
|
+
# b,
|
37
|
+
# i { margin: 0; padding: 0; }
|
38
|
+
#
|
39
|
+
# and:
|
40
|
+
#
|
41
|
+
# p { margin: 0; padding: 0; }
|
42
|
+
#
|
43
|
+
# In other words, the line of the opening curly brace is the line that the
|
44
|
+
# rule set is considered to occupy.
|
45
|
+
def single_line_rule_set?(rule)
|
46
|
+
rule.children.all? { |child| child.line == rule.source_range.end_pos.line }
|
47
|
+
end
|
48
|
+
|
49
|
+
def first_property_not_on_own_line?(rule, properties)
|
50
|
+
properties.any? && properties.first.line == rule.line
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -14,6 +14,11 @@ module SCSSLint
|
|
14
14
|
|
15
15
|
# A comma is invalid if it starts the line or is not the end of the line
|
16
16
|
def invalid_comma_placement?(node)
|
17
|
+
# We must ignore selectors with interpolation, since there's no way to
|
18
|
+
# tell if the overall selector is valid since the interpolation could
|
19
|
+
# insert commas incorrectly. Thus we simply ignore.
|
20
|
+
return if node.rule.any? { |item| item.is_a?(Sass::Script::Variable) }
|
21
|
+
|
17
22
|
normalize_spacing(condense_to_string(node.rule)) =~ /\n,|,[^\n]/
|
18
23
|
end
|
19
24
|
|
@@ -21,7 +26,7 @@ module SCSSLint
|
|
21
26
|
# Sass::Script::Nodes, we need to condense it into a single string that we
|
22
27
|
# can run a regex against.
|
23
28
|
def condense_to_string(sequence_list)
|
24
|
-
sequence_list.select { |item| item.is_a?(String) }.inject(:+)
|
29
|
+
sequence_list.select { |item| item.is_a?(String) }.inject(:+)
|
25
30
|
end
|
26
31
|
|
27
32
|
# Removes extra spacing between lines in a comma-separated sequence due to
|
@@ -66,34 +66,42 @@ module SCSSLint
|
|
66
66
|
def check_commas_after_args(args, arg_type)
|
67
67
|
# For each arg except the last, check the character following the comma
|
68
68
|
args[0..-2].each do |arg|
|
69
|
-
offset =
|
70
|
-
|
71
|
-
# Find the comma following this argument.
|
72
|
-
# The Sass parser is unpredictable in where it marks the end of the
|
73
|
-
# source range. Thus we need to start at the indicated range, and check
|
74
|
-
# left and right of that range, gradually moving further outward until
|
75
|
-
# we find the comma.
|
76
|
-
if character_at(arg.source_range.end_pos, offset) != ','
|
77
|
-
loop do
|
78
|
-
offset += 1
|
79
|
-
break if character_at(arg.source_range.end_pos, offset) == ','
|
80
|
-
offset = -offset
|
81
|
-
break if character_at(arg.source_range.end_pos, offset) == ','
|
82
|
-
offset = -offset
|
83
|
-
end
|
84
|
-
end
|
69
|
+
offset = find_comma_offset(arg)
|
85
70
|
|
86
|
-
# Check for space or newline after
|
71
|
+
# Check for space or newline after comma (we allow arguments to be split
|
87
72
|
# up over multiple lines).
|
88
73
|
spaces = 0
|
89
|
-
while character_at(arg.source_range.end_pos, offset + 1)
|
74
|
+
while (char = character_at(arg.source_range.end_pos, offset + 1)) == ' '
|
90
75
|
spaces += 1
|
91
76
|
offset += 1
|
92
77
|
end
|
93
|
-
next if
|
78
|
+
next if char == "\n" || # Ignore trailing spaces
|
79
|
+
spaces == EXPECTED_SPACES_AFTER_COMMA
|
94
80
|
|
95
81
|
add_lint arg, "Commas in #{arg_type} should be followed by a single space"
|
96
82
|
end
|
97
83
|
end
|
84
|
+
|
85
|
+
# Find the comma following this argument.
|
86
|
+
#
|
87
|
+
# The Sass parser is unpredictable in where it marks the end of the
|
88
|
+
# source range. Thus we need to start at the indicated range, and check
|
89
|
+
# left and right of that range, gradually moving further outward until
|
90
|
+
# we find the comma.
|
91
|
+
def find_comma_offset(arg)
|
92
|
+
offset = 0
|
93
|
+
|
94
|
+
if character_at(arg.source_range.end_pos, offset) != ','
|
95
|
+
loop do
|
96
|
+
offset += 1
|
97
|
+
break if character_at(arg.source_range.end_pos, offset) == ','
|
98
|
+
offset = -offset
|
99
|
+
break if character_at(arg.source_range.end_pos, offset) == ','
|
100
|
+
offset = -offset
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
offset
|
105
|
+
end
|
98
106
|
end
|
99
107
|
end
|
@@ -27,16 +27,17 @@ module SCSSLint
|
|
27
27
|
|
28
28
|
def check_for_space(node, string)
|
29
29
|
line = node.source_range.end_pos.line
|
30
|
+
char_before_is_whitespace = ["\n", ' '].include?(string[-2])
|
30
31
|
|
31
32
|
if config['allow_single_line_padding'] && node_on_single_line?(node)
|
32
|
-
|
33
|
+
unless char_before_is_whitespace
|
33
34
|
add_lint(line, 'Opening curly brace `{` should be ' \
|
34
|
-
|
35
|
+
'preceded by at least one space')
|
35
36
|
end
|
36
37
|
else
|
37
|
-
if
|
38
|
+
if !char_before_is_whitespace || string[-3] == ' '
|
38
39
|
add_lint(line, 'Opening curly brace `{` should be ' \
|
39
|
-
|
40
|
+
'preceded by one space')
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks for a trailing semicolon on statements within rule sets.
|
3
|
+
class Linter::TrailingSemicolon < Linter
|
4
|
+
include LinterRegistry
|
5
|
+
|
6
|
+
def visit_prop(node)
|
7
|
+
if has_nested_properties?(node)
|
8
|
+
yield # Continue checking children
|
9
|
+
else
|
10
|
+
check_semicolon(node)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_extend(node)
|
15
|
+
check_semicolon(node)
|
16
|
+
end
|
17
|
+
|
18
|
+
def visit_mixin(node)
|
19
|
+
check_semicolon(node)
|
20
|
+
end
|
21
|
+
|
22
|
+
def visit_variable(node)
|
23
|
+
check_semicolon(node)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def has_nested_properties?(node)
|
29
|
+
node.children.any? { |n| n.is_a?(Sass::Tree::PropNode) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def check_semicolon(node)
|
33
|
+
if has_space_before_semicolon?(node)
|
34
|
+
line = node.source_range.start_pos.line
|
35
|
+
add_lint line, 'Declaration should be terminated by a semicolon'
|
36
|
+
elsif !ends_with_semicolon?(node)
|
37
|
+
line = node.source_range.start_pos.line
|
38
|
+
add_lint line,
|
39
|
+
'Declaration should not have a space before ' \
|
40
|
+
'the terminating semicolon'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Checks that the node is ended by a semicolon (with no whitespace)
|
45
|
+
def ends_with_semicolon?(node)
|
46
|
+
source_from_range(node.source_range) =~ /;$/
|
47
|
+
end
|
48
|
+
|
49
|
+
def has_space_before_semicolon?(node)
|
50
|
+
source_from_range(node.source_range) =~ /\s;$/
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks for unnecessary uses of the parent reference (&) in nested selectors.
|
3
|
+
class Linter::UnnecessaryParentReference < Linter
|
4
|
+
include LinterRegistry
|
5
|
+
|
6
|
+
MESSAGE = 'Unnecessary parent selector'
|
7
|
+
|
8
|
+
def visit_comma_sequence(comma_sequence)
|
9
|
+
@multiple_sequences = comma_sequence.members.size > 1
|
10
|
+
end
|
11
|
+
|
12
|
+
def visit_sequence(sequence)
|
13
|
+
return unless sequence_starts_with_parent?(sequence.members.first)
|
14
|
+
|
15
|
+
# Special case: allow an isolated parent to appear if it is part of a
|
16
|
+
# comma sequence of more than one sequence, as this could be used to DRY
|
17
|
+
# up code.
|
18
|
+
return if @multiple_sequences && isolated_parent?(sequence)
|
19
|
+
|
20
|
+
add_lint(sequence.members.first.line, MESSAGE)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def isolated_parent?(sequence)
|
26
|
+
sequence.members.size == 1 &&
|
27
|
+
sequence_starts_with_parent?(sequence.members.first)
|
28
|
+
end
|
29
|
+
|
30
|
+
def sequence_starts_with_parent?(simple_sequence)
|
31
|
+
return unless simple_sequence.is_a?(Sass::Selector::SimpleSequence)
|
32
|
+
simple_sequence.members.size == 1 &&
|
33
|
+
simple_sequence.members.first.is_a?(Sass::Selector::Parent)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -6,8 +6,13 @@ module SCSSLint
|
|
6
6
|
|
7
7
|
lints.map do |lint|
|
8
8
|
type = lint.error? ? '[E]'.color(:red) : '[W]'.color(:yellow)
|
9
|
-
|
10
|
-
|
9
|
+
|
10
|
+
linter_name = "#{lint.linter.name}: ".color(:green) if lint.linter
|
11
|
+
message = "#{linter_name}#{lint.description}"
|
12
|
+
|
13
|
+
"#{lint.filename.color(:cyan)}:" <<
|
14
|
+
"#{lint.location.line}".color(:magenta) <<
|
15
|
+
" #{type} #{message}"
|
11
16
|
end.join("\n") + "\n"
|
12
17
|
end
|
13
18
|
end
|
@@ -9,7 +9,8 @@ module SCSSLint
|
|
9
9
|
output << "<file name=#{filename.encode(xml: :attr)}>"
|
10
10
|
|
11
11
|
file_lints.each do |lint|
|
12
|
-
output << "<issue
|
12
|
+
output << "<issue linter=\"#{lint.linter.name if lint.linter}\" " \
|
13
|
+
"line=\"#{lint.location.line}\" " \
|
13
14
|
"column=\"#{lint.location.column}\" " \
|
14
15
|
"length=\"#{lint.location.length}\" " \
|
15
16
|
"severity=\"#{lint.severity}\" " \
|
data/lib/scss_lint/runner.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module SCSSLint
|
2
|
-
class LinterError < StandardError; end
|
3
2
|
class NoFilesError < StandardError; end
|
4
3
|
|
5
4
|
# Finds and aggregates all lints found by running the registered linters
|
@@ -40,9 +39,9 @@ module SCSSLint
|
|
40
39
|
next if config.excluded_file_for_linter?(file, linter)
|
41
40
|
|
42
41
|
begin
|
43
|
-
linter
|
42
|
+
run_linter(linter, engine, config)
|
44
43
|
rescue => error
|
45
|
-
raise LinterError,
|
44
|
+
raise SCSSLint::Exceptions::LinterError,
|
46
45
|
"#{linter.class} raised unexpected error linting file #{file}: " \
|
47
46
|
"'#{error.message}'",
|
48
47
|
error.backtrace
|
@@ -53,5 +52,10 @@ module SCSSLint
|
|
53
52
|
rescue FileEncodingError => ex
|
54
53
|
@lints << Lint.new(nil, file, Location.new, ex.to_s, :error)
|
55
54
|
end
|
55
|
+
|
56
|
+
# For stubbing in tests.
|
57
|
+
def run_linter(linter, engine, config)
|
58
|
+
linter.run(engine, config.linter_options(linter))
|
59
|
+
end
|
56
60
|
end
|
57
61
|
end
|
data/lib/scss_lint/version.rb
CHANGED
@@ -0,0 +1,314 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'scss_lint/cli'
|
3
|
+
|
4
|
+
describe SCSSLint::CLI do
|
5
|
+
let(:config_options) do
|
6
|
+
{
|
7
|
+
'linters' => {
|
8
|
+
'FakeTestLinter1' => { 'enabled' => true },
|
9
|
+
'FakeTestLinter2' => { 'enabled' => true },
|
10
|
+
},
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:config) { SCSSLint::Config.new(config_options) }
|
15
|
+
|
16
|
+
class SCSSLint::Linter::FakeTestLinter1 < SCSSLint::Linter; end
|
17
|
+
class SCSSLint::Linter::FakeTestLinter2 < SCSSLint::Linter; end
|
18
|
+
|
19
|
+
before do
|
20
|
+
# Silence console output
|
21
|
+
@output = ''
|
22
|
+
STDOUT.stub(:write) { |*args| @output.<<(*args) }
|
23
|
+
|
24
|
+
SCSSLint::Config.stub(:load).and_return(config)
|
25
|
+
SCSSLint::LinterRegistry.stub(:linters)
|
26
|
+
.and_return([SCSSLint::Linter::FakeTestLinter1,
|
27
|
+
SCSSLint::Linter::FakeTestLinter2])
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#parse_arguments' do
|
31
|
+
let(:files) { ['file1.scss', 'file2.scss'] }
|
32
|
+
let(:flags) { [] }
|
33
|
+
subject { SCSSLint::CLI.new(flags + files) }
|
34
|
+
|
35
|
+
def safe_parse
|
36
|
+
subject.parse_arguments
|
37
|
+
rescue SystemExit
|
38
|
+
# Keep running tests
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when the config_file flag is set' do
|
42
|
+
let(:config_file) { 'my-config-file.yml' }
|
43
|
+
let(:flags) { ['-c', config_file] }
|
44
|
+
|
45
|
+
it 'loads that config file' do
|
46
|
+
SCSSLint::Config.should_receive(:load).with(config_file)
|
47
|
+
safe_parse
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'and the config file is invalid' do
|
51
|
+
before do
|
52
|
+
SCSSLint::Config.should_receive(:load)
|
53
|
+
.with(config_file)
|
54
|
+
.and_raise(SCSSLint::InvalidConfiguration)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'halts with a configuration error code' do
|
58
|
+
subject.should_receive(:halt).with(:config)
|
59
|
+
safe_parse
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when the excluded files flag is set' do
|
65
|
+
let(:flags) { ['-e', 'file1.scss,file3.scss'] }
|
66
|
+
|
67
|
+
it 'sets the :excluded_files option' do
|
68
|
+
safe_parse
|
69
|
+
subject.options[:excluded_files].should =~ ['file1.scss', 'file3.scss']
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when the include linters flag is set' do
|
74
|
+
let(:flags) { %w[-i FakeTestLinter2] }
|
75
|
+
|
76
|
+
it 'enables only the included linters' do
|
77
|
+
safe_parse
|
78
|
+
subject.config.enabled_linters.should == [SCSSLint::Linter::FakeTestLinter2]
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'and the included linter does not exist' do
|
82
|
+
let(:flags) { %w[-i NonExistentLinter] }
|
83
|
+
|
84
|
+
it 'halts with a configuration error code' do
|
85
|
+
subject.should_receive(:halt).with(:config)
|
86
|
+
safe_parse
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'when the exclude linters flag is set' do
|
92
|
+
let(:flags) { %w[-x FakeTestLinter1] }
|
93
|
+
|
94
|
+
it 'includes all linters except the excluded one' do
|
95
|
+
safe_parse
|
96
|
+
subject.config.enabled_linters.should == [SCSSLint::Linter::FakeTestLinter2]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'when the format flag is set' do
|
101
|
+
context 'and the format is valid' do
|
102
|
+
let(:flags) { %w[--format XML] }
|
103
|
+
|
104
|
+
it 'sets the :reporter option to the correct reporter' do
|
105
|
+
safe_parse
|
106
|
+
subject.options[:reporter].should == SCSSLint::Reporter::XMLReporter
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'and the format is invalid' do
|
111
|
+
let(:flags) { %w[--format InvalidFormat] }
|
112
|
+
|
113
|
+
it 'sets the :reporter option to the correct reporter' do
|
114
|
+
subject.should_receive(:halt).with(:config)
|
115
|
+
safe_parse
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'when the show formatters flag is set' do
|
121
|
+
let(:flags) { ['--show-formatters'] }
|
122
|
+
|
123
|
+
it 'prints the formatters' do
|
124
|
+
subject.should_receive(:print_formatters)
|
125
|
+
safe_parse
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'when the show linters flag is set' do
|
130
|
+
let(:flags) { ['--show-linters'] }
|
131
|
+
|
132
|
+
it 'prints the linters' do
|
133
|
+
subject.should_receive(:print_linters)
|
134
|
+
safe_parse
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'when the help flag is set' do
|
139
|
+
let(:flags) { ['-h'] }
|
140
|
+
|
141
|
+
it 'prints a help message' do
|
142
|
+
subject.should_receive(:print_help)
|
143
|
+
safe_parse
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'when the version flag is set' do
|
148
|
+
let(:flags) { ['-v'] }
|
149
|
+
|
150
|
+
it 'prints the program version' do
|
151
|
+
subject.should_receive(:print_version)
|
152
|
+
safe_parse
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context 'when an invalid option is specified' do
|
157
|
+
let(:flags) { ['--non-existant-option'] }
|
158
|
+
|
159
|
+
it 'prints a help message' do
|
160
|
+
subject.should_receive(:print_help)
|
161
|
+
safe_parse
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context 'when no files are specified' do
|
166
|
+
let(:files) { [] }
|
167
|
+
|
168
|
+
it 'sets :files option to the empty list' do
|
169
|
+
safe_parse
|
170
|
+
subject.options[:files].should be_empty
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'when files are specified' do
|
175
|
+
it 'sets :files option to the list of files' do
|
176
|
+
safe_parse
|
177
|
+
subject.options[:files].should =~ files
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe '#run' do
|
183
|
+
let(:files) { ['file1.scss', 'file2.scss'] }
|
184
|
+
let(:options) { {} }
|
185
|
+
subject { SCSSLint::CLI.new(options) }
|
186
|
+
|
187
|
+
before do
|
188
|
+
subject.stub(:extract_files_from).and_return(files)
|
189
|
+
end
|
190
|
+
|
191
|
+
def safe_run
|
192
|
+
subject.run
|
193
|
+
rescue SystemExit
|
194
|
+
# Keep running tests
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'when no files are specified' do
|
198
|
+
let(:files) { [] }
|
199
|
+
|
200
|
+
it 'exits with a no-input status code' do
|
201
|
+
subject.should_receive(:halt).with(:no_input)
|
202
|
+
safe_run
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context 'when files are specified' do
|
207
|
+
it 'passes the set of files to the runner' do
|
208
|
+
SCSSLint::Runner.any_instance.should_receive(:run).with(files)
|
209
|
+
safe_run
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'uses the default reporter' do
|
213
|
+
SCSSLint::Reporter::DefaultReporter.any_instance
|
214
|
+
.should_receive(:report_lints)
|
215
|
+
safe_run
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context 'when there are no lints' do
|
220
|
+
before do
|
221
|
+
SCSSLint::Runner.any_instance.stub(:lints).and_return([])
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'exits cleanly' do
|
225
|
+
subject.should_not_receive(:halt)
|
226
|
+
safe_run
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'outputs nothing' do
|
230
|
+
safe_run
|
231
|
+
@output.should be_empty
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
context 'when there are only warnings' do
|
236
|
+
before do
|
237
|
+
SCSSLint::Runner.any_instance.stub(:lints).and_return([
|
238
|
+
SCSSLint::Lint.new(
|
239
|
+
SCSSLint::Linter::FakeTestLinter1.new,
|
240
|
+
'some-file.scss',
|
241
|
+
SCSSLint::Location.new(1, 1, 1),
|
242
|
+
'Some description',
|
243
|
+
:warning,
|
244
|
+
),
|
245
|
+
])
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'exits cleanly' do
|
249
|
+
subject.should_receive(:halt).with(:warning)
|
250
|
+
safe_run
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'outputs the warnings' do
|
254
|
+
safe_run
|
255
|
+
@output.should include 'Some description'
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
context 'when there are errors' do
|
260
|
+
before do
|
261
|
+
SCSSLint::Runner.any_instance.stub(:lints).and_return([
|
262
|
+
SCSSLint::Lint.new(
|
263
|
+
SCSSLint::Linter::FakeTestLinter1.new,
|
264
|
+
'some-file.scss',
|
265
|
+
SCSSLint::Location.new(1, 1, 1),
|
266
|
+
'Some description',
|
267
|
+
:error,
|
268
|
+
),
|
269
|
+
])
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'exits with an error status code' do
|
273
|
+
subject.should_receive(:halt).with(:error)
|
274
|
+
safe_run
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'outputs the errors' do
|
278
|
+
safe_run
|
279
|
+
@output.should include 'Some description'
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
context 'when the runner raises an error' do
|
284
|
+
let(:backtrace) { %w[file1.rb file2.rb] }
|
285
|
+
let(:message) { 'Some error message' }
|
286
|
+
|
287
|
+
let(:error) do
|
288
|
+
StandardError.new(message).tap { |e| e.set_backtrace(backtrace) }
|
289
|
+
end
|
290
|
+
|
291
|
+
before { SCSSLint::Runner.stub(:new).and_raise(error) }
|
292
|
+
|
293
|
+
it 'exits with an internal software error status code' do
|
294
|
+
subject.should_receive(:halt).with(:software)
|
295
|
+
safe_run
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'outputs the error message' do
|
299
|
+
safe_run
|
300
|
+
@output.should include message
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'outputs the backtrace' do
|
304
|
+
safe_run
|
305
|
+
@output.should include backtrace.join("\n")
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'outputs a link to the issue tracker' do
|
309
|
+
safe_run
|
310
|
+
@output.should include SCSSLint::BUG_REPORT_URL
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|