scss_lint 0.43.2 → 0.44.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/scss-lint +2 -1
- data/config/default.yml +3 -0
- data/data/properties.txt +35 -0
- data/data/property-sort-orders/smacss.txt +9 -0
- data/data/pseudo-elements.txt +5 -0
- data/lib/scss_lint.rb +1 -0
- data/lib/scss_lint/cli.rb +50 -30
- data/lib/scss_lint/config.rb +28 -3
- data/lib/scss_lint/constants.rb +6 -4
- data/lib/scss_lint/control_comment_processor.rb +8 -7
- data/lib/scss_lint/engine.rb +1 -1
- data/lib/scss_lint/file_finder.rb +1 -1
- data/lib/scss_lint/linter.rb +8 -7
- data/lib/scss_lint/linter/bang_format.rb +2 -2
- data/lib/scss_lint/linter/border_zero.rb +2 -2
- data/lib/scss_lint/linter/color_variable.rb +1 -1
- data/lib/scss_lint/linter/declaration_order.rb +10 -8
- data/lib/scss_lint/linter/duplicate_property.rb +2 -2
- data/lib/scss_lint/linter/empty_line_between_blocks.rb +3 -1
- data/lib/scss_lint/linter/final_newline.rb +3 -3
- data/lib/scss_lint/linter/indentation.rb +20 -20
- data/lib/scss_lint/linter/leading_zero.rb +2 -2
- data/lib/scss_lint/linter/mergeable_selector.rb +3 -4
- data/lib/scss_lint/linter/name_format.rb +2 -1
- data/lib/scss_lint/linter/nesting_depth.rb +1 -1
- data/lib/scss_lint/linter/property_sort_order.rb +11 -13
- data/lib/scss_lint/linter/selector_depth.rb +9 -8
- data/lib/scss_lint/linter/selector_format.rb +1 -1
- data/lib/scss_lint/linter/shorthand.rb +1 -1
- data/lib/scss_lint/linter/single_line_per_selector.rb +3 -1
- data/lib/scss_lint/linter/space_around_operator.rb +4 -2
- data/lib/scss_lint/linter/space_before_brace.rb +8 -8
- data/lib/scss_lint/linter/space_between_parens.rb +11 -11
- data/lib/scss_lint/linter/string_quotes.rb +9 -10
- data/lib/scss_lint/linter/trailing_zero.rb +1 -1
- data/lib/scss_lint/linter/transition_all.rb +1 -1
- data/lib/scss_lint/linter/unnecessary_mantissa.rb +3 -1
- data/lib/scss_lint/linter/unnecessary_parent_reference.rb +9 -2
- data/lib/scss_lint/linter/url_quotes.rb +4 -2
- data/lib/scss_lint/linter/variable_for_property.rb +1 -1
- data/lib/scss_lint/linter/vendor_prefix.rb +3 -3
- data/lib/scss_lint/linter/zero_unit.rb +3 -1
- data/lib/scss_lint/location.rb +1 -1
- data/lib/scss_lint/logger.rb +149 -0
- data/lib/scss_lint/options.rb +5 -1
- data/lib/scss_lint/rake_task.rb +10 -3
- data/lib/scss_lint/reporter.rb +4 -2
- data/lib/scss_lint/reporter/default_reporter.rb +3 -3
- data/lib/scss_lint/version.rb +3 -1
- data/spec/scss_lint/cli_spec.rb +66 -14
- data/spec/scss_lint/config_spec.rb +25 -5
- data/spec/scss_lint/linter/name_format_spec.rb +10 -0
- data/spec/scss_lint/linter/property_sort_order_spec.rb +28 -0
- data/spec/scss_lint/linter/unnecessary_parent_reference_spec.rb +10 -0
- data/spec/scss_lint/linter/variable_for_property_spec.rb +10 -0
- data/spec/scss_lint/logger_spec.rb +27 -0
- data/spec/scss_lint/options_spec.rb +18 -0
- data/spec/scss_lint/plugins/linter_dir_spec.rb +1 -1
- data/spec/scss_lint/reporter/clean_files_reporter_spec.rb +1 -1
- data/spec/scss_lint/reporter/config_reporter_spec.rb +1 -1
- data/spec/scss_lint/reporter/default_reporter_spec.rb +2 -1
- data/spec/scss_lint/reporter/files_reporter_spec.rb +1 -1
- data/spec/scss_lint/reporter/json_reporter_spec.rb +5 -5
- metadata +10 -7
data/lib/scss_lint/engine.rb
CHANGED
@@ -6,7 +6,7 @@ module SCSSLint
|
|
6
6
|
# Contains all information for a parsed SCSS file, including its name,
|
7
7
|
# contents, and parse tree.
|
8
8
|
class Engine
|
9
|
-
ENGINE_OPTIONS = { cache: false, syntax: :scss }
|
9
|
+
ENGINE_OPTIONS = { cache: false, syntax: :scss }.freeze
|
10
10
|
|
11
11
|
attr_reader :contents, :filename, :lines, :tree, :any_control_commands
|
12
12
|
|
data/lib/scss_lint/linter.rb
CHANGED
@@ -86,20 +86,21 @@ module SCSSLint
|
|
86
86
|
last_line = source_range.end_pos.line - 1
|
87
87
|
start_pos = source_range.start_pos.offset - 1
|
88
88
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
89
|
+
source =
|
90
|
+
if current_line == last_line
|
91
|
+
engine.lines[current_line][start_pos..(source_range.end_pos.offset - 1)]
|
92
|
+
else
|
93
|
+
engine.lines[current_line][start_pos..-1]
|
94
|
+
end
|
94
95
|
|
95
96
|
current_line += 1
|
96
97
|
while current_line < last_line
|
97
|
-
source +=
|
98
|
+
source += engine.lines[current_line].to_s
|
98
99
|
current_line += 1
|
99
100
|
end
|
100
101
|
|
101
102
|
if source_range.start_pos.line != source_range.end_pos.line
|
102
|
-
source +=
|
103
|
+
source += ((engine.lines[current_line] || '')[0...source_range.end_pos.offset]).to_s
|
103
104
|
end
|
104
105
|
|
105
106
|
source
|
@@ -3,7 +3,7 @@ module SCSSLint
|
|
3
3
|
class Linter::BangFormat < Linter
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
|
-
STOPPING_CHARACTERS = ['!', "'", '"', nil]
|
6
|
+
STOPPING_CHARACTERS = ['!', "'", '"', nil].freeze
|
7
7
|
|
8
8
|
def visit_extend(node)
|
9
9
|
check_bang(node)
|
@@ -31,7 +31,7 @@ module SCSSLint
|
|
31
31
|
before_qualifier = config['space_before_bang'] ? '' : 'not '
|
32
32
|
after_qualifier = config['space_after_bang'] ? '' : 'not '
|
33
33
|
|
34
|
-
add_lint(node, "! should #{before_qualifier}be
|
34
|
+
add_lint(node, "! should #{before_qualifier}be preceded by a space, " \
|
35
35
|
"and should #{after_qualifier}be followed by a space")
|
36
36
|
end
|
37
37
|
|
@@ -6,7 +6,7 @@ module SCSSLint
|
|
6
6
|
CONVENTION_TO_PREFERENCE = {
|
7
7
|
'zero' => %w[0 none],
|
8
8
|
'none' => %w[none 0],
|
9
|
-
}
|
9
|
+
}.freeze
|
10
10
|
|
11
11
|
BORDER_PROPERTIES = %w[
|
12
12
|
border
|
@@ -14,7 +14,7 @@ module SCSSLint
|
|
14
14
|
border-right
|
15
15
|
border-bottom
|
16
16
|
border-left
|
17
|
-
]
|
17
|
+
].freeze
|
18
18
|
|
19
19
|
def visit_root(_node)
|
20
20
|
@preference = CONVENTION_TO_PREFERENCE[config['convention']]
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SCSSLint
|
2
4
|
# Checks the order of nested items within a rule set.
|
3
5
|
class Linter::DeclarationOrder < Linter
|
@@ -8,9 +10,9 @@ module SCSSLint
|
|
8
10
|
yield # Continue linting children
|
9
11
|
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
13
|
+
alias visit_rule check_order
|
14
|
+
alias visit_mixin check_order
|
15
|
+
alias visit_media check_order
|
14
16
|
|
15
17
|
private
|
16
18
|
|
@@ -18,9 +20,9 @@ module SCSSLint
|
|
18
20
|
'Rule sets should be ordered as follows: '\
|
19
21
|
'`@extends`, `@includes` without `@content`, ' \
|
20
22
|
'properties, `@includes` with `@content`, ' \
|
21
|
-
'nested rule sets'
|
23
|
+
'nested rule sets'.freeze
|
22
24
|
|
23
|
-
MIXIN_WITH_CONTENT = 'mixin_with_content'
|
25
|
+
MIXIN_WITH_CONTENT = 'mixin_with_content'.freeze
|
24
26
|
|
25
27
|
DECLARATION_ORDER = [
|
26
28
|
Sass::Tree::ExtendNode,
|
@@ -28,7 +30,7 @@ module SCSSLint
|
|
28
30
|
Sass::Tree::PropNode,
|
29
31
|
MIXIN_WITH_CONTENT,
|
30
32
|
Sass::Tree::RuleNode,
|
31
|
-
]
|
33
|
+
].freeze
|
32
34
|
|
33
35
|
def important_node?(node)
|
34
36
|
DECLARATION_ORDER.include?(node.class)
|
@@ -36,8 +38,8 @@ module SCSSLint
|
|
36
38
|
|
37
39
|
def check_node(node)
|
38
40
|
children = node.children.each_with_index
|
39
|
-
|
40
|
-
|
41
|
+
.select { |n, _| important_node?(n) }
|
42
|
+
.map { |n, i| [n, node_declaration_type(n), i] }
|
41
43
|
|
42
44
|
sorted_children = children.sort do |(_, a_type, i), (_, b_type, j)|
|
43
45
|
[DECLARATION_ORDER.index(a_type), i] <=> [DECLARATION_ORDER.index(b_type), j]
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SCSSLint
|
2
4
|
# Reports the lack of empty lines between block defintions.
|
3
5
|
class Linter::EmptyLineBetweenBlocks < Linter
|
@@ -37,7 +39,7 @@ module SCSSLint
|
|
37
39
|
|
38
40
|
private
|
39
41
|
|
40
|
-
MESSAGE_FORMAT = '%s declaration should be %s by an empty line'
|
42
|
+
MESSAGE_FORMAT = '%s declaration should be %s by an empty line'.freeze
|
41
43
|
|
42
44
|
def check(node, type)
|
43
45
|
return if config['ignore_single_line_blocks'] && node_on_single_line?(node)
|
@@ -11,10 +11,10 @@ module SCSSLint
|
|
11
11
|
if config['present']
|
12
12
|
add_lint(engine.lines.count,
|
13
13
|
'Files should end with a trailing newline') unless ends_with_newline
|
14
|
-
|
15
|
-
add_lint(engine.lines.count,
|
16
|
-
'Files should not end with a trailing newline') if ends_with_newline
|
14
|
+
elsif ends_with_newline
|
15
|
+
add_lint(engine.lines.count, 'Files should not end with a trailing newline')
|
17
16
|
end
|
17
|
+
|
18
18
|
yield
|
19
19
|
end
|
20
20
|
end
|
@@ -64,8 +64,8 @@ module SCSSLint
|
|
64
64
|
|
65
65
|
if @allow_non_nested_indentation
|
66
66
|
yield # Continue linting else statement
|
67
|
-
else
|
68
|
-
visit(node.else)
|
67
|
+
elsif node.else
|
68
|
+
visit(node.else)
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
@@ -103,26 +103,26 @@ module SCSSLint
|
|
103
103
|
end
|
104
104
|
|
105
105
|
# Define node types that increase indentation level
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
106
|
+
alias visit_directive check_and_visit_children
|
107
|
+
alias visit_each check_and_visit_children
|
108
|
+
alias visit_for check_and_visit_children
|
109
|
+
alias visit_function check_and_visit_children
|
110
|
+
alias visit_media check_and_visit_children
|
111
|
+
alias visit_mixin check_and_visit_children
|
112
|
+
alias visit_mixindef check_and_visit_children
|
113
|
+
alias visit_prop check_and_visit_children
|
114
|
+
alias visit_rule check_and_visit_children
|
115
|
+
alias visit_supports check_and_visit_children
|
116
|
+
alias visit_while check_and_visit_children
|
117
117
|
|
118
118
|
# Define node types to check indentation of (notice comments are left out)
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
119
|
+
alias visit_charset check_indentation
|
120
|
+
alias visit_content check_indentation
|
121
|
+
alias visit_cssimport check_indentation
|
122
|
+
alias visit_extend check_indentation
|
123
|
+
alias visit_return check_indentation
|
124
|
+
alias visit_variable check_indentation
|
125
|
+
alias visit_warn check_indentation
|
126
126
|
|
127
127
|
private
|
128
128
|
|
@@ -15,7 +15,7 @@ module SCSSLint
|
|
15
15
|
|
16
16
|
def visit_script_number(node)
|
17
17
|
return unless number =
|
18
|
-
|
18
|
+
source_from_range(node.source_range)[NUMBER_WITH_LEADING_ZERO_REGEX, 1]
|
19
19
|
|
20
20
|
check_for_leading_zeros(node, number)
|
21
21
|
end
|
@@ -35,7 +35,7 @@ module SCSSLint
|
|
35
35
|
validator: ->(original) { original =~ /^0\.\d+$/ },
|
36
36
|
converter: ->(original) { "0#{original}" }
|
37
37
|
},
|
38
|
-
}
|
38
|
+
}.freeze
|
39
39
|
|
40
40
|
def check_for_leading_zeros(node, original_number)
|
41
41
|
style = config.fetch('style', 'exclude_zero')
|
@@ -23,8 +23,8 @@ module SCSSLint
|
|
23
23
|
yield # Continue linting children
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
alias visit_root check_node
|
27
|
+
alias visit_rule check_node
|
28
28
|
|
29
29
|
private
|
30
30
|
|
@@ -75,8 +75,7 @@ module SCSSLint
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def subrule?(rule1, rule2)
|
78
|
-
|
79
|
-
"#{rule1}".start_with?("#{rule2}.")
|
78
|
+
rule1.to_s.start_with?("#{rule2} ", "#{rule2}.")
|
80
79
|
end
|
81
80
|
|
82
81
|
def whitelist_contains(node)
|
@@ -20,6 +20,7 @@ module SCSSLint
|
|
20
20
|
|
21
21
|
def visit_script_funcall(node)
|
22
22
|
check_name(node, 'function') unless FUNCTION_WHITELIST.include?(node.name)
|
23
|
+
yield # Continue linting any arguments of this function call
|
23
24
|
end
|
24
25
|
|
25
26
|
def visit_script_variable(node)
|
@@ -72,7 +73,7 @@ module SCSSLint
|
|
72
73
|
'instead of underscores',
|
73
74
|
validator: ->(name) { name !~ /[_A-Z]/ },
|
74
75
|
},
|
75
|
-
}
|
76
|
+
}.freeze
|
76
77
|
|
77
78
|
def violated_convention(name_string, type)
|
78
79
|
convention_name = convention_name(type)
|
@@ -3,7 +3,7 @@ module SCSSLint
|
|
3
3
|
class Linter::NestingDepth < Linter
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
|
-
IGNORED_SELECTORS = [Sass::Selector::Parent, Sass::Selector::Pseudo]
|
6
|
+
IGNORED_SELECTORS = [Sass::Selector::Parent, Sass::Selector::Pseudo].freeze
|
7
7
|
|
8
8
|
def visit_root(_node)
|
9
9
|
@max_depth = config['max_depth']
|
@@ -19,8 +19,8 @@ module SCSSLint
|
|
19
19
|
end
|
20
20
|
|
21
21
|
if sortable_props.count >= config.fetch('min_properties', 2)
|
22
|
-
sortable_prop_info =
|
23
|
-
.map do |child|
|
22
|
+
sortable_prop_info =
|
23
|
+
sortable_props.map do |child|
|
24
24
|
name = child.name.join
|
25
25
|
/^(?<vendor>-\w+(-osx)?-)?(?<property>.+)/ =~ name
|
26
26
|
{ name: name, vendor: vendor, property: "#{@nested_under}#{property}", node: child }
|
@@ -33,10 +33,10 @@ module SCSSLint
|
|
33
33
|
yield # Continue linting children
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
alias visit_media check_order
|
37
|
+
alias visit_mixin check_order
|
38
|
+
alias visit_rule check_order
|
39
|
+
alias visit_prop check_order
|
40
40
|
|
41
41
|
def visit_prop(node, &block)
|
42
42
|
# Handle nested properties by appending the parent property they are
|
@@ -76,8 +76,8 @@ module SCSSLint
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def check_sort_order(sortable_prop_info)
|
79
|
-
|
80
|
-
|
79
|
+
sortable_prop_info = sortable_prop_info.uniq { |item| item[:name] }
|
80
|
+
sorted_props = sortable_prop_info.sort { |a, b| compare_properties(a, b) }
|
81
81
|
|
82
82
|
sorted_props.each_with_index do |prop, index|
|
83
83
|
# Once we reach the portion of the list with unspecified properties, we
|
@@ -128,12 +128,10 @@ module SCSSLint
|
|
128
128
|
def compare_properties(a, b)
|
129
129
|
if a[:property] == b[:property]
|
130
130
|
compare_by_vendor(a, b)
|
131
|
+
elsif @preferred_order
|
132
|
+
compare_by_order(a, b, @preferred_order)
|
131
133
|
else
|
132
|
-
|
133
|
-
compare_by_order(a, b, @preferred_order)
|
134
|
-
else
|
135
|
-
a[:property] <=> b[:property]
|
136
|
-
end
|
134
|
+
a[:property] <=> b[:property]
|
137
135
|
end
|
138
136
|
end
|
139
137
|
|
@@ -49,14 +49,15 @@ module SCSSLint
|
|
49
49
|
depth = simple_sequences.size -
|
50
50
|
separators.count { |item| item == '~' || item == '+' }
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
52
|
+
depth +=
|
53
|
+
if parent_selectors > 0
|
54
|
+
# If parent selectors are present, add the current depth for each
|
55
|
+
# additional parent selector.
|
56
|
+
parent_selectors * (current_depth - 1)
|
57
|
+
else
|
58
|
+
# Otherwise this just descends from the containing selector
|
59
|
+
current_depth
|
60
|
+
end
|
60
61
|
|
61
62
|
depth
|
62
63
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SCSSLint
|
2
4
|
# Checks that selector sequences are split over multiple lines by comma.
|
3
5
|
class Linter::SingleLinePerSelector < Linter
|
4
6
|
include LinterRegistry
|
5
7
|
|
6
|
-
MESSAGE = 'Each selector in a comma sequence should be on its own single line'
|
8
|
+
MESSAGE = 'Each selector in a comma sequence should be on its own single line'.freeze
|
7
9
|
|
8
10
|
def visit_comma_sequence(node)
|
9
11
|
return unless node.members.count > 1
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SCSSLint
|
2
4
|
# Checks for space around operators on values.
|
3
5
|
class Linter::SpaceAroundOperator < Linter
|
@@ -109,10 +111,10 @@ module SCSSLint
|
|
109
111
|
private
|
110
112
|
|
111
113
|
SPACE_MSG = '`%s` should be written with a single space on each side of ' \
|
112
|
-
'the operator: `%s %s %s`'
|
114
|
+
'the operator: `%s %s %s`'.freeze
|
113
115
|
|
114
116
|
NO_SPACE_MSG = '`%s` should be written without spaces around the ' \
|
115
|
-
'operator: `%s%s%s`'
|
117
|
+
'operator: `%s%s%s`'.freeze
|
116
118
|
|
117
119
|
def calculate_operator_source
|
118
120
|
# We don't want to add 1 to range1.end_pos.offset for the same reason as
|