scss-lint 0.19.0 → 0.20.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 +2 -0
- data/lib/scss_lint/linter/color_keyword.rb +5 -1
- data/lib/scss_lint/linter/hex_format.rb +3 -3
- data/lib/scss_lint/linter/leading_zero.rb +28 -9
- data/lib/scss_lint/linter/name_format.rb +34 -28
- data/lib/scss_lint/linter/space_between_parens.rb +2 -1
- data/lib/scss_lint/linter/zero_unit.rb +7 -3
- data/lib/scss_lint/rake_task.rb +43 -0
- data/lib/scss_lint/sass/script.rb +0 -65
- data/lib/scss_lint/utils.rb +0 -8
- data/lib/scss_lint/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9afb9a82ced2ebf876f8b1a56d829d1360c806fb
|
4
|
+
data.tar.gz: fc5bdfbcb4565f704792b0e5f0ccad90230476bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 64348900df0980e60f076434c46e0475924086124b1b8e5960c4e67d9c64b87519e2cd343a90643b424a57630c9cde537fd9ef7d8c23f3a580bc7245237030cc
|
7
|
+
data.tar.gz: 32a3dcb769845f9dba7ad7d07187c754d20ba4156c3c41eb06a59ef0193fe2d9df7ed9dbb19550b26ddb73bde87ef8f0b0afc3f0afcca94aec86406e0ac8d0a4
|
data/config/default.yml
CHANGED
@@ -5,7 +5,9 @@ module SCSSLint
|
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
7
|
def visit_script_color(node)
|
8
|
-
|
8
|
+
color = source_from_range(node.source_range)[COLOR_REGEX, 1]
|
9
|
+
|
10
|
+
add_color_lint(node, color) if color_keyword?(color)
|
9
11
|
end
|
10
12
|
|
11
13
|
def visit_script_string(node)
|
@@ -18,6 +20,8 @@ module SCSSLint
|
|
18
20
|
|
19
21
|
private
|
20
22
|
|
23
|
+
COLOR_REGEX = /(#?[a-f0-9]{3,6}|[a-z]+)/i
|
24
|
+
|
21
25
|
def add_color_lint(node, original)
|
22
26
|
hex_form = Sass::Script::Value::Color.new(color_rgb(original)).tap do |color|
|
23
27
|
color.options = {} # `inspect` requires options to be set
|
@@ -4,10 +4,10 @@ module SCSSLint
|
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
6
|
def visit_script_color(node)
|
7
|
-
return unless
|
7
|
+
return unless color = source_from_range(node.source_range)[HEX_REGEX, 1]
|
8
8
|
|
9
|
-
unless valid_hex_format?(
|
10
|
-
add_hex_lint(node,
|
9
|
+
unless valid_hex_format?(color)
|
10
|
+
add_hex_lint(node, color)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -9,25 +9,44 @@ module SCSSLint
|
|
9
9
|
non_string_values = remove_quoted_strings(node.value).split
|
10
10
|
|
11
11
|
non_string_values.each do |value|
|
12
|
-
if number = value[
|
13
|
-
|
12
|
+
if number = value[FRACTIONAL_DIGIT_REGEX, 1]
|
13
|
+
check_number(node, number)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
def visit_script_number(node)
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
return unless number = source_from_range(node.source_range)[FRACTIONAL_DIGIT_REGEX, 1]
|
20
|
+
|
21
|
+
check_number(node, number)
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
FRACTIONAL_DIGIT_REGEX = /^-?(0?\.\d+)/
|
27
|
+
|
28
|
+
CONVENTIONS = {
|
29
|
+
'exclude_zero' => {
|
30
|
+
explanation: '`%s` should be written without a leading zero as `%s`',
|
31
|
+
validator: ->(original) { original =~ /^\.\d+$/ },
|
32
|
+
converter: ->(original) { original[1..-1] },
|
33
|
+
},
|
34
|
+
'include_zero' => {
|
35
|
+
explanation: '`%s` should be written with a leading zero as `%s`',
|
36
|
+
validator: ->(original) { original =~ /^0\.\d+$/ },
|
37
|
+
converter: ->(original) { "0#{original}" }
|
38
|
+
},
|
39
|
+
}
|
28
40
|
|
29
|
-
|
30
|
-
|
41
|
+
def check_number(node, original_number)
|
42
|
+
style = config.fetch('style', 'exclude_zero')
|
43
|
+
convention = CONVENTIONS[style]
|
44
|
+
|
45
|
+
unless convention[:validator].call(original_number)
|
46
|
+
corrected = convention[:converter].call(original_number)
|
47
|
+
|
48
|
+
add_lint(node, convention[:explanation] % [original_number, corrected])
|
49
|
+
end
|
31
50
|
end
|
32
51
|
end
|
33
52
|
end
|
@@ -5,36 +5,34 @@ module SCSSLint
|
|
5
5
|
include LinterRegistry
|
6
6
|
|
7
7
|
def visit_extend(node)
|
8
|
-
|
9
|
-
add_name_lint(node, node.selector.join, 'placeholder')
|
10
|
-
end
|
8
|
+
check_placeholder(node)
|
11
9
|
end
|
12
10
|
|
13
11
|
def visit_function(node)
|
14
|
-
|
12
|
+
check_name(node, 'function')
|
15
13
|
yield # Continue into content block of this function definition
|
16
14
|
end
|
17
15
|
|
18
16
|
def visit_mixin(node)
|
19
|
-
|
17
|
+
check_name(node, 'mixin')
|
20
18
|
yield # Continue into content block of this mixin's block
|
21
19
|
end
|
22
20
|
|
23
21
|
def visit_mixindef(node)
|
24
|
-
|
22
|
+
check_name(node, 'mixin')
|
25
23
|
yield # Continue into content block of this mixin definition
|
26
24
|
end
|
27
25
|
|
28
26
|
def visit_script_funcall(node)
|
29
|
-
|
27
|
+
check_name(node, 'function') unless FUNCTION_WHITELIST.include?(node.name)
|
30
28
|
end
|
31
29
|
|
32
30
|
def visit_script_variable(node)
|
33
|
-
|
31
|
+
check_name(node, 'variable')
|
34
32
|
end
|
35
33
|
|
36
34
|
def visit_variable(node)
|
37
|
-
|
35
|
+
check_name(node, 'variable')
|
38
36
|
yield # Continue into expression tree for this variable definition
|
39
37
|
end
|
40
38
|
|
@@ -47,32 +45,40 @@ module SCSSLint
|
|
47
45
|
translateX translateY translateZ
|
48
46
|
].to_set
|
49
47
|
|
50
|
-
def
|
51
|
-
if
|
52
|
-
|
53
|
-
|
54
|
-
add_lint(node, "Name of #{node_type} `#{node.name}` should " <<
|
55
|
-
"be written in lowercase as `#{fixed_name}`")
|
48
|
+
def check_name(node, node_type, node_text = node.name)
|
49
|
+
if convention = violated_convention(node_text)
|
50
|
+
add_lint(node, "Name of #{node_type} `#{node_text}` should be " <<
|
51
|
+
"written #{convention[:explanation]}")
|
56
52
|
end
|
57
53
|
end
|
58
54
|
|
59
|
-
def
|
60
|
-
|
55
|
+
def check_placeholder(node)
|
56
|
+
extract_string_selectors(node.selector).any? do |selector_str|
|
57
|
+
check_name(node, 'placeholder', selector_str.gsub('%', ''))
|
58
|
+
end
|
61
59
|
end
|
62
60
|
|
63
|
-
|
64
|
-
|
61
|
+
CONVENTIONS = {
|
62
|
+
'hyphenated_lowercase' => {
|
63
|
+
explanation: 'in lowercase with hyphens instead of underscores',
|
64
|
+
validator: ->(name) { name !~ /[_A-Z]/ },
|
65
|
+
},
|
66
|
+
'BEM' => {
|
67
|
+
explanation: 'in BEM (Block Element Modifier) format',
|
68
|
+
validator: ->(name) { name !~ /[A-Z]|-{3}|_{3}|[^_]_[^_]/ },
|
69
|
+
},
|
70
|
+
}
|
65
71
|
|
66
|
-
|
67
|
-
|
68
|
-
|
72
|
+
# Checks the given name and returns the violated convention if it failed.
|
73
|
+
def violated_convention(name_string)
|
74
|
+
convention_name = config['convention'] || 'hyphenated_lowercase'
|
69
75
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
+
convention = CONVENTIONS[convention_name] || {
|
77
|
+
explanation: "must match regex /#{convention_name}/",
|
78
|
+
validator: ->(name) { name =~ /#{convention_name}/ }
|
79
|
+
}
|
80
|
+
|
81
|
+
convention unless convention[:validator].call(name_string)
|
76
82
|
end
|
77
83
|
end
|
78
84
|
end
|
@@ -5,8 +5,9 @@ module SCSSLint
|
|
5
5
|
|
6
6
|
def visit_root(node)
|
7
7
|
@spaces = config['spaces']
|
8
|
+
|
8
9
|
engine.lines.each_with_index do |line, index|
|
9
|
-
line.scan(/
|
10
|
+
line.gsub(%r{((//|/\*).*$)}, '').scan(/
|
10
11
|
(^(\t|\s)*\))? # Capture leading spaces and tabs followed by a `)`
|
11
12
|
(
|
12
13
|
\([ ]*(?!$) # Find `( ` as long as its not EOL )
|
@@ -4,19 +4,23 @@ module SCSSLint
|
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
6
|
def visit_script_string(node)
|
7
|
-
node.value.scan(
|
7
|
+
node.value.scan(ZERO_UNIT_REGEX) do |match|
|
8
8
|
add_lint(node, MESSAGE_FORMAT % match.first)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
12
|
def visit_script_number(node)
|
13
|
-
|
14
|
-
|
13
|
+
length = source_from_range(node.source_range)[ZERO_UNIT_REGEX, 1]
|
14
|
+
|
15
|
+
if node.value == 0 && zero_with_units?(length)
|
16
|
+
add_lint(node, MESSAGE_FORMAT % length)
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
20
|
private
|
19
21
|
|
22
|
+
ZERO_UNIT_REGEX = /\b(0[a-z]+)\b/i
|
23
|
+
|
20
24
|
MESSAGE_FORMAT = '`%s` should be written without units as `0`'
|
21
25
|
|
22
26
|
def zero_with_units?(string)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
|
4
|
+
module SCSSLint
|
5
|
+
# Provide task for invoking scss-lint via Rake.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# require 'scss_lint/rake_task'
|
9
|
+
# SCSSLint::RakeTask.new
|
10
|
+
class RakeTask < Rake::TaskLib
|
11
|
+
# The name of the task (default 'scss-lint')
|
12
|
+
attr_accessor :name
|
13
|
+
|
14
|
+
def initialize(*args, &task_block)
|
15
|
+
@name = args.shift || :scss_lint
|
16
|
+
|
17
|
+
desc 'Run scss-lint' unless ::Rake.application.last_comment
|
18
|
+
|
19
|
+
task(name, *args) do |_, task_args|
|
20
|
+
if task_block
|
21
|
+
task_block.call(*[self, task_args].slice(0, task_block.arity))
|
22
|
+
end
|
23
|
+
run_task
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def run_task
|
28
|
+
# Lazy load so task doesn't impact load time of Rakefile
|
29
|
+
require 'scss_lint'
|
30
|
+
|
31
|
+
CLI.new([]).tap do |cli|
|
32
|
+
cli.parse_arguments
|
33
|
+
cli.run
|
34
|
+
end
|
35
|
+
rescue SystemExit => ex
|
36
|
+
if ex.status == CLI::EXIT_CODES[:data]
|
37
|
+
abort('scss-lint found lints')
|
38
|
+
elsif ex.status != 0
|
39
|
+
abort('scss-lint failed with an error')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -2,47 +2,6 @@
|
|
2
2
|
# rubocop:disable Documentation
|
3
3
|
|
4
4
|
module Sass::Script
|
5
|
-
# Redefine some of the lexer helpers in order to store the original string
|
6
|
-
# with the created object so that the original string can be inspected rather
|
7
|
-
# than a typically normalized version.
|
8
|
-
class Lexer
|
9
|
-
def color
|
10
|
-
return unless color_string = scan(REGULAR_EXPRESSIONS[:color])
|
11
|
-
|
12
|
-
unless [4, 7].include?(color_string.length)
|
13
|
-
raise ::Sass::SyntaxError,
|
14
|
-
"Colors must have either three or six digits: '#{color_string}'"
|
15
|
-
end
|
16
|
-
|
17
|
-
[:color, Value::Color.from_string(color_string)]
|
18
|
-
end
|
19
|
-
|
20
|
-
def number
|
21
|
-
return unless scan(REGULAR_EXPRESSIONS[:number])
|
22
|
-
value = @scanner[2] ? @scanner[2].to_f : @scanner[3].to_i
|
23
|
-
value = -value if @scanner[1]
|
24
|
-
|
25
|
-
number = Value::Number.new(value, Array(@scanner[4])).tap do |num|
|
26
|
-
num.original_string = @scanner[0]
|
27
|
-
end
|
28
|
-
[:number, number]
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class Parser
|
33
|
-
# We redefine the ident parser to specially handle color keywords.
|
34
|
-
def ident
|
35
|
-
return funcall unless @lexer.peek && @lexer.peek.type == :ident
|
36
|
-
return if @stop_at && @stop_at.include?(@lexer.peek.value)
|
37
|
-
|
38
|
-
name = @lexer.next
|
39
|
-
if (color = Value::Color::COLOR_NAMES[name.value.downcase])
|
40
|
-
return literal_node(Value::Color.from_string(name.value, color), name.source_range)
|
41
|
-
end
|
42
|
-
literal_node(Value::String.new(name.value, :identifier), name.source_range)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
5
|
# Since the Sass library is already loaded at this point.
|
47
6
|
# Define the `node_name` and `visit_method` class methods for each Sass Script
|
48
7
|
# parse tree node type so that our custom visitor can seamless traverse the
|
@@ -91,30 +50,6 @@ module Sass::Script
|
|
91
50
|
end
|
92
51
|
end
|
93
52
|
|
94
|
-
# When linting colors, it's convenient to be able to inspect the original
|
95
|
-
# color string. This adds an attribute to the Color to keep track of the
|
96
|
-
# original string and provides a method which the modified lexer can use to
|
97
|
-
# set it.
|
98
|
-
class Value::Color
|
99
|
-
attr_accessor :original_string
|
100
|
-
|
101
|
-
def self.from_string(string, rgb = nil)
|
102
|
-
unless rgb
|
103
|
-
rgb = string.scan(/^#(..?)(..?)(..?)$/).
|
104
|
-
first.
|
105
|
-
map { |hex| hex.ljust(2, hex).to_i(16) }
|
106
|
-
end
|
107
|
-
|
108
|
-
color = new(rgb, false)
|
109
|
-
color.original_string = string
|
110
|
-
color
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
class Value::Number
|
115
|
-
attr_accessor :original_string
|
116
|
-
end
|
117
|
-
|
118
53
|
# Contains extensions of Sass::Script::Tree::Nodes to add support for
|
119
54
|
# accessing various parts of the parse tree not provided out-of-the-box.
|
120
55
|
module Tree
|
data/lib/scss_lint/utils.rb
CHANGED
@@ -20,10 +20,6 @@ module SCSSLint
|
|
20
20
|
split
|
21
21
|
end
|
22
22
|
|
23
|
-
def node_has_bad_name?(node)
|
24
|
-
node.name =~ /#{INVALID_NAME_CHARS}/
|
25
|
-
end
|
26
|
-
|
27
23
|
def shortest_hex_form(hex)
|
28
24
|
(can_be_condensed?(hex) ? (hex[0..1] + hex[3] + hex[5]) : hex).downcase
|
29
25
|
end
|
@@ -59,9 +55,5 @@ module SCSSLint
|
|
59
55
|
def pluralize(value, word)
|
60
56
|
value == 1 ? "#{value} #{word}" : "#{value} #{word}s"
|
61
57
|
end
|
62
|
-
|
63
|
-
private
|
64
|
-
|
65
|
-
INVALID_NAME_CHARS = '[_A-Z]'
|
66
58
|
end
|
67
59
|
end
|
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.20.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-03-
|
12
|
+
date: 2014-03-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: colorize
|
@@ -88,6 +88,7 @@ files:
|
|
88
88
|
- lib/scss_lint/reporter.rb
|
89
89
|
- lib/scss_lint/lint.rb
|
90
90
|
- lib/scss_lint/linter.rb
|
91
|
+
- lib/scss_lint/rake_task.rb
|
91
92
|
- lib/scss_lint/selector_visitor.rb
|
92
93
|
- lib/scss_lint/sass/tree.rb
|
93
94
|
- lib/scss_lint/sass/script.rb
|