scss-lint 0.6.7 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|