scss_lint 0.39.0 → 0.40.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 +15 -0
- data/lib/scss_lint.rb +1 -0
- data/lib/scss_lint/cli.rb +10 -3
- data/lib/scss_lint/config.rb +54 -2
- data/lib/scss_lint/control_comment_processor.rb +15 -6
- data/lib/scss_lint/exceptions.rb +3 -0
- data/lib/scss_lint/linter/color_variable.rb +7 -0
- data/lib/scss_lint/linter/else_placement.rb +1 -0
- data/lib/scss_lint/linter/extend_directive.rb +11 -0
- data/lib/scss_lint/linter/final_newline.rb +1 -0
- data/lib/scss_lint/linter/name_format.rb +1 -12
- data/lib/scss_lint/linter/nesting_depth.rb +22 -1
- data/lib/scss_lint/linter/property_sort_order.rb +8 -0
- data/lib/scss_lint/linter/property_units.rb +21 -3
- data/lib/scss_lint/linter/space_after_variable_name.rb +18 -0
- data/lib/scss_lint/linter/space_between_parens.rb +1 -0
- data/lib/scss_lint/linter/trailing_whitespace.rb +15 -0
- data/lib/scss_lint/plugins.rb +33 -0
- data/lib/scss_lint/plugins/linter_dir.rb +24 -0
- data/lib/scss_lint/plugins/linter_gem.rb +51 -0
- data/lib/scss_lint/version.rb +1 -1
- data/spec/scss_lint/config_spec.rb +64 -0
- data/spec/scss_lint/fixtures/plugins/linter_plugin.rb +7 -0
- data/spec/scss_lint/linter/color_variable_spec.rb +12 -0
- data/spec/scss_lint/linter/else_placement_spec.rb +34 -0
- data/spec/scss_lint/linter/extend_directive_spec.rb +73 -0
- data/spec/scss_lint/linter/nesting_depth_spec.rb +72 -0
- data/spec/scss_lint/linter/property_sort_order_spec.rb +32 -0
- data/spec/scss_lint/linter/property_units_spec.rb +40 -0
- data/spec/scss_lint/linter/space_after_variable_name_spec.rb +13 -0
- data/spec/scss_lint/linter/trailing_whitespace_spec.rb +33 -0
- data/spec/scss_lint/linter_spec.rb +15 -2
- data/spec/scss_lint/plugins/linter_dir_spec.rb +21 -0
- data/spec/scss_lint/plugins/linter_gem_spec.rb +60 -0
- data/spec/scss_lint/plugins_spec.rb +53 -0
- data/spec/spec_helper.rb +10 -0
- metadata +26 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2875735c6ff15b0a0c04496ae19043825c289c95
|
4
|
+
data.tar.gz: abf49a6521c68efcedcf413a15208983bd116914
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f14a4ee19fc7124cadc19e33fa9f3ee8232f9b34c27bb2824100d4400d998e590004b214ccefc3b37b97490f167aef0950dc1775467e84d5fe2fa63ad6f37798
|
7
|
+
data.tar.gz: 56abaad1303a536a32d57a6cfc3862144c05e5a4c6140e377745a7a801bbd9ba9bd43fdb397527532918aa3203579473dcd0336f0e387e15e223d7194eef8628
|
data/config/default.yml
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
# Default application configuration that all configurations inherit from.
|
2
2
|
|
3
3
|
scss_files: "**/*.scss"
|
4
|
+
plugin_directories: ['.scss-linters']
|
5
|
+
|
6
|
+
# List of gem names to load custom linters from (make sure they are already
|
7
|
+
# installed)
|
8
|
+
plugin_gems: []
|
4
9
|
|
5
10
|
linters:
|
6
11
|
BangFormat:
|
@@ -45,6 +50,9 @@ linters:
|
|
45
50
|
EmptyRule:
|
46
51
|
enabled: true
|
47
52
|
|
53
|
+
ExtendDirective:
|
54
|
+
enabled: false
|
55
|
+
|
48
56
|
FinalNewline:
|
49
57
|
enabled: true
|
50
58
|
present: true
|
@@ -93,6 +101,7 @@ linters:
|
|
93
101
|
NestingDepth:
|
94
102
|
enabled: true
|
95
103
|
max_depth: 3
|
104
|
+
ignore_parent_selectors: false
|
96
105
|
|
97
106
|
PlaceholderInExtend:
|
98
107
|
enabled: true
|
@@ -160,6 +169,9 @@ linters:
|
|
160
169
|
SpaceAfterPropertyName:
|
161
170
|
enabled: true
|
162
171
|
|
172
|
+
SpaceAfterVariableName:
|
173
|
+
enabled: true
|
174
|
+
|
163
175
|
SpaceBeforeBrace:
|
164
176
|
enabled: true
|
165
177
|
style: space # or 'new_line'
|
@@ -176,6 +188,9 @@ linters:
|
|
176
188
|
TrailingSemicolon:
|
177
189
|
enabled: true
|
178
190
|
|
191
|
+
TrailingWhitespace:
|
192
|
+
enabled: true
|
193
|
+
|
179
194
|
TrailingZero:
|
180
195
|
enabled: false
|
181
196
|
|
data/lib/scss_lint.rb
CHANGED
data/lib/scss_lint/cli.rb
CHANGED
@@ -20,6 +20,7 @@ module SCSSLint
|
|
20
20
|
config: 78, # Configuration error
|
21
21
|
no_files: 80, # No files matched by specified glob patterns
|
22
22
|
files_filtered: 81, # All matched files were filtered by exclusions
|
23
|
+
plugin: 82, # Plugin loading error
|
23
24
|
}
|
24
25
|
|
25
26
|
def run(args)
|
@@ -39,13 +40,16 @@ module SCSSLint
|
|
39
40
|
print_help(options)
|
40
41
|
elsif options[:version]
|
41
42
|
print_version
|
42
|
-
elsif options[:show_linters]
|
43
|
-
print_linters
|
44
43
|
elsif options[:show_formatters]
|
45
44
|
print_formatters
|
46
45
|
else
|
47
46
|
config = setup_configuration(options)
|
48
|
-
|
47
|
+
|
48
|
+
if options[:show_linters]
|
49
|
+
print_linters
|
50
|
+
else
|
51
|
+
scan_for_lints(options, config)
|
52
|
+
end
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
@@ -81,6 +85,9 @@ module SCSSLint
|
|
81
85
|
when SCSSLint::Exceptions::NoFilesError
|
82
86
|
puts exception.message
|
83
87
|
halt :no_files
|
88
|
+
when SCSSLint::Exceptions::PluginGemLoadError
|
89
|
+
puts exception.message
|
90
|
+
halt :plugin
|
84
91
|
when Errno::ENOENT
|
85
92
|
puts exception.message
|
86
93
|
halt :no_input
|
data/lib/scss_lint/config.rb
CHANGED
@@ -18,11 +18,18 @@ module SCSSLint
|
|
18
18
|
def load(file, options = {})
|
19
19
|
config_options = load_options_hash_from_file(file)
|
20
20
|
|
21
|
+
config = new(config_options)
|
22
|
+
|
23
|
+
# Need to call this before merging with the default configuration so
|
24
|
+
# that plugins can override the default configuration while still being
|
25
|
+
# overridden by the repo's configuration.
|
26
|
+
config.load_plugins
|
27
|
+
|
21
28
|
if options.fetch(:merge_with_default, true)
|
22
|
-
|
29
|
+
config = default.extend(config)
|
23
30
|
end
|
24
31
|
|
25
|
-
|
32
|
+
config
|
26
33
|
end
|
27
34
|
|
28
35
|
# Returns the location of the user-wide scss-lint configuration.
|
@@ -178,6 +185,32 @@ module SCSSLint
|
|
178
185
|
validate_linters
|
179
186
|
end
|
180
187
|
|
188
|
+
def [](key)
|
189
|
+
@options[key]
|
190
|
+
end
|
191
|
+
|
192
|
+
# Compares this configuration with another.
|
193
|
+
#
|
194
|
+
# @param other [SCSSLint::Config]
|
195
|
+
# @return [true,false]
|
196
|
+
def ==(other)
|
197
|
+
super || @options == other.options
|
198
|
+
end
|
199
|
+
alias_method :eql?, :==
|
200
|
+
|
201
|
+
# Extend this {Config} with another configuration.
|
202
|
+
#
|
203
|
+
# @return [SCSSLint::Config]
|
204
|
+
def extend(config)
|
205
|
+
@options = self.class.send(:smart_merge, @options, config.options)
|
206
|
+
@warnings += config.warnings
|
207
|
+
self
|
208
|
+
end
|
209
|
+
|
210
|
+
def load_plugins
|
211
|
+
load_plugins_and_merge_config.tap { ensure_plugins_have_default_options }
|
212
|
+
end
|
213
|
+
|
181
214
|
def enabled_linters
|
182
215
|
LinterRegistry.extract_linters_from(@options['linters'].keys).select do |linter|
|
183
216
|
linter_options(linter)['enabled']
|
@@ -255,5 +288,24 @@ module SCSSLint
|
|
255
288
|
end
|
256
289
|
end
|
257
290
|
end
|
291
|
+
|
292
|
+
def load_plugins_and_merge_config
|
293
|
+
SCSSLint::Plugins.new(self).load.each do |plugin|
|
294
|
+
# Have the plugin options be overrideable by the local configuration
|
295
|
+
@options = self.class.send(:smart_merge, plugin.config.options, @options)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def ensure_plugins_have_default_options
|
300
|
+
LinterRegistry.linters.each do |linter|
|
301
|
+
if linter_options(linter).nil?
|
302
|
+
@options['linters'].merge!(default_plugin_options(linter))
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def default_plugin_options(linter)
|
308
|
+
{ self.class.linter_name(linter) => { 'enabled' => true } }
|
309
|
+
end
|
258
310
|
end
|
259
311
|
end
|
@@ -82,10 +82,21 @@ module SCSSLint
|
|
82
82
|
return unless comment_node = @disable_stack.pop
|
83
83
|
|
84
84
|
start_line = comment_node.line
|
85
|
+
if comment_node.class.node_name == :rule
|
86
|
+
end_line = start_line
|
87
|
+
elsif node.class.node_name == :root
|
88
|
+
end_line = @linter.engine.lines.length
|
89
|
+
else
|
90
|
+
end_line = end_line(node)
|
91
|
+
end
|
92
|
+
|
93
|
+
@disabled_lines.merge(start_line..end_line)
|
94
|
+
end
|
85
95
|
|
86
|
-
|
87
|
-
|
88
|
-
|
96
|
+
# Find the deepest child that has a line number to which a lint might
|
97
|
+
# apply (if it is a control comment enable node, it will be the line of
|
98
|
+
# the comment itself).
|
99
|
+
def end_line(node)
|
89
100
|
child = node
|
90
101
|
prev_child = node
|
91
102
|
until [nil, prev_child].include?(child = last_child(child))
|
@@ -94,9 +105,7 @@ module SCSSLint
|
|
94
105
|
|
95
106
|
# Fall back to prev_child if last_child() returned nil (i.e. node had no
|
96
107
|
# children with line numbers)
|
97
|
-
|
98
|
-
|
99
|
-
@disabled_lines.merge(start_line..end_line)
|
108
|
+
(child || prev_child).line
|
100
109
|
end
|
101
110
|
|
102
111
|
# Gets the child of the node that resides on the lowest line in the file.
|
data/lib/scss_lint/exceptions.rb
CHANGED
@@ -18,4 +18,7 @@ module SCSSLint::Exceptions
|
|
18
18
|
|
19
19
|
# Raised when a required library (specified via command line) does not exist.
|
20
20
|
class RequiredLibraryMissingError < StandardError; end
|
21
|
+
|
22
|
+
# Raised when a linter gem plugin is required but not installed.
|
23
|
+
class PluginGemLoadError < StandardError; end
|
21
24
|
end
|
@@ -23,6 +23,13 @@ module SCSSLint
|
|
23
23
|
.each { |_, color| record_lint(node, color) }
|
24
24
|
end
|
25
25
|
|
26
|
+
def visit_comment(_node)
|
27
|
+
# Don't lint children. Sass multiline comments (/*...*/) are actually
|
28
|
+
# rendered in code and thus allow variable interpolation. Unfortunately,
|
29
|
+
# the Sass parser returns bad source ranges for interpolation in these
|
30
|
+
# comments, so it's easiest to just ignore them.
|
31
|
+
end
|
32
|
+
|
26
33
|
private
|
27
34
|
|
28
35
|
def record_lint(node, color)
|
@@ -1,13 +1,8 @@
|
|
1
1
|
module SCSSLint
|
2
|
-
# Checks the format of declared names of functions, mixins, variables
|
3
|
-
# placeholders.
|
2
|
+
# Checks the format of declared names of functions, mixins, and variables.
|
4
3
|
class Linter::NameFormat < Linter
|
5
4
|
include LinterRegistry
|
6
5
|
|
7
|
-
def visit_extend(node)
|
8
|
-
check_placeholder(node)
|
9
|
-
end
|
10
|
-
|
11
6
|
def visit_function(node)
|
12
7
|
check_name(node, 'function')
|
13
8
|
yield # Continue into content block of this function definition
|
@@ -63,12 +58,6 @@ module SCSSLint
|
|
63
58
|
name
|
64
59
|
end
|
65
60
|
|
66
|
-
def check_placeholder(node)
|
67
|
-
extract_string_selectors(node.selector).any? do |selector_str|
|
68
|
-
check_name(node, 'placeholder', selector_str.gsub('%', ''))
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
61
|
CONVENTIONS = {
|
73
62
|
'camel_case' => {
|
74
63
|
explanation: 'should be written in camelCase format',
|
@@ -3,6 +3,8 @@ module SCSSLint
|
|
3
3
|
class Linter::NestingDepth < Linter
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
|
+
IGNORED_SELECTORS = [Sass::Selector::Parent, Sass::Selector::Pseudo]
|
7
|
+
|
6
8
|
def visit_root(_node)
|
7
9
|
@max_depth = config['max_depth']
|
8
10
|
@depth = 1
|
@@ -10,8 +12,11 @@ module SCSSLint
|
|
10
12
|
end
|
11
13
|
|
12
14
|
def visit_rule(node)
|
15
|
+
return yield if ignore_selectors?(node)
|
16
|
+
|
13
17
|
if @depth > @max_depth
|
14
|
-
add_lint
|
18
|
+
add_lint node, "Nesting should be no greater than #{@max_depth}, " \
|
19
|
+
"but was #{@depth}"
|
15
20
|
else
|
16
21
|
# Only continue if we didn't exceed the max depth already (this makes
|
17
22
|
# the lint less noisy)
|
@@ -20,5 +25,21 @@ module SCSSLint
|
|
20
25
|
@depth -= 1
|
21
26
|
end
|
22
27
|
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def ignore_selectors?(node)
|
32
|
+
return unless config['ignore_parent_selectors']
|
33
|
+
|
34
|
+
simple_selectors(node.parsed_rules).all? do |selector|
|
35
|
+
IGNORED_SELECTORS.include?(selector.class)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def simple_selectors(node)
|
40
|
+
node.members.flat_map(&:members).reject do |simple_sequence|
|
41
|
+
simple_sequence.is_a?(String)
|
42
|
+
end.flat_map(&:members)
|
43
|
+
end
|
23
44
|
end
|
24
45
|
end
|
@@ -80,6 +80,10 @@ module SCSSLint
|
|
80
80
|
.sort { |a, b| compare_properties(a, b) }
|
81
81
|
|
82
82
|
sorted_props.each_with_index do |prop, index|
|
83
|
+
# Once we reach the portion of the list with unspecified properties, we
|
84
|
+
# can stop checking since we don't care about order after that point
|
85
|
+
break unless specified_property?(prop[:property])
|
86
|
+
|
83
87
|
next unless prop != sortable_prop_info[index]
|
84
88
|
|
85
89
|
add_lint(sortable_prop_info[index][:node], lint_message(sorted_props))
|
@@ -186,6 +190,10 @@ module SCSSLint
|
|
186
190
|
!@preferred_order.include?(prop_node.name.join)
|
187
191
|
end
|
188
192
|
|
193
|
+
def specified_property?(prop_name)
|
194
|
+
!@preferred_order || @preferred_order.include?(prop_name)
|
195
|
+
end
|
196
|
+
|
189
197
|
def preset_order?
|
190
198
|
config['order'].is_a?(String)
|
191
199
|
end
|
@@ -3,6 +3,20 @@ module SCSSLint
|
|
3
3
|
class Linter::PropertyUnits < Linter
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
|
+
NUMBER_WITH_UNITS_REGEX = /
|
7
|
+
(?:
|
8
|
+
(["']).+?\1 # [0: quote mark] quoted string, e.g. "hi there"
|
9
|
+
| # or
|
10
|
+
(?:^|\s) # beginning of value or whitespace
|
11
|
+
(?:
|
12
|
+
\d+ # any number of digits, e.g. 123
|
13
|
+
| # or
|
14
|
+
\d*\.?\d+ # any number of digits with decimal, e.g. 1.23 or .123
|
15
|
+
)
|
16
|
+
([a-z%]+) # [1: units] letters or percent sign, e.g. px or %
|
17
|
+
)
|
18
|
+
/ix
|
19
|
+
|
6
20
|
def visit_root(_node)
|
7
21
|
@globally_allowed_units = config['global'].to_set
|
8
22
|
@allowed_units_for_property = config['properties']
|
@@ -18,9 +32,13 @@ module SCSSLint
|
|
18
32
|
property = "#{@nested_under}-#{property}"
|
19
33
|
end
|
20
34
|
|
21
|
-
if node.value.respond_to?(:value)
|
22
|
-
|
23
|
-
|
35
|
+
if node.value.respond_to?(:value)
|
36
|
+
node.value.value.to_s.scan(NUMBER_WITH_UNITS_REGEX).each do |matches|
|
37
|
+
is_quoted_value = !matches[0].nil?
|
38
|
+
next if is_quoted_value
|
39
|
+
units = matches[1]
|
40
|
+
check_units(node, property, units)
|
41
|
+
end
|
24
42
|
end
|
25
43
|
|
26
44
|
@nested_under = property
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks for spaces following the name of a variable and before the colon
|
3
|
+
# separating the variables's name from its value.
|
4
|
+
class SpaceAfterVariableName < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
def visit_variable(node)
|
8
|
+
return unless spaces_before_colon?(node)
|
9
|
+
add_lint(node, 'Variable names should be followed immediately by a colon')
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def spaces_before_colon?(node)
|
15
|
+
source_from_range(node.source_range) =~ /\s+:/
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks for trailing whitespace on a line.
|
3
|
+
class Linter::TrailingWhitespace < Linter
|
4
|
+
include LinterRegistry
|
5
|
+
|
6
|
+
def visit_root(_node)
|
7
|
+
engine.lines.each_with_index do |line, index|
|
8
|
+
next unless line =~ /[ \t]+$/
|
9
|
+
|
10
|
+
add_lint(index + 1, 'Line contains trailing whitespace')
|
11
|
+
end
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|