scss-lint 0.10.1 → 0.11.1
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/lib/sass/script.rb +71 -37
- data/lib/sass/tree.rb +11 -6
- data/lib/scss_lint/lint.rb +7 -2
- data/lib/scss_lint/linter.rb +15 -4
- data/lib/scss_lint/linter/color_keyword.rb +7 -14
- data/lib/scss_lint/linter/duplicate_property.rb +23 -0
- data/lib/scss_lint/linter/hex_format.rb +3 -3
- data/lib/scss_lint/linter/indentation.rb +71 -0
- data/lib/scss_lint/linter/leading_zero.rb +32 -0
- data/lib/scss_lint/linter/shorthand.rb +46 -26
- data/lib/scss_lint/linter/zero_unit.rb +4 -10
- data/lib/scss_lint/reporter/default_reporter.rb +3 -1
- data/lib/scss_lint/reporter/xml_reporter.rb +3 -1
- data/lib/scss_lint/runner.rb +1 -1
- data/lib/scss_lint/utils.rb +21 -0
- data/lib/scss_lint/version.rb +1 -1
- metadata +20 -17
- data/lib/scss_lint/linter/no_zero_before_decimal.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62980ca32775a70ec7170fc8b7399eb13b304c7b
|
4
|
+
data.tar.gz: 76fa2ea621edad2ad9ebc9d0e2b302b57c43a70d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce7dfa08c1c68044da63de6f376a3ce483e5145eaa7854668b616dbd0b1596c983e7dbc5f510c8cdbf3d27f47658b21cebe5f0caa63b9c9a888ef0d0df772729
|
7
|
+
data.tar.gz: dae5b62b186b24c851394b7e18c16e45f4f1281a2046c7f6a00513deb94d8300df30e8a5e7e1d3845c77f9efe68f1fef5c9e4f059239499b55e56cf4ff24f0c4
|
data/lib/sass/script.rb
CHANGED
@@ -1,12 +1,58 @@
|
|
1
|
-
# Contains extensions of Sass::Script::Nodes to add support for accessing
|
2
|
-
# various parts of the parse tree not provided out-of-the-box.
|
3
1
|
module Sass::Script
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def
|
9
|
-
|
2
|
+
# Redefine some of the lexer helpers in order to store the original string
|
3
|
+
# with the created object so that the original string can be inspected rather
|
4
|
+
# than a typically normalized version.
|
5
|
+
class Lexer
|
6
|
+
def color
|
7
|
+
return unless color_string = scan(REGULAR_EXPRESSIONS[:color])
|
8
|
+
|
9
|
+
unless [4, 7].include?(color_string.length)
|
10
|
+
raise ::Sass::SyntaxError,
|
11
|
+
"Colors must have either three or six digits: '#{color_string}'"
|
12
|
+
end
|
13
|
+
|
14
|
+
[:color, Value::Color.from_string(color_string)]
|
15
|
+
end
|
16
|
+
|
17
|
+
def number
|
18
|
+
return unless scan(REGULAR_EXPRESSIONS[:number])
|
19
|
+
value = @scanner[2] ? @scanner[2].to_f : @scanner[3].to_i
|
20
|
+
value = -value if @scanner[1]
|
21
|
+
|
22
|
+
number = Value::Number.new(value, Array(@scanner[4])).tap do |num|
|
23
|
+
num.original_string = @scanner[0]
|
24
|
+
end
|
25
|
+
[:number, number]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Parser
|
30
|
+
# We redefine the ident parser to specially handle color keywords.
|
31
|
+
def ident
|
32
|
+
return funcall unless @lexer.peek && @lexer.peek.type == :ident
|
33
|
+
return if @stop_at && @stop_at.include?(@lexer.peek.value)
|
34
|
+
|
35
|
+
name = @lexer.next
|
36
|
+
if (color = Value::Color::COLOR_NAMES[name.value.downcase])
|
37
|
+
return literal_node(Value::Color.from_string(name.value, color), name.source_range)
|
38
|
+
end
|
39
|
+
literal_node(Value::String.new(name.value, :identifier), name.source_range)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Value::Base
|
44
|
+
attr_accessor :node_parent
|
45
|
+
|
46
|
+
def children
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
50
|
+
def line
|
51
|
+
@line || (node_parent && node_parent.line)
|
52
|
+
end
|
53
|
+
|
54
|
+
def source_range
|
55
|
+
@source_range || (node_parent && node_parent.source_range)
|
10
56
|
end
|
11
57
|
end
|
12
58
|
|
@@ -14,8 +60,8 @@ module Sass::Script
|
|
14
60
|
# color string. This adds an attribute to the Color to keep track of the
|
15
61
|
# original string and provides a method which the modified lexer can use to
|
16
62
|
# set it.
|
17
|
-
class Color
|
18
|
-
attr_accessor :
|
63
|
+
class Value::Color
|
64
|
+
attr_accessor :original_string
|
19
65
|
|
20
66
|
def self.from_string(string, rgb = nil)
|
21
67
|
unless rgb
|
@@ -24,41 +70,29 @@ module Sass::Script
|
|
24
70
|
map { |hex| hex.ljust(2, hex).to_i(16) }
|
25
71
|
end
|
26
72
|
|
27
|
-
color =
|
28
|
-
color.
|
73
|
+
color = new(rgb, false)
|
74
|
+
color.original_string = string
|
29
75
|
color
|
30
76
|
end
|
31
77
|
end
|
32
78
|
|
33
|
-
class
|
34
|
-
|
35
|
-
# `Color` object so that we can inspect the original string before it is
|
36
|
-
# normalized.
|
37
|
-
#
|
38
|
-
# This is an adapted version from the original Sass source code.
|
39
|
-
def color
|
40
|
-
return unless color_string = scan(REGULAR_EXPRESSIONS[:color])
|
41
|
-
|
42
|
-
unless [4, 7].include?(color_string.length)
|
43
|
-
raise ::Sass::SyntaxError,
|
44
|
-
"Colors must have either three or six digits: '#{color_string}'"
|
45
|
-
end
|
46
|
-
|
47
|
-
[:color, Color.from_string(color_string)]
|
48
|
-
end
|
79
|
+
class Value::Number
|
80
|
+
attr_accessor :original_string
|
49
81
|
end
|
50
82
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
83
|
+
# Contains extensions of Sass::Script::Tree::Nodes to add support for
|
84
|
+
# accessing various parts of the parse tree not provided out-of-the-box.
|
85
|
+
module Tree
|
86
|
+
class Node
|
87
|
+
attr_accessor :node_parent
|
88
|
+
end
|
56
89
|
|
57
|
-
|
58
|
-
|
59
|
-
|
90
|
+
class Literal
|
91
|
+
# Literals wrap their underlying values. For sake of convenience, consider
|
92
|
+
# the wrapped value a child of the Literal.
|
93
|
+
def children
|
94
|
+
[value]
|
60
95
|
end
|
61
|
-
node(Sass::Script::String.new(name.value, :identifier))
|
62
96
|
end
|
63
97
|
end
|
64
98
|
end
|
data/lib/sass/tree.rb
CHANGED
@@ -5,6 +5,9 @@
|
|
5
5
|
module Sass::Tree
|
6
6
|
# Define some common helper code for use in the various monkey patchings.
|
7
7
|
class Node
|
8
|
+
# Stores node for which this node is a direct child
|
9
|
+
attr_accessor :node_parent
|
10
|
+
|
8
11
|
# The `args` field of some Sass::Tree::Node classes returns
|
9
12
|
# Sass::Script::Variable nodes with no line numbers. This adds the line
|
10
13
|
# numbers back in so lint reporting works for those nodes.
|
@@ -17,7 +20,7 @@ module Sass::Tree
|
|
17
20
|
# The Sass parser sometimes doesn't assign line numbers in cases where it
|
18
21
|
# should. This is a helper to easily correct that.
|
19
22
|
def add_line_number(node)
|
20
|
-
node.line ||= line if node.is_a?(Sass::Script::Node)
|
23
|
+
node.line ||= line if node.is_a?(::Sass::Script::Tree::Node)
|
21
24
|
node
|
22
25
|
end
|
23
26
|
|
@@ -25,7 +28,7 @@ module Sass::Tree
|
|
25
28
|
# the name of the variable. This helper takes that name and turns it back
|
26
29
|
# into a Sass::Script::Variable that supports lint reporting.
|
27
30
|
def create_variable(var_name)
|
28
|
-
Sass::Script::Variable.new(var_name).tap do |v|
|
31
|
+
::Sass::Script::Tree::Variable.new(var_name).tap do |v|
|
29
32
|
v.line = line # Use line number of the containing parse tree node
|
30
33
|
end
|
31
34
|
end
|
@@ -34,7 +37,7 @@ module Sass::Tree
|
|
34
37
|
# Sass::Script::Nodes interspersed within them. This returns a filtered list
|
35
38
|
# of just those nodes.
|
36
39
|
def extract_script_nodes(list)
|
37
|
-
list.select { |item| item.is_a?
|
40
|
+
list.select { |item| item.is_a?(::Sass::Script::Tree::Node) }
|
38
41
|
end
|
39
42
|
|
40
43
|
# Takes a list of arguments, be they arrays or individual objects, and
|
@@ -70,7 +73,9 @@ module Sass::Tree
|
|
70
73
|
|
71
74
|
class EachNode
|
72
75
|
def children
|
73
|
-
|
76
|
+
loop_vars = vars.map { |var| create_variable(var) }
|
77
|
+
|
78
|
+
concat_expr_lists super, loop_vars, list
|
74
79
|
end
|
75
80
|
end
|
76
81
|
|
@@ -114,9 +119,9 @@ module Sass::Tree
|
|
114
119
|
|
115
120
|
# Keyword mapping is String -> Expr, so convert the string to a variable
|
116
121
|
# node that supports lint reporting
|
117
|
-
keyword_exprs = keywords.map do |var_name, var_expr|
|
122
|
+
keyword_exprs = keywords.as_stored.map do |var_name, var_expr|
|
118
123
|
[create_variable(var_name), var_expr]
|
119
|
-
end
|
124
|
+
end if keywords.any?
|
120
125
|
|
121
126
|
concat_expr_lists super, args, keyword_exprs, splat
|
122
127
|
end
|
data/lib/scss_lint/lint.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
module SCSSLint
|
2
2
|
class Lint
|
3
|
-
attr_reader :filename, :line, :description
|
3
|
+
attr_reader :filename, :line, :description, :severity
|
4
4
|
|
5
|
-
def initialize(filename, line, description)
|
5
|
+
def initialize(filename, line, description, severity = :warning)
|
6
6
|
@filename = filename
|
7
7
|
@line = line
|
8
8
|
@description = description
|
9
|
+
@severity = severity
|
10
|
+
end
|
11
|
+
|
12
|
+
def error?
|
13
|
+
severity == :error
|
9
14
|
end
|
10
15
|
end
|
11
16
|
end
|
data/lib/scss_lint/linter.rb
CHANGED
@@ -20,17 +20,20 @@ module SCSSLint
|
|
20
20
|
end
|
21
21
|
|
22
22
|
# Helper for creating lint from a parse tree node
|
23
|
-
def add_lint(
|
23
|
+
def add_lint(node_or_line, message = nil)
|
24
|
+
line = node_or_line.respond_to?(:line) ? node_or_line.line : node_or_line
|
25
|
+
|
24
26
|
@lints << Lint.new(engine.filename,
|
25
|
-
|
27
|
+
line,
|
26
28
|
message || description)
|
27
29
|
end
|
28
30
|
|
29
31
|
# Monkey-patched implementation that adds support for traversing
|
30
32
|
# Sass::Script::Nodes (original implementation only supports
|
31
33
|
# Sass::Tree::Nodes).
|
32
|
-
def node_name(node)
|
33
|
-
|
34
|
+
def self.node_name(node)
|
35
|
+
case node
|
36
|
+
when Sass::Script::Tree::Node, Sass::Script::Value::Base
|
34
37
|
"script_#{node.class.name.gsub(/.*::(.*?)$/, '\\1').downcase}"
|
35
38
|
else
|
36
39
|
super
|
@@ -46,5 +49,13 @@ module SCSSLint
|
|
46
49
|
|
47
50
|
super
|
48
51
|
end
|
52
|
+
|
53
|
+
# Redefine so we can set the `node_parent` of each node
|
54
|
+
def visit_children(parent)
|
55
|
+
parent.children.each do |child|
|
56
|
+
child.node_parent = parent
|
57
|
+
visit(child)
|
58
|
+
end
|
59
|
+
end
|
49
60
|
end
|
50
61
|
end
|
@@ -3,7 +3,7 @@ module SCSSLint
|
|
3
3
|
include LinterRegistry
|
4
4
|
|
5
5
|
def visit_script_color(node)
|
6
|
-
add_color_lint(node, node.
|
6
|
+
add_color_lint(node, node.original_string) if color_keyword?(node.original_string)
|
7
7
|
end
|
8
8
|
|
9
9
|
def visit_script_string(node)
|
@@ -17,28 +17,21 @@ module SCSSLint
|
|
17
17
|
private
|
18
18
|
|
19
19
|
def add_color_lint(node, original)
|
20
|
-
hex_form = Sass::Script::Color.new(color_rgb(original)).
|
20
|
+
hex_form = Sass::Script::Value::Color.new(color_rgb(original)).tap do |color|
|
21
|
+
color.options = {} # `inspect` requires options to be set
|
22
|
+
end.inspect
|
23
|
+
|
21
24
|
add_lint(node,
|
22
25
|
"Color `#{original}` should be written in hexadecimal form " <<
|
23
26
|
"as `#{shortest_hex_form(hex_form)}`")
|
24
27
|
end
|
25
28
|
|
26
29
|
def color_keyword?(string)
|
27
|
-
!!color_rgb(string)
|
30
|
+
!!color_rgb(string) && string != 'transparent'
|
28
31
|
end
|
29
32
|
|
30
33
|
def color_rgb(string)
|
31
|
-
Sass::Script::Color::COLOR_NAMES[string]
|
32
|
-
end
|
33
|
-
|
34
|
-
# Takes a string like `hello "world" 'how are' you` and turns it into:
|
35
|
-
# `hello you`.
|
36
|
-
# This is useful for scanning for keywords in shorthand properties or lists
|
37
|
-
# which can contain quoted strings but for which you don't want to inspect
|
38
|
-
# quoted strings (e.g. you care about the actual color keyword `red`, not
|
39
|
-
# the string "red").
|
40
|
-
def remove_quoted_strings(string)
|
41
|
-
string.gsub(/"[^"]*"|'[^']*'/, '')
|
34
|
+
Sass::Script::Value::Color::COLOR_NAMES[string]
|
42
35
|
end
|
43
36
|
end
|
44
37
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
class Linter::DuplicateProperty < Linter
|
3
|
+
include LinterRegistry
|
4
|
+
|
5
|
+
def visit_rule(node)
|
6
|
+
properties = node.children
|
7
|
+
.select { |child| child.is_a?(Sass::Tree::PropNode) }
|
8
|
+
.reject { |prop| prop.name.any? { |item| item.is_a?(Sass::Script::Node) } }
|
9
|
+
|
10
|
+
prop_names = {}
|
11
|
+
|
12
|
+
properties.each do |prop|
|
13
|
+
name = prop.name.join
|
14
|
+
|
15
|
+
if existing_prop = prop_names[name]
|
16
|
+
add_lint(prop, "Property '#{name}' already defined on line #{existing_prop.line}")
|
17
|
+
else
|
18
|
+
prop_names[name] = prop
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -3,10 +3,10 @@ module SCSSLint
|
|
3
3
|
include LinterRegistry
|
4
4
|
|
5
5
|
def visit_script_color(node)
|
6
|
-
return unless node.
|
6
|
+
return unless node.original_string && node.original_string.match(HEX_REGEX)
|
7
7
|
|
8
|
-
unless valid_hex_format?(node.
|
9
|
-
add_hex_lint(node, node.
|
8
|
+
unless valid_hex_format?(node.original_string[HEX_REGEX, 1])
|
9
|
+
add_hex_lint(node, node.original_string)
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
class Linter::Indentation < Linter
|
3
|
+
include LinterRegistry
|
4
|
+
|
5
|
+
def visit_root(node)
|
6
|
+
@indent = 0
|
7
|
+
yield
|
8
|
+
end
|
9
|
+
|
10
|
+
def check_and_visit_children(node)
|
11
|
+
# Don't continue checking children as the moment a parent's indentation is
|
12
|
+
# off it's likely the children will be as will. We don't display the child
|
13
|
+
# indentation problems as that would likely make the lint too noisy.
|
14
|
+
return if check_indentation(node)
|
15
|
+
|
16
|
+
@indent += INDENT_WIDTH
|
17
|
+
yield
|
18
|
+
@indent -= INDENT_WIDTH
|
19
|
+
end
|
20
|
+
|
21
|
+
def check_indentation(node)
|
22
|
+
return unless node.line
|
23
|
+
|
24
|
+
# Ignore the case where the node is on the same line as its previous
|
25
|
+
# sibling or its parent, as indentation isn't possible
|
26
|
+
return if (previous = previous_node(node)) && previous.line == node.line
|
27
|
+
|
28
|
+
actual_indent = engine.lines[node.line - 1][/^(\s*)/, 1]
|
29
|
+
|
30
|
+
if actual_indent.length != @indent
|
31
|
+
add_lint(node.line,
|
32
|
+
"Line should be indented #{@indent} spaces, " <<
|
33
|
+
"but was indented #{actual_indent.length} spaces")
|
34
|
+
return true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Deal with `else` statements
|
39
|
+
def visit_if(node, &block)
|
40
|
+
check_and_visit_children(node, &block)
|
41
|
+
visit(node.else) if node.else
|
42
|
+
end
|
43
|
+
|
44
|
+
# Define node types that increase indentation level
|
45
|
+
alias :visit_directive :check_and_visit_children
|
46
|
+
alias :visit_each :check_and_visit_children
|
47
|
+
alias :visit_for :check_and_visit_children
|
48
|
+
alias :visit_function :check_and_visit_children
|
49
|
+
alias :visit_media :check_and_visit_children
|
50
|
+
alias :visit_mixin :check_and_visit_children
|
51
|
+
alias :visit_mixindef :check_and_visit_children
|
52
|
+
alias :visit_prop :check_and_visit_children
|
53
|
+
alias :visit_rule :check_and_visit_children
|
54
|
+
alias :visit_supports :check_and_visit_children
|
55
|
+
alias :visit_while :check_and_visit_children
|
56
|
+
|
57
|
+
# Define node types to check indentation of (notice comments are left out)
|
58
|
+
alias :visit_charset :check_indentation
|
59
|
+
alias :visit_content :check_indentation
|
60
|
+
alias :visit_cssimport :check_indentation
|
61
|
+
alias :visit_extend :check_indentation
|
62
|
+
alias :visit_import :check_indentation
|
63
|
+
alias :visit_return :check_indentation
|
64
|
+
alias :visit_variable :check_indentation
|
65
|
+
alias :visit_warn :check_indentation
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
INDENT_WIDTH = 2
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
class Linter::LeadingZero < Linter
|
3
|
+
include LinterRegistry
|
4
|
+
|
5
|
+
def visit_script_string(node)
|
6
|
+
return unless node.type == :identifier
|
7
|
+
|
8
|
+
non_string_values = remove_quoted_strings(node.value).split
|
9
|
+
|
10
|
+
non_string_values.each do |value|
|
11
|
+
if number = value[/\b(0\.\d+)/, 1]
|
12
|
+
add_leading_zero_lint(node, number)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def visit_script_number(node)
|
18
|
+
if node.original_string =~ /^0\./
|
19
|
+
add_leading_zero_lint(node, node.original_string)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def add_leading_zero_lint(node, number)
|
26
|
+
trimmed_number = number[/^[^\.]+(.*)$/, 1]
|
27
|
+
|
28
|
+
add_lint(node, "`#{number}` should be written without a leading zero " <<
|
29
|
+
"as `#{trimmed_number}`")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -3,47 +3,67 @@ module SCSSLint
|
|
3
3
|
include LinterRegistry
|
4
4
|
|
5
5
|
def visit_prop(node)
|
6
|
-
|
6
|
+
property_name = node.name.join
|
7
|
+
return unless SHORTHANDABLE_PROPERTIES.include?(property_name)
|
7
8
|
|
8
9
|
case node.value
|
9
|
-
when Sass::Script::
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
when Sass::Script::
|
15
|
-
|
16
|
-
add_lint(node) unless valid_shorthand?(*$1.split(/\s+/))
|
17
|
-
end
|
10
|
+
when Sass::Script::Tree::Literal
|
11
|
+
# HACK: node_parent may not be initialized at this point, so we need to
|
12
|
+
# set it ourselves
|
13
|
+
node.value.value.node_parent = node.value
|
14
|
+
check_script_string(property_name, node.value.value)
|
15
|
+
when Sass::Script::Tree::ListLiteral
|
16
|
+
check_script_list(property_name, node.value)
|
18
17
|
end
|
19
18
|
end
|
20
19
|
|
21
|
-
|
22
|
-
|
20
|
+
private
|
21
|
+
|
22
|
+
SHORTHANDABLE_PROPERTIES = %w[
|
23
|
+
border-color
|
24
|
+
border-radius
|
25
|
+
border-style
|
26
|
+
border-width
|
27
|
+
margin
|
28
|
+
padding
|
29
|
+
]
|
30
|
+
|
31
|
+
def check_script_list(prop, list)
|
32
|
+
check_shorthand(prop, list, list.children.map(&:to_sass))
|
23
33
|
end
|
24
34
|
|
25
|
-
|
35
|
+
def check_script_string(prop, script_string)
|
36
|
+
return unless script_string.type == :identifier
|
37
|
+
|
38
|
+
if values = script_string.value.strip[/\A(\S+\s+\S+(\s+\S+){0,2})\z/, 1]
|
39
|
+
check_shorthand(prop, script_string, values.split)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_shorthand(prop, node, values)
|
44
|
+
return unless (2..4).member?(values.count)
|
26
45
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
46
|
+
shortest_form = condensed_shorthand(*values)
|
47
|
+
return if values == shortest_form
|
48
|
+
|
49
|
+
add_lint(node, "Shorthand form for property `#{prop}` should be " <<
|
50
|
+
"written more concisely as `#{shortest_form.join(' ')}` " <<
|
51
|
+
"instead of `#{values.join(' ')}`")
|
52
|
+
end
|
33
53
|
|
34
|
-
def
|
54
|
+
def condensed_shorthand(top, right, bottom = nil, left = nil)
|
35
55
|
if top == right && right == bottom && bottom == left
|
36
|
-
|
56
|
+
[top]
|
37
57
|
elsif top == right && bottom.nil? && left.nil?
|
38
|
-
|
58
|
+
[top]
|
39
59
|
elsif top == bottom && right == left
|
40
|
-
|
60
|
+
[top, right]
|
41
61
|
elsif top == bottom && left.nil?
|
42
|
-
|
62
|
+
top == right ? [top] : [top, right]
|
43
63
|
elsif right == left
|
44
|
-
|
64
|
+
[top, right, bottom]
|
45
65
|
else
|
46
|
-
|
66
|
+
[top, right, bottom, left].compact
|
47
67
|
end
|
48
68
|
end
|
49
69
|
end
|
@@ -2,21 +2,15 @@ module SCSSLint
|
|
2
2
|
class Linter::ZeroUnit < Linter
|
3
3
|
include LinterRegistry
|
4
4
|
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
node.value.value.scan(/\b(0[a-z]+)\b/i) do |match|
|
10
|
-
add_lint(node, MESSAGE_FORMAT % match.first)
|
11
|
-
end
|
5
|
+
def visit_script_string(node)
|
6
|
+
node.value.scan(/\b(0[a-z]+)\b/i) do |match|
|
7
|
+
add_lint(node, MESSAGE_FORMAT % match.first)
|
12
8
|
end
|
13
|
-
|
14
|
-
yield # Continue visiting children
|
15
9
|
end
|
16
10
|
|
17
11
|
def visit_script_number(node)
|
18
12
|
if node.value == 0 && !node.unitless?
|
19
|
-
add_lint(node, MESSAGE_FORMAT % node.
|
13
|
+
add_lint(node, MESSAGE_FORMAT % node.original_string)
|
20
14
|
end
|
21
15
|
end
|
22
16
|
|
@@ -5,7 +5,9 @@ module SCSSLint
|
|
5
5
|
def report_lints
|
6
6
|
if lints.any?
|
7
7
|
lints.map do |lint|
|
8
|
-
|
8
|
+
type = lint.error? ? '[E]'.red : '[W]'.yellow
|
9
|
+
"#{lint.filename.cyan}:" << "#{lint.line}".magenta <<
|
10
|
+
" #{type} #{lint.description}"
|
9
11
|
end.join("\n") + "\n"
|
10
12
|
end
|
11
13
|
end
|
@@ -8,7 +8,9 @@ module SCSSLint
|
|
8
8
|
output << "<file name='#{filename}'>"
|
9
9
|
|
10
10
|
file_lints.each do |lint|
|
11
|
-
output << "<issue line='#{lint.line}'
|
11
|
+
output << "<issue line='#{lint.line}' " <<
|
12
|
+
"severity='#{lint.severity}' " <<
|
13
|
+
"reason='#{lint.description}' />"
|
12
14
|
end
|
13
15
|
|
14
16
|
output << '</file>'
|
data/lib/scss_lint/runner.rb
CHANGED
data/lib/scss_lint/utils.rb
CHANGED
@@ -34,6 +34,27 @@ module SCSSLint
|
|
34
34
|
hex[5] == hex[6]
|
35
35
|
end
|
36
36
|
|
37
|
+
# Takes a string like `hello "world" 'how are' you` and turns it into:
|
38
|
+
# `hello you`.
|
39
|
+
# This is useful for scanning for keywords in shorthand properties or lists
|
40
|
+
# which can contain quoted strings but for which you don't want to inspect
|
41
|
+
# quoted strings (e.g. you care about the actual color keyword `red`, not
|
42
|
+
# the string "red").
|
43
|
+
def remove_quoted_strings(string)
|
44
|
+
string.gsub(/"[^"]*"|'[^']*'/, '')
|
45
|
+
end
|
46
|
+
|
47
|
+
def previous_node(node)
|
48
|
+
return unless node && parent = node.node_parent
|
49
|
+
index = parent.children.index(node)
|
50
|
+
|
51
|
+
if index == 0
|
52
|
+
parent
|
53
|
+
else
|
54
|
+
parent.children[index - 1]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
37
58
|
private
|
38
59
|
|
39
60
|
INVALID_NAME_CHARS = '[_A-Z]'
|
data/lib/scss_lint/version.rb
CHANGED
metadata
CHANGED
@@ -1,71 +1,72 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scss-lint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Causes Engineering
|
8
|
+
- Shane da Silva
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2013-
|
12
|
+
date: 2013-10-21 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: colorize
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
16
17
|
requirements:
|
17
|
-
- - '
|
18
|
+
- - '='
|
18
19
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
+
version: 0.5.8
|
20
21
|
type: :runtime
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
|
-
- - '
|
25
|
+
- - '='
|
25
26
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
27
|
+
version: 0.5.8
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
29
|
name: sass
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
32
|
- - '='
|
32
33
|
- !ruby/object:Gem::Version
|
33
|
-
version: 3.
|
34
|
+
version: 3.3.0.rc.1
|
34
35
|
type: :runtime
|
35
36
|
prerelease: false
|
36
37
|
version_requirements: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
39
|
- - '='
|
39
40
|
- !ruby/object:Gem::Version
|
40
|
-
version: 3.
|
41
|
+
version: 3.3.0.rc.1
|
41
42
|
- !ruby/object:Gem::Dependency
|
42
43
|
name: nokogiri
|
43
44
|
requirement: !ruby/object:Gem::Requirement
|
44
45
|
requirements:
|
45
|
-
- - '
|
46
|
+
- - '='
|
46
47
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
48
|
+
version: 1.6.0
|
48
49
|
type: :development
|
49
50
|
prerelease: false
|
50
51
|
version_requirements: !ruby/object:Gem::Requirement
|
51
52
|
requirements:
|
52
|
-
- - '
|
53
|
+
- - '='
|
53
54
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
55
|
+
version: 1.6.0
|
55
56
|
- !ruby/object:Gem::Dependency
|
56
57
|
name: rspec
|
57
58
|
requirement: !ruby/object:Gem::Requirement
|
58
59
|
requirements:
|
59
|
-
- - '
|
60
|
+
- - '='
|
60
61
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
62
|
+
version: 2.13.0
|
62
63
|
type: :development
|
63
64
|
prerelease: false
|
64
65
|
version_requirements: !ruby/object:Gem::Requirement
|
65
66
|
requirements:
|
66
|
-
- - '
|
67
|
+
- - '='
|
67
68
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
69
|
+
version: 2.13.0
|
69
70
|
description: Opinionated tool to help write clean and consistent SCSS
|
70
71
|
email:
|
71
72
|
- eng@causes.com
|
@@ -87,19 +88,21 @@ files:
|
|
87
88
|
- lib/scss_lint/linter/property_format.rb
|
88
89
|
- lib/scss_lint/linter/space_before_brace.rb
|
89
90
|
- lib/scss_lint/linter/capitalization_in_selector.rb
|
91
|
+
- lib/scss_lint/linter/indentation.rb
|
90
92
|
- lib/scss_lint/linter/declared_name.rb
|
91
93
|
- lib/scss_lint/linter/sorted_properties.rb
|
92
94
|
- lib/scss_lint/linter/shorthand.rb
|
93
95
|
- lib/scss_lint/linter/id_with_extraneous_selector.rb
|
94
96
|
- lib/scss_lint/linter/declaration_order.rb
|
95
|
-
- lib/scss_lint/linter/no_zero_before_decimal.rb
|
96
97
|
- lib/scss_lint/linter/zero_unit.rb
|
97
98
|
- lib/scss_lint/linter/placeholder_in_extend.rb
|
98
99
|
- lib/scss_lint/linter/usage_name.rb
|
99
100
|
- lib/scss_lint/linter/border_zero.rb
|
100
101
|
- lib/scss_lint/linter/debug_statement.rb
|
102
|
+
- lib/scss_lint/linter/duplicate_property.rb
|
101
103
|
- lib/scss_lint/linter/hex_format.rb
|
102
104
|
- lib/scss_lint/linter/color_keyword.rb
|
105
|
+
- lib/scss_lint/linter/leading_zero.rb
|
103
106
|
- lib/scss_lint/linter/comment.rb
|
104
107
|
- lib/scss_lint/linter/empty_rule.rb
|
105
108
|
- lib/scss_lint/linter/single_line_per_selector.rb
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module SCSSLint
|
2
|
-
class Linter::NoZeroBeforeDecimal < Linter
|
3
|
-
include LinterRegistry
|
4
|
-
|
5
|
-
def visit_prop(node)
|
6
|
-
# Misleading, but anything that isn't Sass Script is considered an
|
7
|
-
# `identifier` in the context of a Sass::Script::String.
|
8
|
-
return unless node.value.is_a?(Sass::Script::String) && node.value.type == :identifier
|
9
|
-
|
10
|
-
# Remove string chunks (e.g. `"hello" 3 'world'` -> `3`
|
11
|
-
non_string_values = node.value.value.gsub(/"[^"]*"|'[^']'/, '').split
|
12
|
-
|
13
|
-
non_string_values.each do |value|
|
14
|
-
add_lint(node) if value =~ /\b0\.\d+/
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def description
|
19
|
-
'Leading zero should be omitted in fractional values'
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|