scss-lint 0.6 → 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/scss_lint/linter.rb +17 -15
- data/lib/scss_lint/linter/debug_linter.rb +5 -11
- data/lib/scss_lint/linter/declaration_order_linter.rb +17 -31
- data/lib/scss_lint/linter/empty_rule_linter.rb +6 -19
- data/lib/scss_lint/linter/hex_linter.rb +19 -31
- data/lib/scss_lint/linter/property_format_linter.rb +19 -30
- data/lib/scss_lint/linter/shorthand_linter.rb +34 -45
- data/lib/scss_lint/linter/single_line_per_selector_linter.rb +6 -19
- data/lib/scss_lint/linter/sorted_properties_linter.rb +12 -24
- data/lib/scss_lint/linter/type_in_id_selector_linter.rb +8 -23
- data/lib/scss_lint/linter/zero_unit_linter.rb +6 -19
- data/lib/scss_lint/runner.rb +7 -1
- data/lib/scss_lint/version.rb +1 -1
- metadata +2 -2
data/lib/scss_lint/linter.rb
CHANGED
@@ -1,25 +1,27 @@
|
|
1
1
|
module SCSSLint
|
2
|
-
class Linter
|
2
|
+
class Linter < Sass::Tree::Visitors::Base
|
3
3
|
include LinterRegistry
|
4
4
|
|
5
|
-
|
6
|
-
def run(engine)
|
7
|
-
[] # No lints
|
8
|
-
end
|
5
|
+
attr_reader :engine, :lints
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
def initialize
|
8
|
+
@lints = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(engine)
|
12
|
+
@engine = engine
|
13
|
+
visit(engine.tree)
|
14
|
+
end
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
def description
|
17
|
+
nil
|
18
|
+
end
|
17
19
|
|
18
|
-
|
20
|
+
protected
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
# Helper for creating lint from a parse tree node
|
23
|
+
def add_lint(node)
|
24
|
+
@lints << Lint.new(node.filename, node.line, description)
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -4,18 +4,12 @@ module SCSSLint
|
|
4
4
|
class Linter::DebugLinter < Linter
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
engine.tree.each do |node|
|
11
|
-
lints << create_lint(node) if node.is_a?(Sass::Tree::DebugNode)
|
12
|
-
end
|
13
|
-
lints
|
14
|
-
end
|
7
|
+
def visit_debug(node)
|
8
|
+
add_lint(node)
|
9
|
+
end
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
11
|
+
def description
|
12
|
+
'@debug line'
|
19
13
|
end
|
20
14
|
end
|
21
15
|
end
|
@@ -10,44 +10,30 @@ module SCSSLint
|
|
10
10
|
Sass::Tree::RuleNode,
|
11
11
|
]
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
engine.tree.each do |node|
|
17
|
-
if node.is_a?(Sass::Tree::RuleNode)
|
18
|
-
lints << check_order_of_declarations(node)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
lints.compact
|
22
|
-
end
|
13
|
+
def visit_rule(node)
|
14
|
+
children = node.children.select { |node| important_node?(node) }.
|
15
|
+
map { |node| node.class }
|
23
16
|
|
24
|
-
|
25
|
-
|
26
|
-
'properties and nested rule sets, in that order'
|
17
|
+
sorted_children = children.sort do |a, b|
|
18
|
+
DECLARATION_ORDER.index(a) <=> DECLARATION_ORDER.index(b)
|
27
19
|
end
|
28
20
|
|
29
|
-
|
30
|
-
|
31
|
-
def important_node?(node)
|
32
|
-
case node
|
33
|
-
when *DECLARATION_ORDER
|
34
|
-
true
|
35
|
-
end
|
21
|
+
if children != sorted_children
|
22
|
+
add_lint(node.children.first)
|
36
23
|
end
|
37
24
|
|
38
|
-
|
39
|
-
|
40
|
-
|
25
|
+
yield # Continue linting children
|
26
|
+
end
|
27
|
+
|
28
|
+
def description
|
29
|
+
'Rule sets should start with @extend declarations, followed by ' <<
|
30
|
+
'properties and nested rule sets, in that order'
|
31
|
+
end
|
41
32
|
|
42
|
-
|
43
|
-
sorted_children = children.sort do |x,y|
|
44
|
-
DECLARATION_ORDER.index(x) <=> DECLARATION_ORDER.index(y)
|
45
|
-
end
|
33
|
+
private
|
46
34
|
|
47
|
-
|
48
|
-
|
49
|
-
end
|
50
|
-
end
|
35
|
+
def important_node?(node)
|
36
|
+
DECLARATION_ORDER.include? node.class
|
51
37
|
end
|
52
38
|
end
|
53
39
|
end
|
@@ -4,26 +4,13 @@ module SCSSLint
|
|
4
4
|
class Linter::EmptyRuleLinter < Linter
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
if node.is_a?(Sass::Tree::RuleNode)
|
12
|
-
lints << check_empty_rule(node)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
lints.compact
|
16
|
-
end
|
17
|
-
|
18
|
-
def description
|
19
|
-
'Empty rule'
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
7
|
+
def visit_rule(node)
|
8
|
+
add_lint(node) if node.children.empty?
|
9
|
+
yield # Continue linting children
|
10
|
+
end
|
23
11
|
|
24
|
-
|
25
|
-
|
26
|
-
end
|
12
|
+
def description
|
13
|
+
'Empty rule'
|
27
14
|
end
|
28
15
|
end
|
29
16
|
end
|
@@ -4,42 +4,30 @@ module SCSSLint
|
|
4
4
|
class Linter::HexLinter < Linter
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
if node.is_a?(Sass::Tree::PropNode)
|
12
|
-
lints << check_valid_hex_value(node)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
lints.compact
|
16
|
-
end
|
17
|
-
|
18
|
-
def description
|
19
|
-
'Hexadecimal color codes should be lowercase and in 3-digit form where possible'
|
7
|
+
def visit_prop(node)
|
8
|
+
if node.value.is_a?(Sass::Script::String) &&
|
9
|
+
node.value.to_s =~ /#(\h{3,6})/
|
10
|
+
add_lint(node) unless valid_hex?($1)
|
20
11
|
end
|
12
|
+
end
|
21
13
|
|
22
|
-
|
14
|
+
def description
|
15
|
+
'Hexadecimal color codes should be lowercase and in 3-digit form where possible'
|
16
|
+
end
|
23
17
|
|
24
|
-
|
25
|
-
if prop_node.value.is_a?(Sass::Script::String) &&
|
26
|
-
prop_node.value.to_s =~ /#(\h{3,6})/
|
27
|
-
return create_lint(prop_node) unless valid_hex?($1)
|
28
|
-
end
|
29
|
-
end
|
18
|
+
private
|
30
19
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
20
|
+
def valid_hex?(hex)
|
21
|
+
[3,6].include?(hex.length) &&
|
22
|
+
hex.downcase == hex &&
|
23
|
+
!can_be_condensed(hex)
|
24
|
+
end
|
36
25
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
26
|
+
def can_be_condensed(hex)
|
27
|
+
hex.length == 6 &&
|
28
|
+
hex[0] == hex[1] &&
|
29
|
+
hex[2] == hex[3] &&
|
30
|
+
hex[4] == hex[5]
|
43
31
|
end
|
44
32
|
end
|
45
33
|
end
|
@@ -4,38 +4,27 @@ module SCSSLint
|
|
4
4
|
class Linter::PropertyFormatLinter < Linter
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
if node.is_a?(Sass::Tree::PropNode)
|
12
|
-
lints << check_property_format(node, engine.lines[node.line - 1]) if node.line
|
13
|
-
end
|
14
|
-
end
|
15
|
-
lints.compact
|
16
|
-
end
|
17
|
-
|
18
|
-
def description
|
19
|
-
'Property declarations should always be on one line of the form ' <<
|
20
|
-
'`name: value;` or `name: [value] {` ' <<
|
21
|
-
'(are you missing a trailing semi-colon?)'
|
22
|
-
end
|
7
|
+
def visit_prop(node)
|
8
|
+
line = engine.lines[node.line - 1] if node.line
|
9
|
+
add_lint(node) unless line =~ PROPERTY_RE
|
10
|
+
end
|
23
11
|
|
24
|
-
|
12
|
+
def description
|
13
|
+
'Property declarations should always be on one line of the form ' <<
|
14
|
+
'`name: value;` or `name: [value] {` ' <<
|
15
|
+
'(are you missing a trailing semi-colon?)'
|
16
|
+
end
|
25
17
|
|
26
|
-
|
27
|
-
PROPERTY_RE = %r{
|
28
|
-
^\s*[\w-]+:\s # property name, colon, one space
|
29
|
-
( # followed by
|
30
|
-
#{VALUE_RE}; # property and terminating semi-colon eg. a b c;
|
31
|
-
| # or
|
32
|
-
((#{VALUE_RE}\s)?\{) # nested property, optional value, trailing curly
|
33
|
-
)
|
34
|
-
}x
|
18
|
+
private
|
35
19
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
20
|
+
VALUE_RE = %r{(\S+\s)*\S+} # eg. "10px", "10px normal"
|
21
|
+
PROPERTY_RE = %r{
|
22
|
+
^\s*[\w-]+:\s # property name, colon, one space
|
23
|
+
( # followed by
|
24
|
+
#{VALUE_RE}; # property and terminating semi-colon eg. a b c;
|
25
|
+
| # or
|
26
|
+
((#{VALUE_RE}\s)?\{) # nested property, optional value, trailing curly
|
27
|
+
)
|
28
|
+
}x
|
40
29
|
end
|
41
30
|
end
|
@@ -4,56 +4,45 @@ module SCSSLint
|
|
4
4
|
class Linter::ShorthandLinter < Linter
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
engine.tree.each do |node|
|
11
|
-
if node.is_a?(Sass::Tree::PropNode)
|
12
|
-
lints << check_valid_shorthand_value(node)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
lints.compact
|
7
|
+
def visit_prop(node)
|
8
|
+
unless SHORTHANDABLE_PROPERTIES.include? node.name.first.to_s
|
9
|
+
return
|
16
10
|
end
|
17
11
|
|
18
|
-
|
19
|
-
|
12
|
+
if node.value.to_sass.strip =~ /\A(\S+\s+\S+(\s+\S+){0,2})\Z/
|
13
|
+
add_lint(node) unless valid_shorthand?($1)
|
20
14
|
end
|
15
|
+
end
|
21
16
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
border-style
|
26
|
-
border-width
|
27
|
-
margin
|
28
|
-
padding]
|
29
|
-
|
30
|
-
def check_valid_shorthand_value(prop_node)
|
31
|
-
unless SHORTHANDABLE_PROPERTIES.include? prop_node.name.first.to_s
|
32
|
-
return
|
33
|
-
end
|
34
|
-
|
35
|
-
if prop_node.value.to_s.strip =~ /\A(\S+\s+\S+(\s+\S+){0,2})\Z/
|
36
|
-
return create_lint(prop_node) unless valid_shorthand?($1)
|
37
|
-
end
|
38
|
-
end
|
17
|
+
def description
|
18
|
+
'Property values should use the shortest shorthand syntax allowed'
|
19
|
+
end
|
39
20
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
21
|
+
private
|
22
|
+
|
23
|
+
SHORTHANDABLE_PROPERTIES = %w[border-color
|
24
|
+
border-radius
|
25
|
+
border-style
|
26
|
+
border-width
|
27
|
+
margin
|
28
|
+
padding]
|
29
|
+
|
30
|
+
def valid_shorthand?(shorthand)
|
31
|
+
values = shorthand.split(/\s+/)
|
32
|
+
top, right, bottom, left = values
|
33
|
+
|
34
|
+
if top == right && right == bottom && bottom == left
|
35
|
+
false
|
36
|
+
elsif top == right && bottom.nil? && left.nil?
|
37
|
+
false
|
38
|
+
elsif top == bottom && right == left
|
39
|
+
false
|
40
|
+
elsif top == bottom && left.nil?
|
41
|
+
false
|
42
|
+
elsif right == left
|
43
|
+
false
|
44
|
+
else
|
45
|
+
true
|
57
46
|
end
|
58
47
|
end
|
59
48
|
end
|
@@ -4,26 +4,13 @@ module SCSSLint
|
|
4
4
|
class Linter::SingleLinePerSelector < Linter
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
if node.is_a?(Sass::Tree::RuleNode)
|
12
|
-
lints << check_selector_format(node)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
lints.compact
|
16
|
-
end
|
17
|
-
|
18
|
-
def description
|
19
|
-
'Each selector should be on its own line'
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
7
|
+
def visit_rule(node)
|
8
|
+
add_lint(node) unless node.rule.grep(/,[^\n]/).empty?
|
9
|
+
yield # Continue linting children
|
10
|
+
end
|
23
11
|
|
24
|
-
|
25
|
-
|
26
|
-
end
|
12
|
+
def description
|
13
|
+
'Each selector should be on its own line'
|
27
14
|
end
|
28
15
|
end
|
29
16
|
end
|
@@ -4,36 +4,24 @@ module SCSSLint
|
|
4
4
|
class Linter::SortedPropertiesLinter < Linter
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
engine.tree.each do |node|
|
11
|
-
if node.is_a?(Sass::Tree::RuleNode)
|
12
|
-
lints << check_properties_sorted(node)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
lints.compact
|
7
|
+
def visit_rule(node)
|
8
|
+
properties = node.children.select do |child|
|
9
|
+
child.is_a?(Sass::Tree::PropNode)
|
16
10
|
end
|
17
11
|
|
18
|
-
|
19
|
-
|
12
|
+
prop_names = properties.map do |prop_node|
|
13
|
+
prop_node.name.first.to_s
|
20
14
|
end
|
21
15
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
properties = rule_node.children.select do |node|
|
26
|
-
node.is_a?(Sass::Tree::PropNode)
|
27
|
-
end
|
16
|
+
if prop_names.sort != prop_names
|
17
|
+
add_lint(properties.first)
|
18
|
+
end
|
28
19
|
|
29
|
-
|
30
|
-
|
31
|
-
end
|
20
|
+
yield # Continue linting children
|
21
|
+
end
|
32
22
|
|
33
|
-
|
34
|
-
|
35
|
-
end
|
36
|
-
end
|
23
|
+
def description
|
24
|
+
'Properties should be sorted in alphabetical order'
|
37
25
|
end
|
38
26
|
end
|
39
27
|
end
|
@@ -4,32 +4,17 @@ module SCSSLint
|
|
4
4
|
class Linter::TypeInIdSelectorLinter < Linter
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
if node.is_a?(Sass::Tree::RuleNode)
|
12
|
-
lints << check_type_in_selector(node)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
lints.compact
|
7
|
+
def visit_rule(node)
|
8
|
+
selectors = node.rule.first.to_s.split(',')
|
9
|
+
selectors.each do |selector|
|
10
|
+
add_lint(node) if selector.strip =~ /^[a-z0-9]+#.*/i
|
16
11
|
end
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def check_type_in_selector(rule_node)
|
25
|
-
selectors = rule_node.rule.first.to_s.split(',')
|
26
|
-
|
27
|
-
selectors.each do |selector|
|
28
|
-
return create_lint(rule_node) if selector.strip =~ /^[a-z0-9]+#.*/i
|
29
|
-
end
|
13
|
+
yield # Continue linting children
|
14
|
+
end
|
30
15
|
|
31
|
-
|
32
|
-
|
16
|
+
def description
|
17
|
+
'Avoid ID names with unnecessary type selectors (e.g. prefer `#id` over `p#id`)'
|
33
18
|
end
|
34
19
|
end
|
35
20
|
end
|
@@ -4,26 +4,13 @@ module SCSSLint
|
|
4
4
|
class Linter::ZeroUnitLinter < Linter
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
if node.is_a?(Sass::Tree::PropNode)
|
12
|
-
lints << check_zero_unit(node, engine.lines[node.line - 1]) if node.line
|
13
|
-
end
|
14
|
-
end
|
15
|
-
lints.compact
|
16
|
-
end
|
17
|
-
|
18
|
-
def description
|
19
|
-
'Properties with a value of zero should be unit-less'
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
7
|
+
def visit_prop(node)
|
8
|
+
line = engine.lines[node.line - 1] if node.line
|
9
|
+
add_lint(node) if line =~ /^\s*[\w-]+:\s*0[a-z]+;$/i
|
10
|
+
end
|
23
11
|
|
24
|
-
|
25
|
-
|
26
|
-
end
|
12
|
+
def description
|
13
|
+
'Properties with a value of zero should be unit-less, e.g. "0" instead of "0px"'
|
27
14
|
end
|
28
15
|
end
|
29
16
|
end
|
data/lib/scss_lint/runner.rb
CHANGED
@@ -16,6 +16,8 @@ module SCSSLint
|
|
16
16
|
|
17
17
|
@linters = LinterRegistry.linters.reject do |linter|
|
18
18
|
ignored_linters.include?(linter)
|
19
|
+
end.map do |linter_class|
|
20
|
+
linter_class.new
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
@@ -26,13 +28,17 @@ module SCSSLint
|
|
26
28
|
files.each do |file|
|
27
29
|
find_lints(file)
|
28
30
|
end
|
31
|
+
|
32
|
+
linters.each do |linter|
|
33
|
+
@lints += linter.lints
|
34
|
+
end
|
29
35
|
end
|
30
36
|
|
31
37
|
def find_lints(file)
|
32
38
|
engine = Engine.new(file)
|
33
39
|
|
34
40
|
linters.each do |linter|
|
35
|
-
|
41
|
+
linter.run(engine)
|
36
42
|
end
|
37
43
|
rescue Sass::SyntaxError => ex
|
38
44
|
@lints << Lint.new(ex.sass_filename, ex.sass_line, ex.to_s)
|
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:
|
4
|
+
version: 0.6.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: colorize
|