scss_lint 0.39.0 → 0.40.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.
- 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
|