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