scss_lint 0.43.2 → 0.44.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/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
|