scss-lint 0.23.1 → 0.24.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 +13 -4
- data/lib/scss_lint/cli.rb +18 -0
- data/lib/scss_lint/config.rb +4 -5
- data/lib/scss_lint/lint.rb +4 -2
- data/lib/scss_lint/linter.rb +22 -12
- data/lib/scss_lint/linter/border_zero.rb +2 -3
- data/lib/scss_lint/linter/capitalization_in_selector.rb +5 -5
- data/lib/scss_lint/linter/color_keyword.rb +3 -4
- data/lib/scss_lint/linter/compass/property_with_mixin.rb +17 -10
- data/lib/scss_lint/linter/empty_line_between_blocks.rb +4 -5
- data/lib/scss_lint/linter/final_newline.rb +1 -1
- data/lib/scss_lint/linter/hex_length.rb +56 -0
- data/lib/scss_lint/linter/hex_notation.rb +38 -0
- data/lib/scss_lint/linter/hex_validation.rb +23 -0
- data/lib/scss_lint/linter/id_with_extraneous_selector.rb +5 -6
- data/lib/scss_lint/linter/indentation.rb +8 -7
- data/lib/scss_lint/linter/leading_zero.rb +5 -9
- data/lib/scss_lint/linter/mergeable_selector.rb +59 -0
- data/lib/scss_lint/linter/name_format.rb +4 -4
- data/lib/scss_lint/linter/placeholder_in_extend.rb +3 -4
- data/lib/scss_lint/linter/property_sort_order.rb +4 -4
- data/lib/scss_lint/linter/property_spelling.rb +4 -4
- data/lib/scss_lint/linter/selector_depth.rb +1 -1
- data/lib/scss_lint/linter/shorthand.rb +10 -12
- data/lib/scss_lint/linter/single_line_per_selector.rb +1 -1
- data/lib/scss_lint/linter/space_after_comma.rb +2 -3
- data/lib/scss_lint/linter/space_after_property_name.rb +2 -3
- data/lib/scss_lint/linter/space_between_parens.rb +8 -9
- data/lib/scss_lint/linter/string_quotes.rb +2 -3
- data/lib/scss_lint/linter/unnecessary_mantissa.rb +5 -5
- data/lib/scss_lint/linter/url_quotes.rb +2 -3
- data/lib/scss_lint/linter/zero_unit.rb +11 -9
- data/lib/scss_lint/reporter.rb +4 -0
- data/lib/scss_lint/reporter/config_reporter.rb +26 -0
- data/lib/scss_lint/reporter/default_reporter.rb +7 -7
- data/lib/scss_lint/reporter/xml_reporter.rb +4 -4
- data/lib/scss_lint/runner.rb +2 -2
- data/lib/scss_lint/sass/tree.rb +1 -1
- data/lib/scss_lint/utils.rb +0 -11
- data/lib/scss_lint/version.rb +1 -1
- metadata +11 -8
- data/lib/scss_lint/linter/duplicate_root.rb +0 -34
- data/lib/scss_lint/linter/hex_format.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17fa291ee89e19fe0f6103299f2615c7e3a83633
|
4
|
+
data.tar.gz: e52fcd5476a7f944884cb7d8714649dc51ea4e89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19b28041ce7fa7c14ff869de3be4193543a9c1e43285c2f150e23dc22cdaac3aeaebf4764803a61880646fb4baf5e5d9a5d78e6a970956b0eeae7763f14c0d8e
|
7
|
+
data.tar.gz: b87d1c50bbd9805083d687de07e8eaafbd90e931c85171a58fa6ca09cc344d57da30b6fd85ab55d928b633ff4cf7c4c680a2b5671c310c79f7f9cec03e6181c6
|
data/config/default.yml
CHANGED
@@ -21,9 +21,6 @@ linters:
|
|
21
21
|
DuplicateProperty:
|
22
22
|
enabled: true
|
23
23
|
|
24
|
-
DuplicateRoot:
|
25
|
-
enabled: true
|
26
|
-
|
27
24
|
EmptyLineBetweenBlocks:
|
28
25
|
enabled: true
|
29
26
|
ignore_single_line_blocks: true
|
@@ -35,7 +32,15 @@ linters:
|
|
35
32
|
enabled: true
|
36
33
|
present: true
|
37
34
|
|
38
|
-
|
35
|
+
HexLength:
|
36
|
+
enabled: true
|
37
|
+
style: short # or 'long'
|
38
|
+
|
39
|
+
HexNotation:
|
40
|
+
enabled: true
|
41
|
+
style: lowercase # or 'uppercase'
|
42
|
+
|
43
|
+
HexValidation:
|
39
44
|
enabled: true
|
40
45
|
|
41
46
|
IdWithExtraneousSelector:
|
@@ -49,6 +54,10 @@ linters:
|
|
49
54
|
enabled: true
|
50
55
|
style: exclude_zero # or 'include_zero'
|
51
56
|
|
57
|
+
MergeableSelector:
|
58
|
+
enabled: true
|
59
|
+
force_nesting: true
|
60
|
+
|
52
61
|
NameFormat:
|
53
62
|
enabled: true
|
54
63
|
convention: hyphenated_lowercase # or 'BEM', or a regex pattern
|
data/lib/scss_lint/cli.rb
CHANGED
@@ -65,6 +65,10 @@ module SCSSLint
|
|
65
65
|
define_output_format(format)
|
66
66
|
end
|
67
67
|
|
68
|
+
opts.on_tail('--show-formatters', 'Shows available formatters') do
|
69
|
+
print_formatters
|
70
|
+
end
|
71
|
+
|
68
72
|
opts.on('-i', '--include-linter linter,...', Array,
|
69
73
|
'Specify which linters you want to run') do |linters|
|
70
74
|
@options[:included_linters] = linters
|
@@ -196,6 +200,20 @@ module SCSSLint
|
|
196
200
|
halt :config
|
197
201
|
end
|
198
202
|
|
203
|
+
def print_formatters
|
204
|
+
puts 'Installed formatters:'
|
205
|
+
|
206
|
+
reporter_names = SCSSLint::Reporter.descendants.map do |reporter|
|
207
|
+
reporter.name.split('::').last.split('Reporter').first
|
208
|
+
end
|
209
|
+
|
210
|
+
reporter_names.sort.each do |reporter_name|
|
211
|
+
puts " - #{reporter_name}"
|
212
|
+
end
|
213
|
+
|
214
|
+
halt
|
215
|
+
end
|
216
|
+
|
199
217
|
def print_linters
|
200
218
|
puts 'Installed linters:'
|
201
219
|
|
data/lib/scss_lint/config.rb
CHANGED
@@ -128,11 +128,10 @@ module SCSSLint
|
|
128
128
|
|
129
129
|
LinterRegistry.linters.each do |linter_class|
|
130
130
|
name = linter_name(linter_class)
|
131
|
+
next unless name.match(class_name_regex)
|
131
132
|
|
132
|
-
|
133
|
-
|
134
|
-
options['linters'][name] = smart_merge(old_options, wildcard_options)
|
135
|
-
end
|
133
|
+
old_options = options['linters'].fetch(name, {})
|
134
|
+
options['linters'][name] = smart_merge(old_options, wildcard_options)
|
136
135
|
end
|
137
136
|
end
|
138
137
|
|
@@ -187,7 +186,7 @@ module SCSSLint
|
|
187
186
|
|
188
187
|
# Merge two hashes, concatenating lists and further merging nested hashes.
|
189
188
|
def smart_merge(parent, child)
|
190
|
-
parent.merge(child) do |
|
189
|
+
parent.merge(child) do |_key, old, new|
|
191
190
|
case old
|
192
191
|
when Array
|
193
192
|
old + new
|
data/lib/scss_lint/lint.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
module SCSSLint
|
2
2
|
# Stores information about a single problem that was detected by a [Linter].
|
3
3
|
class Lint
|
4
|
-
attr_reader :filename, :location, :description, :severity
|
4
|
+
attr_reader :linter, :filename, :location, :description, :severity
|
5
5
|
|
6
|
+
# @param linter [SCSSLint::Linter]
|
6
7
|
# @param filename [String]
|
7
8
|
# @param location [SCSSLint::Location]
|
8
9
|
# @param description [String]
|
9
10
|
# @param severity [Symbol]
|
10
|
-
def initialize(filename, location, description, severity = :warning)
|
11
|
+
def initialize(linter, filename, location, description, severity = :warning)
|
12
|
+
@linter = linter
|
11
13
|
@filename = filename
|
12
14
|
@location = location
|
13
15
|
@description = description
|
data/lib/scss_lint/linter.rb
CHANGED
@@ -18,21 +18,16 @@ module SCSSLint
|
|
18
18
|
visit(engine.tree)
|
19
19
|
end
|
20
20
|
|
21
|
+
protected
|
22
|
+
|
21
23
|
# Helper for creating lint from a parse tree node
|
22
24
|
#
|
23
|
-
# @param
|
25
|
+
# @param node_or_line_or_location [Sass::Script::Tree::Node, Fixnum, SCSSLint::Location]
|
24
26
|
# @param message [String]
|
25
|
-
def add_lint(
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
Location.new(node_or_line.line)
|
30
|
-
else
|
31
|
-
Location.new(node_or_line)
|
32
|
-
end
|
33
|
-
|
34
|
-
@lints << Lint.new(engine.filename,
|
35
|
-
location,
|
27
|
+
def add_lint(node_or_line_or_location, message)
|
28
|
+
@lints << Lint.new(self,
|
29
|
+
engine.filename,
|
30
|
+
extract_location(node_or_line_or_location),
|
36
31
|
message)
|
37
32
|
end
|
38
33
|
|
@@ -130,5 +125,20 @@ module SCSSLint
|
|
130
125
|
visit(child)
|
131
126
|
end
|
132
127
|
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def extract_location(node_or_line_or_location)
|
132
|
+
if node_or_line_or_location.is_a?(Location)
|
133
|
+
node_or_line_or_location
|
134
|
+
elsif node_or_line_or_location.respond_to?(:source_range) &&
|
135
|
+
node_or_line_or_location.source_range
|
136
|
+
location_from_range(node_or_line_or_location.source_range)
|
137
|
+
elsif node_or_line_or_location.respond_to?(:line)
|
138
|
+
Location.new(node_or_line_or_location.line)
|
139
|
+
else
|
140
|
+
Location.new(node_or_line_or_location)
|
141
|
+
end
|
142
|
+
end
|
133
143
|
end
|
134
144
|
end
|
@@ -5,10 +5,9 @@ module SCSSLint
|
|
5
5
|
|
6
6
|
def visit_prop(node)
|
7
7
|
return unless BORDER_PROPERTIES.include?(node.name.first.to_s)
|
8
|
+
return unless node.value.to_sass.strip == 'none'
|
8
9
|
|
9
|
-
|
10
|
-
add_lint(node, '`border: 0;` is preferred over `border: none;`')
|
11
|
-
end
|
10
|
+
add_lint(node, '`border: 0;` is preferred over `border: none;`')
|
12
11
|
end
|
13
12
|
|
14
13
|
private
|
@@ -31,11 +31,11 @@ module SCSSLint
|
|
31
31
|
|
32
32
|
def check(node, selector_name = nil)
|
33
33
|
name = node.name.join
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
34
|
+
return unless name =~ /[A-Z]/
|
35
|
+
|
36
|
+
selector_name ||= node.class.name.split('::').last
|
37
|
+
add_lint(node, "#{selector_name} `#{name}` in selector should be " \
|
38
|
+
"written in all lowercase as `#{name.downcase}`")
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -4,9 +4,10 @@ module SCSSLint
|
|
4
4
|
class Linter::ColorKeyword < Linter
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
|
+
COLOR_REGEX = /(#?[a-f0-9]{3,6}|[a-z]+)/i
|
8
|
+
|
7
9
|
def visit_script_color(node)
|
8
10
|
color = source_from_range(node.source_range)[COLOR_REGEX, 1]
|
9
|
-
|
10
11
|
add_color_lint(node, color) if color_keyword?(color)
|
11
12
|
end
|
12
13
|
|
@@ -20,8 +21,6 @@ module SCSSLint
|
|
20
21
|
|
21
22
|
private
|
22
23
|
|
23
|
-
COLOR_REGEX = /(#?[a-f0-9]{3,6}|[a-z]+)/i
|
24
|
-
|
25
24
|
def add_color_lint(node, original)
|
26
25
|
hex_form = Sass::Script::Value::Color.new(color_rgb(original)).tap do |color|
|
27
26
|
color.options = {} # `inspect` requires options to be set
|
@@ -29,7 +28,7 @@ module SCSSLint
|
|
29
28
|
|
30
29
|
add_lint(node,
|
31
30
|
"Color `#{original}` should be written in hexadecimal form " \
|
32
|
-
"as `#{
|
31
|
+
"as `#{hex_form}`")
|
33
32
|
end
|
34
33
|
|
35
34
|
def color_keyword?(string)
|
@@ -4,16 +4,8 @@ module SCSSLint
|
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
6
|
def visit_prop(node)
|
7
|
-
|
8
|
-
|
9
|
-
if PROPERTIES_WITH_MIXINS.include?(prop_name)
|
10
|
-
add_lint node, "Use the Compass `#{prop_name}` mixin instead of the property"
|
11
|
-
end
|
12
|
-
|
13
|
-
if prop_name == 'display' && node.value.to_sass == 'inline-block'
|
14
|
-
add_lint node,
|
15
|
-
'Use the Compass `inline-block` mixin instead of `display: inline-block`'
|
16
|
-
end
|
7
|
+
check_for_properties_with_mixins(node)
|
8
|
+
check_for_inline_block(node)
|
17
9
|
end
|
18
10
|
|
19
11
|
private
|
@@ -29,5 +21,20 @@ module SCSSLint
|
|
29
21
|
text-shadow
|
30
22
|
transform
|
31
23
|
].to_set
|
24
|
+
|
25
|
+
def check_for_properties_with_mixins(node)
|
26
|
+
prop_name = node.name.join
|
27
|
+
return unless PROPERTIES_WITH_MIXINS.include?(prop_name)
|
28
|
+
|
29
|
+
add_lint node, "Use the Compass `#{prop_name}` mixin instead of the property"
|
30
|
+
end
|
31
|
+
|
32
|
+
def check_for_inline_block(node)
|
33
|
+
prop_name = node.name.join
|
34
|
+
return unless prop_name == 'display' && node.value.to_sass == 'inline-block'
|
35
|
+
|
36
|
+
add_lint node,
|
37
|
+
'Use the Compass `inline-block` mixin instead of `display: inline-block`'
|
38
|
+
end
|
32
39
|
end
|
33
40
|
end
|
@@ -36,11 +36,10 @@ module SCSSLint
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def check_following_node(node, type)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
39
|
+
return unless (following_node = next_node(node)) && (next_start_line = following_node.line)
|
40
|
+
return if engine.lines[next_start_line - 2].strip.empty?
|
41
|
+
|
42
|
+
add_lint(next_start_line - 1, MESSAGE_FORMAT % [type, 'followed'])
|
44
43
|
end
|
45
44
|
|
46
45
|
# In cases where the previous node is not a block declaration, we won't
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks that hexadecimal colors are written in the desired number of
|
3
|
+
# characters.
|
4
|
+
class Linter::HexLength < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
HEX_REGEX = /(#(\h{3}|\h{6}))(?!\h)/
|
8
|
+
|
9
|
+
def visit_script_color(node)
|
10
|
+
return unless hex = source_from_range(node.source_range)[HEX_REGEX, 1]
|
11
|
+
check_hex(hex, node)
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_script_string(node)
|
15
|
+
return unless node.type == :identifier
|
16
|
+
|
17
|
+
node.value.scan(HEX_REGEX) do |match|
|
18
|
+
check_hex(match.first, node)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def check_hex(hex, node)
|
25
|
+
return if expected(hex) == hex
|
26
|
+
|
27
|
+
add_lint(node, "Color `#{hex}` should be written as `#{expected(hex)}`")
|
28
|
+
end
|
29
|
+
|
30
|
+
def expected(hex)
|
31
|
+
return short_hex_form(hex) if can_be_shorter?(hex) && short_style?
|
32
|
+
return long_hex_form(hex) if hex.length == 4 && !short_style?
|
33
|
+
|
34
|
+
hex
|
35
|
+
end
|
36
|
+
|
37
|
+
def can_be_shorter?(hex)
|
38
|
+
hex.length == 7 &&
|
39
|
+
hex[1] == hex[2] &&
|
40
|
+
hex[3] == hex[4] &&
|
41
|
+
hex[5] == hex[6]
|
42
|
+
end
|
43
|
+
|
44
|
+
def short_hex_form(hex)
|
45
|
+
[hex[0..1], hex[3], hex[5]].join
|
46
|
+
end
|
47
|
+
|
48
|
+
def long_hex_form(hex)
|
49
|
+
[hex[0..1], hex[1], hex[2], hex[2], hex[3], hex[3]].join
|
50
|
+
end
|
51
|
+
|
52
|
+
def short_style?
|
53
|
+
config['style'] == 'short'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks if hexadecimal colors are written lowercase / uppercase.
|
3
|
+
class Linter::HexNotation < Linter
|
4
|
+
include LinterRegistry
|
5
|
+
|
6
|
+
HEX_REGEX = /(#(\h{3}|\h{6}))(?!\h)/
|
7
|
+
|
8
|
+
def visit_script_color(node)
|
9
|
+
return unless hex = source_from_range(node.source_range)[HEX_REGEX, 1]
|
10
|
+
check_hex(hex, node)
|
11
|
+
end
|
12
|
+
|
13
|
+
def visit_script_string(node)
|
14
|
+
return unless node.type == :identifier
|
15
|
+
|
16
|
+
node.value.scan(HEX_REGEX) do |match|
|
17
|
+
check_hex(match.first, node)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def check_hex(hex, node)
|
24
|
+
return if expected(hex) == hex
|
25
|
+
|
26
|
+
add_lint(node, "Color `#{hex}` should be written as `#{expected(hex)}`")
|
27
|
+
end
|
28
|
+
|
29
|
+
def expected(color)
|
30
|
+
return color.downcase if lowercase_style?
|
31
|
+
color.upcase
|
32
|
+
end
|
33
|
+
|
34
|
+
def lowercase_style?
|
35
|
+
config['style'] == 'lowercase'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks for invalid hexadecimal colors.
|
3
|
+
class Linter::HexValidation < Linter
|
4
|
+
include LinterRegistry
|
5
|
+
|
6
|
+
def visit_script_string(node)
|
7
|
+
return unless node.type == :identifier
|
8
|
+
|
9
|
+
node.value.scan(/(#\h+)/) do |match|
|
10
|
+
check_hex(match.first, node)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
HEX_REGEX = /(#(\h{3}|\h{6}))(?!\h)/
|
17
|
+
|
18
|
+
def check_hex(hex, node)
|
19
|
+
return if HEX_REGEX.match(hex)
|
20
|
+
add_lint(node, "Colors must have either three or six digits: `#{hex}`")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -11,13 +11,12 @@ module SCSSLint
|
|
11
11
|
!simple.is_a?(Sass::Selector::Id) &&
|
12
12
|
!simple.is_a?(Sass::Selector::Pseudo)
|
13
13
|
end
|
14
|
+
return unless can_be_simplified
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
'since IDs should be uniquely identifying')
|
20
|
-
end
|
16
|
+
# TODO: Sass::Selector::SimpleSequence#source_range sometimes lies about
|
17
|
+
# its line, so reference `#line` directly
|
18
|
+
add_lint(seq.line, "Selector `#{seq}` can be simplified to `#{id_sel}`, " \
|
19
|
+
'since IDs should be uniquely identifying')
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
@@ -3,7 +3,7 @@ module SCSSLint
|
|
3
3
|
class Linter::Indentation < Linter
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
|
-
def visit_root(
|
6
|
+
def visit_root(_node)
|
7
7
|
@indent_width = config['width']
|
8
8
|
@indent = 0
|
9
9
|
yield
|
@@ -29,12 +29,12 @@ module SCSSLint
|
|
29
29
|
|
30
30
|
actual_indent = engine.lines[node.line - 1][/^(\s*)/, 1]
|
31
31
|
|
32
|
-
if actual_indent.length
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
return if actual_indent.length == @indent
|
33
|
+
|
34
|
+
add_lint(node.line,
|
35
|
+
"Line should be indented #{@indent} spaces, " \
|
36
|
+
"but was indented #{actual_indent.length} spaces")
|
37
|
+
true
|
38
38
|
end
|
39
39
|
|
40
40
|
# Deal with `else` statements
|
@@ -44,6 +44,7 @@ module SCSSLint
|
|
44
44
|
end
|
45
45
|
|
46
46
|
# Define node types that increase indentation level
|
47
|
+
alias_method :visit_atroot, :check_and_visit_children
|
47
48
|
alias_method :visit_directive, :check_and_visit_children
|
48
49
|
alias_method :visit_each, :check_and_visit_children
|
49
50
|
alias_method :visit_for, :check_and_visit_children
|
@@ -7,11 +7,9 @@ module SCSSLint
|
|
7
7
|
return unless node.type == :identifier
|
8
8
|
|
9
9
|
non_string_values = remove_quoted_strings(node.value).split
|
10
|
-
|
11
10
|
non_string_values.each do |value|
|
12
|
-
|
13
|
-
|
14
|
-
end
|
11
|
+
next unless number = value[FRACTIONAL_DIGIT_REGEX, 1]
|
12
|
+
check_number(node, number)
|
15
13
|
end
|
16
14
|
end
|
17
15
|
|
@@ -41,12 +39,10 @@ module SCSSLint
|
|
41
39
|
def check_number(node, original_number)
|
42
40
|
style = config.fetch('style', 'exclude_zero')
|
43
41
|
convention = CONVENTIONS[style]
|
42
|
+
return if convention[:validator].call(original_number)
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
add_lint(node, convention[:explanation] % [original_number, corrected])
|
49
|
-
end
|
44
|
+
corrected = convention[:converter].call(original_number)
|
45
|
+
add_lint(node, convention[:explanation] % [original_number, corrected])
|
50
46
|
end
|
51
47
|
end
|
52
48
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks for rule sets that can be merged with other rule sets.
|
3
|
+
class Linter::MergeableSelector < Linter
|
4
|
+
include LinterRegistry
|
5
|
+
|
6
|
+
def check_node(node)
|
7
|
+
node.children.each_with_object([]) do |child_node, seen_nodes|
|
8
|
+
next unless child_node.is_a?(Sass::Tree::RuleNode)
|
9
|
+
|
10
|
+
mergeable_node = find_mergeable_node(child_node, seen_nodes)
|
11
|
+
seen_nodes << child_node
|
12
|
+
next unless mergeable_node
|
13
|
+
|
14
|
+
add_lint child_node.line,
|
15
|
+
"Merge rule `#{node_rule(child_node)}` with rule " \
|
16
|
+
"on line #{mergeable_node.line}"
|
17
|
+
end
|
18
|
+
|
19
|
+
yield # Continue linting children
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :visit_root, :check_node
|
23
|
+
alias_method :visit_rule, :check_node
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def find_mergeable_node(node, seen_nodes)
|
28
|
+
seen_nodes.find do |seen_node|
|
29
|
+
equal?(node, seen_node) ||
|
30
|
+
(config['force_nesting'] && nested?(node, seen_node))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def equal?(node1, node2)
|
35
|
+
node_rule(node1) == node_rule(node2)
|
36
|
+
end
|
37
|
+
|
38
|
+
def nested?(node1, node2)
|
39
|
+
return false unless single_rule?(node1) && single_rule?(node2)
|
40
|
+
|
41
|
+
rule1 = node_rule(node1)
|
42
|
+
rule2 = node_rule(node2)
|
43
|
+
subrule?(rule1, rule2) || subrule?(rule2, rule1)
|
44
|
+
end
|
45
|
+
|
46
|
+
def node_rule(node)
|
47
|
+
node.rule.join
|
48
|
+
end
|
49
|
+
|
50
|
+
def single_rule?(node)
|
51
|
+
node.parsed_rules.members.count == 1
|
52
|
+
end
|
53
|
+
|
54
|
+
def subrule?(rule1, rule2)
|
55
|
+
"#{rule1}".start_with?("#{rule2} ") ||
|
56
|
+
"#{rule1}".start_with?("#{rule2}.")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -46,10 +46,10 @@ module SCSSLint
|
|
46
46
|
].to_set
|
47
47
|
|
48
48
|
def check_name(node, node_type, node_text = node.name)
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
return unless violation = violated_convention(node_text)
|
50
|
+
|
51
|
+
add_lint(node, "Name of #{node_type} `#{node_text}` should be " \
|
52
|
+
"written #{violation[:explanation]}")
|
53
53
|
end
|
54
54
|
|
55
55
|
def check_placeholder(node)
|
@@ -7,11 +7,10 @@ module SCSSLint
|
|
7
7
|
# The array returned by the parser is a bit awkward in that it splits on
|
8
8
|
# every word boundary (so %placeholder becomes ['%', 'placeholder']).
|
9
9
|
selector = node.selector.join
|
10
|
+
return if selector.start_with?('%')
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
'Prefer using placeholder selectors (e.g. %some-placeholder) with @extend')
|
14
|
-
end
|
12
|
+
add_lint(node, 'Prefer using placeholder selectors (e.g. ' \
|
13
|
+
'%some-placeholder) with @extend')
|
15
14
|
end
|
16
15
|
end
|
17
16
|
end
|
@@ -21,10 +21,10 @@ module SCSSLint
|
|
21
21
|
.sort { |a, b| compare_properties(a, b) }
|
22
22
|
|
23
23
|
sorted_props.each_with_index do |prop, index|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
next unless prop != sortable_prop_info[index]
|
25
|
+
|
26
|
+
add_lint(sortable_props[index], MESSAGE)
|
27
|
+
break
|
28
28
|
end
|
29
29
|
|
30
30
|
yield # Continue linting children
|
@@ -3,7 +3,7 @@ module SCSSLint
|
|
3
3
|
class Linter::PropertySpelling < Linter
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
|
-
def visit_root(
|
6
|
+
def visit_root(_node)
|
7
7
|
@extra_properties = config['extra_properties'].to_set
|
8
8
|
yield # Continue linting children
|
9
9
|
end
|
@@ -16,10 +16,10 @@ module SCSSLint
|
|
16
16
|
|
17
17
|
# Ignore vendor-prefixed properties
|
18
18
|
return if name.start_with?('-')
|
19
|
+
return if KNOWN_PROPERTIES.include?(name) ||
|
20
|
+
@extra_properties.include?(name)
|
19
21
|
|
20
|
-
|
21
|
-
add_lint(node, "Unknown property #{name}")
|
22
|
-
end
|
22
|
+
add_lint(node, "Unknown property #{name}")
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
@@ -37,25 +37,23 @@ module SCSSLint
|
|
37
37
|
# HACK: node_parent may not be initialized at this point, so we need to
|
38
38
|
# set it ourselves
|
39
39
|
value.node_parent = literal
|
40
|
+
return unless value.is_a?(Sass::Script::Value::String)
|
40
41
|
|
41
|
-
|
42
|
-
check_script_string(prop, value)
|
43
|
-
end
|
42
|
+
check_script_string(prop, value)
|
44
43
|
end
|
45
44
|
|
46
|
-
LIST_LITERAL_REGEX =
|
45
|
+
LIST_LITERAL_REGEX = /
|
47
46
|
\A
|
48
47
|
(\S+\s+\S+(\s+\S+){0,2}) # Two to four values separated by spaces
|
49
48
|
(\s+!\w+)? # Ignore `!important` priority overrides
|
50
49
|
\z
|
51
|
-
|
50
|
+
/x
|
52
51
|
|
53
52
|
def check_script_string(prop, script_string)
|
54
53
|
return unless script_string.type == :identifier
|
54
|
+
return unless values = script_string.value.strip[LIST_LITERAL_REGEX, 1]
|
55
55
|
|
56
|
-
|
57
|
-
check_shorthand(prop, script_string, values.split)
|
58
|
-
end
|
56
|
+
check_shorthand(prop, script_string, values.split)
|
59
57
|
end
|
60
58
|
|
61
59
|
def check_shorthand(prop, node, values)
|
@@ -82,10 +80,10 @@ module SCSSLint
|
|
82
80
|
end
|
83
81
|
|
84
82
|
def can_condense_to_one_value(top, right, bottom, left)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
83
|
+
return unless top == right
|
84
|
+
|
85
|
+
top == bottom && (bottom == left || left.nil?) ||
|
86
|
+
bottom.nil? && left.nil?
|
89
87
|
end
|
90
88
|
|
91
89
|
def can_condense_to_two_values(top, right, bottom, left)
|
@@ -21,7 +21,7 @@ module SCSSLint
|
|
21
21
|
# Sass::Script::Nodes, we need to condense it into a single string that we
|
22
22
|
# can run a regex against.
|
23
23
|
def condense_to_string(sequence_list)
|
24
|
-
sequence_list.select { |item| item.is_a?(String) }.inject(:+)
|
24
|
+
sequence_list.select { |item| item.is_a?(String) }.inject(:+) || ''
|
25
25
|
end
|
26
26
|
|
27
27
|
# Removes extra spacing between lines in a comma-separated sequence due to
|
@@ -90,10 +90,9 @@ module SCSSLint
|
|
90
90
|
spaces += 1
|
91
91
|
offset += 1
|
92
92
|
end
|
93
|
+
next if spaces == EXPECTED_SPACES_AFTER_COMMA
|
93
94
|
|
94
|
-
|
95
|
-
add_lint arg, "Commas in #{arg_type} should be followed by a single space"
|
96
|
-
end
|
95
|
+
add_lint arg, "Commas in #{arg_type} should be followed by a single space"
|
97
96
|
end
|
98
97
|
end
|
99
98
|
end
|
@@ -5,9 +5,8 @@ module SCSSLint
|
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
7
|
def visit_prop(node)
|
8
|
-
|
9
|
-
|
10
|
-
end
|
8
|
+
return unless character_at(node.name_source_range.end_pos) != ':'
|
9
|
+
add_lint node, 'Property name should be immediately followed by a colon'
|
11
10
|
end
|
12
11
|
end
|
13
12
|
end
|
@@ -3,7 +3,7 @@ module SCSSLint
|
|
3
3
|
class Linter::SpaceBetweenParens < Linter
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
|
-
def visit_root(
|
6
|
+
def visit_root(_node)
|
7
7
|
@spaces = config['spaces']
|
8
8
|
|
9
9
|
engine.lines.each_with_index do |line, index|
|
@@ -15,22 +15,21 @@ module SCSSLint
|
|
15
15
|
[ ]*\)
|
16
16
|
)?
|
17
17
|
/x) do |match|
|
18
|
-
check(match[2], index
|
18
|
+
check(match[2], index) if match[2]
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
def check(str, index
|
25
|
+
def check(str, index)
|
26
26
|
spaces = str.count ' '
|
27
|
+
return if spaces == @spaces
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
@lints << Lint.new(engine.filename, location, message)
|
33
|
-
end
|
29
|
+
location = Location.new(index + 1)
|
30
|
+
message = "Expected #{pluralize(@spaces, 'space')} " \
|
31
|
+
"between parentheses instead of #{spaces}"
|
32
|
+
add_lint(location, message)
|
34
33
|
end
|
35
34
|
end
|
36
35
|
end
|
@@ -43,9 +43,8 @@ module SCSSLint
|
|
43
43
|
}x
|
44
44
|
|
45
45
|
def extract_string_without_quotes(source)
|
46
|
-
|
47
|
-
|
48
|
-
end
|
46
|
+
return unless match = STRING_WITHOUT_QUOTES_REGEX.match(source)
|
47
|
+
match[1]
|
49
48
|
end
|
50
49
|
|
51
50
|
def check_double_quotes(node, string)
|
@@ -16,22 +16,22 @@ module SCSSLint
|
|
16
16
|
|
17
17
|
def visit_script_number(node)
|
18
18
|
return unless match = REAL_NUMBER_REGEX.match(source_from_range(node.source_range))
|
19
|
+
return unless unnecessary_mantissa?(match[:mantissa])
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
21
|
+
add_lint(node, MESSAGE_FORMAT % [match[:number], match[:integer],
|
22
|
+
match[:units]])
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
26
26
|
|
27
|
-
REAL_NUMBER_REGEX =
|
27
|
+
REAL_NUMBER_REGEX = /
|
28
28
|
\b(?<number>
|
29
29
|
(?<integer>\d*)
|
30
30
|
\.
|
31
31
|
(?<mantissa>\d+)
|
32
32
|
(?<units>\w*)
|
33
33
|
)\b
|
34
|
-
|
34
|
+
/ix
|
35
35
|
|
36
36
|
MESSAGE_FORMAT = '`%s` should be written without the mantissa as `%s%s`'
|
37
37
|
|
@@ -18,9 +18,8 @@ module SCSSLint
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def check(node, string)
|
21
|
-
|
22
|
-
|
23
|
-
end
|
21
|
+
return unless string =~ /^\s*url\(\s*[^"']/
|
22
|
+
add_lint(node, 'URLs should be enclosed in quotes')
|
24
23
|
end
|
25
24
|
end
|
26
25
|
end
|
@@ -7,31 +7,33 @@ module SCSSLint
|
|
7
7
|
return unless node.type == :identifier
|
8
8
|
|
9
9
|
node.value.scan(ZERO_UNIT_REGEX) do |match|
|
10
|
+
next unless zero_with_length_units?(match.first)
|
10
11
|
add_lint(node, MESSAGE_FORMAT % match.first)
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
15
|
def visit_script_number(node)
|
15
16
|
length = source_from_range(node.source_range)[ZERO_UNIT_REGEX, 1]
|
17
|
+
return unless zero_with_length_units?(length)
|
16
18
|
|
17
|
-
|
18
|
-
add_lint(node, MESSAGE_FORMAT % length)
|
19
|
-
end
|
19
|
+
add_lint(node, MESSAGE_FORMAT % length)
|
20
20
|
end
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
|
-
ZERO_UNIT_REGEX =
|
24
|
+
ZERO_UNIT_REGEX = /
|
25
25
|
\b
|
26
|
-
(?<!\.|\#) # Ignore zeroes following `#` or `.` (
|
27
|
-
(0[a-z]+) # Zero followed by letters
|
26
|
+
(?<!\.|\#) # Ignore zeroes following `#` (colors) or `.` (decimals)
|
27
|
+
(0[a-z]+) # Zero followed by letters indicating some sort of unit
|
28
28
|
\b
|
29
|
-
|
29
|
+
/ix
|
30
30
|
|
31
31
|
MESSAGE_FORMAT = '`%s` should be written without units as `0`'
|
32
32
|
|
33
|
-
|
34
|
-
|
33
|
+
LENGTH_UNITS = %w[em ex ch rem vw vh vmin vmax cm mm in pt pc px].to_set
|
34
|
+
|
35
|
+
def zero_with_length_units?(string)
|
36
|
+
string =~ /^0([a-z]+)/ && LENGTH_UNITS.include?(Regexp.last_match(1))
|
35
37
|
end
|
36
38
|
end
|
37
39
|
end
|
data/lib/scss_lint/reporter.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Returns a YAML configuration where all linters are disabled which
|
3
|
+
# caused a lint.
|
4
|
+
class Reporter::ConfigReporter < Reporter
|
5
|
+
def report_lints
|
6
|
+
{ 'linters' => disabled_linters }.to_yaml unless lints.empty?
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def disabled_linters
|
12
|
+
linters.each_with_object({}) do |linter, m|
|
13
|
+
m[linter] = { 'enabled' => false }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def linters
|
18
|
+
lints.map { |lint| linter_name(lint.linter) }.compact.uniq.sort
|
19
|
+
end
|
20
|
+
|
21
|
+
def linter_name(linter)
|
22
|
+
return unless linter
|
23
|
+
linter.class.to_s.split('::').last
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -2,13 +2,13 @@ module SCSSLint
|
|
2
2
|
# Reports a single line per lint.
|
3
3
|
class Reporter::DefaultReporter < Reporter
|
4
4
|
def report_lints
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
5
|
+
return unless lints.any?
|
6
|
+
|
7
|
+
lints.map do |lint|
|
8
|
+
type = lint.error? ? '[E]'.color(:red) : '[W]'.color(:yellow)
|
9
|
+
"#{lint.filename.color(:cyan)}:" << "#{lint.location.line}".color(:magenta) <<
|
10
|
+
" #{type} #{lint.description}"
|
11
|
+
end.join("\n") + "\n"
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -9,10 +9,10 @@ module SCSSLint
|
|
9
9
|
output << "<file name=#{filename.encode(xml: :attr)}>"
|
10
10
|
|
11
11
|
file_lints.each do |lint|
|
12
|
-
output << "<issue line=\"#{lint.location.line}\" "
|
13
|
-
"column=\"#{lint.location.column}\" "
|
14
|
-
"length=\"#{lint.location.length}\" "
|
15
|
-
"severity=\"#{lint.severity}\" "
|
12
|
+
output << "<issue line=\"#{lint.location.line}\" " \
|
13
|
+
"column=\"#{lint.location.column}\" " \
|
14
|
+
"length=\"#{lint.location.length}\" " \
|
15
|
+
"severity=\"#{lint.severity}\" " \
|
16
16
|
"reason=#{lint.description.encode(xml: :attr)} />"
|
17
17
|
end
|
18
18
|
|
data/lib/scss_lint/runner.rb
CHANGED
@@ -49,9 +49,9 @@ module SCSSLint
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
rescue Sass::SyntaxError => ex
|
52
|
-
@lints << Lint.new(ex.sass_filename, Location.new(ex.sass_line), ex.to_s, :error)
|
52
|
+
@lints << Lint.new(nil, ex.sass_filename, Location.new(ex.sass_line), ex.to_s, :error)
|
53
53
|
rescue FileEncodingError => ex
|
54
|
-
@lints << Lint.new(file, Location.new, ex.to_s, :error)
|
54
|
+
@lints << Lint.new(nil, file, Location.new, ex.to_s, :error)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
data/lib/scss_lint/sass/tree.rb
CHANGED
@@ -15,7 +15,7 @@ module Sass::Tree
|
|
15
15
|
# Sass::Script::Variable nodes with no line numbers. This adds the line
|
16
16
|
# numbers back in so lint reporting works for those nodes.
|
17
17
|
def add_line_numbers_to_args(arg_list)
|
18
|
-
arg_list.each do |variable,
|
18
|
+
arg_list.each do |variable, _default_expr|
|
19
19
|
add_line_number(variable)
|
20
20
|
end
|
21
21
|
end
|
data/lib/scss_lint/utils.rb
CHANGED
@@ -20,17 +20,6 @@ module SCSSLint
|
|
20
20
|
.split
|
21
21
|
end
|
22
22
|
|
23
|
-
def shortest_hex_form(hex)
|
24
|
-
(can_be_condensed?(hex) ? (hex[0..1] + hex[3] + hex[5]) : hex).downcase
|
25
|
-
end
|
26
|
-
|
27
|
-
def can_be_condensed?(hex)
|
28
|
-
hex.length == 7 &&
|
29
|
-
hex[1] == hex[2] &&
|
30
|
-
hex[3] == hex[4] &&
|
31
|
-
hex[5] == hex[6]
|
32
|
-
end
|
33
|
-
|
34
23
|
# Takes a string like `hello "world" 'how are' you` and turns it into:
|
35
24
|
# `hello you`.
|
36
25
|
# This is useful for scanning for keywords in shorthand properties or lists
|
data/lib/scss_lint/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scss-lint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.24.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Causes Engineering
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-05-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rainbow
|
@@ -71,16 +71,16 @@ dependencies:
|
|
71
71
|
name: rubocop
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
73
73
|
requirements:
|
74
|
-
- - '
|
74
|
+
- - '='
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version:
|
76
|
+
version: 0.21.0
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
|
-
- - '
|
81
|
+
- - '='
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version:
|
83
|
+
version: 0.21.0
|
84
84
|
description: Configurable tool for writing clean and consistent SCSS
|
85
85
|
email:
|
86
86
|
- eng@causes.com
|
@@ -99,6 +99,7 @@ files:
|
|
99
99
|
- lib/scss_lint/reporter/files_reporter.rb
|
100
100
|
- lib/scss_lint/reporter/xml_reporter.rb
|
101
101
|
- lib/scss_lint/reporter/default_reporter.rb
|
102
|
+
- lib/scss_lint/reporter/config_reporter.rb
|
102
103
|
- lib/scss_lint/runner.rb
|
103
104
|
- lib/scss_lint/reporter.rb
|
104
105
|
- lib/scss_lint/lint.rb
|
@@ -112,7 +113,9 @@ files:
|
|
112
113
|
- lib/scss_lint/linter/space_after_comma.rb
|
113
114
|
- lib/scss_lint/linter/space_before_brace.rb
|
114
115
|
- lib/scss_lint/linter/capitalization_in_selector.rb
|
116
|
+
- lib/scss_lint/linter/hex_length.rb
|
115
117
|
- lib/scss_lint/linter/indentation.rb
|
118
|
+
- lib/scss_lint/linter/mergeable_selector.rb
|
116
119
|
- lib/scss_lint/linter/trailing_semicolon_after_property_value.rb
|
117
120
|
- lib/scss_lint/linter/shorthand.rb
|
118
121
|
- lib/scss_lint/linter/final_newline.rb
|
@@ -121,8 +124,8 @@ files:
|
|
121
124
|
- lib/scss_lint/linter/declaration_order.rb
|
122
125
|
- lib/scss_lint/linter/zero_unit.rb
|
123
126
|
- lib/scss_lint/linter/placeholder_in_extend.rb
|
124
|
-
- lib/scss_lint/linter/duplicate_root.rb
|
125
127
|
- lib/scss_lint/linter/selector_depth.rb
|
128
|
+
- lib/scss_lint/linter/hex_validation.rb
|
126
129
|
- lib/scss_lint/linter/compass/property_with_mixin.rb
|
127
130
|
- lib/scss_lint/linter/space_after_property_name.rb
|
128
131
|
- lib/scss_lint/linter/property_spelling.rb
|
@@ -134,7 +137,6 @@ files:
|
|
134
137
|
- lib/scss_lint/linter/debug_statement.rb
|
135
138
|
- lib/scss_lint/linter/duplicate_property.rb
|
136
139
|
- lib/scss_lint/linter/property_sort_order.rb
|
137
|
-
- lib/scss_lint/linter/hex_format.rb
|
138
140
|
- lib/scss_lint/linter/color_keyword.rb
|
139
141
|
- lib/scss_lint/linter/string_quotes.rb
|
140
142
|
- lib/scss_lint/linter/leading_zero.rb
|
@@ -142,6 +144,7 @@ files:
|
|
142
144
|
- lib/scss_lint/linter/comment.rb
|
143
145
|
- lib/scss_lint/linter/empty_rule.rb
|
144
146
|
- lib/scss_lint/linter/single_line_per_selector.rb
|
147
|
+
- lib/scss_lint/linter/hex_notation.rb
|
145
148
|
- lib/scss_lint/linter/url_format.rb
|
146
149
|
- lib/scss_lint/cli.rb
|
147
150
|
- lib/scss_lint/engine.rb
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module SCSSLint
|
2
|
-
# Checks for identical root selectors.
|
3
|
-
class Linter::DuplicateRoot < Linter
|
4
|
-
include LinterRegistry
|
5
|
-
|
6
|
-
def visit_root(node)
|
7
|
-
# Root rules are evaluated per document, so use new hashes for eash file
|
8
|
-
@roots = {}
|
9
|
-
yield # Continue linting children
|
10
|
-
end
|
11
|
-
|
12
|
-
def visit_rule(node)
|
13
|
-
if @roots[node.rule]
|
14
|
-
add_lint node.line,
|
15
|
-
"Merge root rule `#{node.rule.join}` with identical " \
|
16
|
-
"rule on line #{@roots[node.rule].line}"
|
17
|
-
else
|
18
|
-
@roots[node.rule] = node
|
19
|
-
end
|
20
|
-
|
21
|
-
# Don't yield so we only check one level deep
|
22
|
-
end
|
23
|
-
|
24
|
-
# Define stubs so we don't check rules nested in other constructs
|
25
|
-
%w[
|
26
|
-
directive
|
27
|
-
media
|
28
|
-
mixin
|
29
|
-
mixindef
|
30
|
-
].each do |node_type|
|
31
|
-
define_method("visit_#{node_type}") { |*args| }
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module SCSSLint
|
2
|
-
# Checks for hexadecimal colors that can be shortened.
|
3
|
-
class Linter::HexFormat < Linter
|
4
|
-
include LinterRegistry
|
5
|
-
|
6
|
-
def visit_script_color(node)
|
7
|
-
return unless color = source_from_range(node.source_range)[HEX_REGEX, 1]
|
8
|
-
|
9
|
-
unless valid_hex_format?(color)
|
10
|
-
add_hex_lint(node, color)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def visit_script_string(node)
|
15
|
-
return unless node.type == :identifier
|
16
|
-
|
17
|
-
node.value.scan(HEX_REGEX) do |match|
|
18
|
-
add_hex_lint(node, match.first) unless valid_hex_format?(match.first)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
HEX_REGEX = /(#\h{3,6})/
|
25
|
-
|
26
|
-
def add_hex_lint(node, hex)
|
27
|
-
add_lint(node, "Color `#{hex}` should be written as `#{shortest_hex_form(hex)}`")
|
28
|
-
end
|
29
|
-
|
30
|
-
def valid_hex_format?(hex)
|
31
|
-
hex == shortest_hex_form(hex)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|