scss-lint 0.23.1 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/config/default.yml +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
|