scss-lint 0.6.7 → 0.7.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.
- data/lib/sass/tree.rb +148 -0
- data/lib/scss_lint/engine.rb +2 -1
- data/lib/scss_lint/linter/border_zero_linter.rb +24 -0
- data/lib/scss_lint/linter/comment_linter.rb +15 -0
- data/lib/scss_lint/linter/declared_name_linter.rb +45 -0
- data/lib/scss_lint/linter/hex_linter.rb +11 -4
- data/lib/scss_lint/linter/property_format_linter.rb +6 -5
- data/lib/scss_lint/linter/shorthand_linter.rb +13 -10
- data/lib/scss_lint/linter/single_line_per_selector_linter.rb +25 -1
- data/lib/scss_lint/linter.rb +12 -1
- data/lib/scss_lint/version.rb +1 -1
- data/lib/scss_lint.rb +4 -0
- metadata +8 -3
data/lib/sass/tree.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
# Contains extensions of Sass::Tree::Nodes to add support for traversing the
|
2
|
+
# Sass::Script::Node parse trees contained within the nodes. This probably
|
3
|
+
# breaks the Sass compiler, but since we're only doing lints this is fine for
|
4
|
+
# now.
|
5
|
+
module Sass::Tree
|
6
|
+
# Define some common helper code for use in the various monkey patchings.
|
7
|
+
class Node
|
8
|
+
# The `args` field of some Sass::Tree::Node classes returns
|
9
|
+
# Sass::Script::Variable nodes with no line numbers. This adds the line
|
10
|
+
# numbers back in so lint reporting works for those nodes.
|
11
|
+
def add_line_numbers_to_args(arg_list)
|
12
|
+
arg_list.each do |variable, default_expr|
|
13
|
+
variable.line = line # Use line number of containing parse tree node
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Sometimes the parse tree doesn't return a Sass::Script::Variable, but just
|
18
|
+
# the name of the variable. This helper takes that name and turns it back
|
19
|
+
# into a Sass::Script::Variable that supports lint reporting.
|
20
|
+
def create_variable(var_name)
|
21
|
+
Sass::Script::Variable.new(var_name).tap do |v|
|
22
|
+
v.line = line # Use line number of the containing parse tree node
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# A number of tree nodes return lists that have strings and
|
27
|
+
# Sass::Script::Nodes interspersed within them. This returns a filtered list
|
28
|
+
# of just those nodes.
|
29
|
+
def extract_script_nodes(list)
|
30
|
+
list.select { |item| item.is_a? Sass::Script::Node }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Takes a list of arguments, be they arrays or individual objects, and
|
34
|
+
# returns a single flat list that can be passed to
|
35
|
+
# Sass::Tree::Visitors::Base#visit_children.
|
36
|
+
def concat_expr_lists(*expr_lists)
|
37
|
+
expr_lists.flatten.compact
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class CommentNode
|
42
|
+
def children
|
43
|
+
concat_expr_lists super, extract_script_nodes(value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class DebugNode
|
48
|
+
def children
|
49
|
+
concat_expr_lists super, expr
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class DirectiveNode
|
54
|
+
def children
|
55
|
+
concat_expr_lists super, extract_script_nodes(value)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class EachNode
|
60
|
+
def children
|
61
|
+
concat_expr_lists super, create_variable(var), list
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class ExtendNode
|
66
|
+
def children
|
67
|
+
concat_expr_lists super, extract_script_nodes(selector)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class ForNode
|
72
|
+
def children
|
73
|
+
concat_expr_lists super, create_variable(var), from, to
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class FunctionNode
|
78
|
+
def children
|
79
|
+
add_line_numbers_to_args(args)
|
80
|
+
|
81
|
+
concat_expr_lists super, args, splat
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class IfNode
|
86
|
+
def children
|
87
|
+
concat_expr_lists super, expr
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class MixinDefNode
|
92
|
+
def children
|
93
|
+
add_line_numbers_to_args(args)
|
94
|
+
|
95
|
+
concat_expr_lists super, args, splat
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class MixinNode
|
100
|
+
def children
|
101
|
+
add_line_numbers_to_args(args)
|
102
|
+
|
103
|
+
# Keyword mapping is String -> Expr, so convert the string to a variable
|
104
|
+
# node that supports lint reporting
|
105
|
+
keyword_exprs = keywords.map do |var_name, var_expr|
|
106
|
+
[create_variable(var_name), var_expr]
|
107
|
+
end
|
108
|
+
|
109
|
+
concat_expr_lists super, args, keyword_exprs, splat
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class PropNode
|
114
|
+
def children
|
115
|
+
concat_expr_lists super, extract_script_nodes(name), value
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class ReturnNode
|
120
|
+
def children
|
121
|
+
concat_expr_lists super, expr
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class RuleNode
|
126
|
+
def children
|
127
|
+
concat_expr_lists super, extract_script_nodes(rule)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class VariableNode
|
132
|
+
def children
|
133
|
+
concat_expr_lists super, expr
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class WarnNode
|
138
|
+
def children
|
139
|
+
concat_expr_lists super, expr
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class WhileNode
|
144
|
+
def children
|
145
|
+
concat_expr_lists super, expr
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/lib/scss_lint/engine.rb
CHANGED
@@ -4,10 +4,11 @@ module SCSSLint
|
|
4
4
|
class Engine
|
5
5
|
ENGINE_OPTIONS = { cache: false, syntax: :scss }
|
6
6
|
|
7
|
-
attr_reader :contents, :lines, :tree
|
7
|
+
attr_reader :contents, :filename, :lines, :tree
|
8
8
|
|
9
9
|
def initialize(scss_or_filename)
|
10
10
|
if File.exists?(scss_or_filename)
|
11
|
+
@filename = scss_or_filename
|
11
12
|
@engine = Sass::Engine.for_file(scss_or_filename, ENGINE_OPTIONS)
|
12
13
|
@contents = File.open(scss_or_filename, 'r').read
|
13
14
|
else
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'sass'
|
2
|
+
|
3
|
+
module SCSSLint
|
4
|
+
class Linter::BorderZeroLinter < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
def visit_prop(node)
|
8
|
+
return unless BORDER_PROPERTIES.include? node.name.first.to_s
|
9
|
+
add_lint(node) if node.value.to_sass.strip == 'none'
|
10
|
+
end
|
11
|
+
|
12
|
+
def description
|
13
|
+
'`border: 0;` is preferred over `border: none;`'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
BORDER_PROPERTIES = %w[border
|
20
|
+
border-top
|
21
|
+
border-right
|
22
|
+
border-bottom
|
23
|
+
border-left]
|
24
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'sass'
|
2
|
+
|
3
|
+
module SCSSLint
|
4
|
+
class Linter::DeclaredNameLinter < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
def visit_function(node)
|
8
|
+
check(node)
|
9
|
+
yield # Continue into content block of this function definition
|
10
|
+
end
|
11
|
+
|
12
|
+
def visit_mixin(node)
|
13
|
+
check(node)
|
14
|
+
yield # Continue into content block of this mixin's block
|
15
|
+
end
|
16
|
+
|
17
|
+
def visit_mixindef(node)
|
18
|
+
check(node)
|
19
|
+
yield # Continue into content block of this mixin definition
|
20
|
+
end
|
21
|
+
|
22
|
+
def visit_variable(node)
|
23
|
+
check(node)
|
24
|
+
yield # Continue into expression tree for this variable definition
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit_script_funcall(node)
|
28
|
+
check(node)
|
29
|
+
end
|
30
|
+
|
31
|
+
def visit_script_variable(node)
|
32
|
+
check(node)
|
33
|
+
end
|
34
|
+
|
35
|
+
def description
|
36
|
+
'Names of variables, functions, and mixins should be lowercase and not contain underscores. Use hyphens instead.'
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def check(node)
|
42
|
+
add_lint(node) if node.name =~ /[_A-Z]/
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -4,10 +4,17 @@ module SCSSLint
|
|
4
4
|
class Linter::HexLinter < Linter
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
def visit_root(node)
|
8
|
+
# We can't do this via the parse tree because the parser automatically
|
9
|
+
# normalizes all colors encountered in Sass script, so we lose the ability
|
10
|
+
# to check the format of the color. Thus we resort to line-by-line regex
|
11
|
+
# matching.
|
12
|
+
engine.lines.each_with_index do |line, index|
|
13
|
+
line.scan /#(\h{3,6})/ do |match|
|
14
|
+
unless valid_hex?(match.first)
|
15
|
+
@lints << Lint.new(engine.filename, index + 1, description)
|
16
|
+
end
|
17
|
+
end
|
11
18
|
end
|
12
19
|
end
|
13
20
|
|
@@ -6,6 +6,7 @@ module SCSSLint
|
|
6
6
|
|
7
7
|
def visit_prop(node)
|
8
8
|
line = engine.lines[node.line - 1] if node.line
|
9
|
+
|
9
10
|
add_lint(node) unless line =~ PROPERTY_RE
|
10
11
|
end
|
11
12
|
|
@@ -19,11 +20,11 @@ module SCSSLint
|
|
19
20
|
|
20
21
|
VALUE_RE = %r{(\S+\s)*\S+} # eg. "10px", "10px normal"
|
21
22
|
PROPERTY_RE = %r{
|
22
|
-
^\s*[
|
23
|
-
(
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
^\s*[^:]+(?<!\s):\s # property name, colon not preceded by a space, one space
|
24
|
+
( # followed by
|
25
|
+
#{VALUE_RE}; # property and terminating semi-colon eg. a b c;
|
26
|
+
| # or
|
27
|
+
((#{VALUE_RE}\s)?\{) # nested property, optional value, trailing curly
|
27
28
|
)
|
28
29
|
}x
|
29
30
|
end
|
@@ -5,12 +5,18 @@ module SCSSLint
|
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
7
|
def visit_prop(node)
|
8
|
-
unless SHORTHANDABLE_PROPERTIES.include? node.name.first.to_s
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
return unless SHORTHANDABLE_PROPERTIES.include? node.name.first.to_s
|
9
|
+
|
10
|
+
case node.value
|
11
|
+
when Sass::Script::List
|
12
|
+
items = node.value.children
|
13
|
+
if (2..4).member?(items.count)
|
14
|
+
add_lint(node) unless valid_shorthand?(*items.map(&:to_sass))
|
15
|
+
end
|
16
|
+
when Sass::Script::String
|
17
|
+
if node.value.to_sass.strip =~ /\A(\S+\s+\S+(\s+\S+){0,2})\Z/
|
18
|
+
add_lint(node) unless valid_shorthand?(*$1.split(/\s+/))
|
19
|
+
end
|
14
20
|
end
|
15
21
|
end
|
16
22
|
|
@@ -27,10 +33,7 @@ module SCSSLint
|
|
27
33
|
margin
|
28
34
|
padding]
|
29
35
|
|
30
|
-
def valid_shorthand?(
|
31
|
-
values = shorthand.split(/\s+/)
|
32
|
-
top, right, bottom, left = values
|
33
|
-
|
36
|
+
def valid_shorthand?(top, right, bottom = nil, left = nil)
|
34
37
|
if top == right && right == bottom && bottom == left
|
35
38
|
false
|
36
39
|
elsif top == right && bottom.nil? && left.nil?
|
@@ -5,12 +5,36 @@ module SCSSLint
|
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
7
|
def visit_rule(node)
|
8
|
-
add_lint(node)
|
8
|
+
add_lint(node) if invalid_comma_placement? node
|
9
9
|
yield # Continue linting children
|
10
10
|
end
|
11
11
|
|
12
12
|
def description
|
13
13
|
'Each selector should be on its own line'
|
14
14
|
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# A comma is invalid if it starts the line or is not the end of the line
|
19
|
+
def invalid_comma_placement?(node)
|
20
|
+
normalize_spacing(condense_to_string(node.rule)) =~ /\n,|,[^\n]/
|
21
|
+
end
|
22
|
+
|
23
|
+
# Since RuleNode.rule returns an array containing both String and
|
24
|
+
# Sass::Script::Nodes, we need to condense it into a single string that we
|
25
|
+
# can run a regex against.
|
26
|
+
def condense_to_string(sequence_list)
|
27
|
+
sequence_list.inject('') do |combined, string_or_script|
|
28
|
+
combined + (string_or_script.is_a?(String) ? string_or_script
|
29
|
+
: string_or_script.to_sass)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Removes extra spacing between lines in a comma-separated sequence due to
|
34
|
+
# comments being removed in the parse phase. This makes it easy to check if
|
35
|
+
# a comma is where belongs.
|
36
|
+
def normalize_spacing(string_sequence)
|
37
|
+
string_sequence.gsub(/,[^\S\n]*\n\s*/, ",\n")
|
38
|
+
end
|
15
39
|
end
|
16
40
|
end
|
data/lib/scss_lint/linter.rb
CHANGED
@@ -21,7 +21,18 @@ module SCSSLint
|
|
21
21
|
|
22
22
|
# Helper for creating lint from a parse tree node
|
23
23
|
def add_lint(node)
|
24
|
-
@lints << Lint.new(
|
24
|
+
@lints << Lint.new(engine.filename, node.line, description)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Monkey-patched implementation that adds support for traversing
|
28
|
+
# Sass::Script::Nodes (original implementation only supports
|
29
|
+
# Sass::Tree::Nodes).
|
30
|
+
def node_name(node)
|
31
|
+
if node.is_a?(Sass::Script::Node)
|
32
|
+
"script_#{node.class.name.gsub(/.*::(.*?)$/, '\\1').downcase}"
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
25
36
|
end
|
26
37
|
end
|
27
38
|
end
|
data/lib/scss_lint/version.rb
CHANGED
data/lib/scss_lint.rb
CHANGED
@@ -10,6 +10,10 @@ module SCSSLint
|
|
10
10
|
autoload :Runner, 'scss_lint/runner'
|
11
11
|
autoload :VERSION, 'scss_lint/version'
|
12
12
|
|
13
|
+
# Preload Sass so we can monkey patch it
|
14
|
+
require 'sass'
|
15
|
+
require 'sass/tree'
|
16
|
+
|
13
17
|
# Load all linters
|
14
18
|
Dir[File.expand_path('scss_lint/linter/*.rb', File.dirname(__FILE__))].each do |file|
|
15
19
|
require file
|
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.7.0
|
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-
|
12
|
+
date: 2013-06-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: colorize
|
@@ -84,13 +84,17 @@ executables:
|
|
84
84
|
extensions: []
|
85
85
|
extra_rdoc_files: []
|
86
86
|
files:
|
87
|
+
- lib/sass/tree.rb
|
87
88
|
- lib/scss_lint.rb
|
88
89
|
- lib/scss_lint/cli.rb
|
89
90
|
- lib/scss_lint/engine.rb
|
90
91
|
- lib/scss_lint/lint.rb
|
91
92
|
- lib/scss_lint/linter.rb
|
93
|
+
- lib/scss_lint/linter/border_zero_linter.rb
|
94
|
+
- lib/scss_lint/linter/comment_linter.rb
|
92
95
|
- lib/scss_lint/linter/debug_linter.rb
|
93
96
|
- lib/scss_lint/linter/declaration_order_linter.rb
|
97
|
+
- lib/scss_lint/linter/declared_name_linter.rb
|
94
98
|
- lib/scss_lint/linter/empty_rule_linter.rb
|
95
99
|
- lib/scss_lint/linter/hex_linter.rb
|
96
100
|
- lib/scss_lint/linter/property_format_linter.rb
|
@@ -107,7 +111,8 @@ files:
|
|
107
111
|
- lib/scss_lint/version.rb
|
108
112
|
- bin/scss-lint
|
109
113
|
homepage: http://github.com/causes/scss-lint
|
110
|
-
licenses:
|
114
|
+
licenses:
|
115
|
+
- MIT
|
111
116
|
post_install_message:
|
112
117
|
rdoc_options: []
|
113
118
|
require_paths:
|