scss_lint 0.41.0 → 0.42.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +21 -0
- data/config/default.yml +1 -0
- data/lib/scss_lint/cli.rb +0 -4
- data/lib/scss_lint/config.rb +1 -1
- data/lib/scss_lint/engine.rb +8 -2
- data/lib/scss_lint/exceptions.rb +0 -4
- data/lib/scss_lint/file_finder.rb +1 -8
- data/lib/scss_lint/linter.rb +3 -3
- data/lib/scss_lint/linter/color_variable.rb +20 -0
- data/lib/scss_lint/linter/else_placement.rb +1 -0
- data/lib/scss_lint/linter/import_path.rb +2 -2
- data/lib/scss_lint/linter/name_format.rb +1 -1
- data/lib/scss_lint/linter/space_after_comma.rb +11 -2
- data/lib/scss_lint/linter/space_around_operator.rb +115 -59
- data/lib/scss_lint/linter/trailing_semicolon.rb +17 -3
- data/lib/scss_lint/linter/url_format.rb +3 -3
- data/lib/scss_lint/linter/variable_for_property.rb +8 -0
- data/lib/scss_lint/linter/vendor_prefix.rb +1 -1
- data/lib/scss_lint/rake_task.rb +6 -1
- data/lib/scss_lint/runner.rb +2 -2
- data/lib/scss_lint/sass/tree.rb +1 -1
- data/lib/scss_lint/selector_visitor.rb +4 -4
- data/lib/scss_lint/version.rb +1 -1
- data/spec/scss_lint/cli_spec.rb +0 -12
- data/spec/scss_lint/file_finder_spec.rb +2 -6
- data/spec/scss_lint/linter/color_variable_spec.rb +41 -1
- data/spec/scss_lint/linter/space_after_comma_spec.rb +891 -153
- data/spec/scss_lint/linter/space_around_operator_spec.rb +25 -0
- data/spec/scss_lint/linter/trailing_semicolon_spec.rb +50 -0
- data/spec/scss_lint/linter/variable_for_property_spec.rb +10 -0
- data/spec/scss_lint/rake_task_spec.rb +59 -10
- data/spec/scss_lint/reporter/clean_files_reporter_spec.rb +2 -2
- data/spec/scss_lint/reporter/default_reporter_spec.rb +3 -3
- data/spec/scss_lint/reporter/files_reporter_spec.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: 84412ffcfbcad02e1d135dae52e56c33c0f42c19
|
4
|
+
data.tar.gz: af6e1f97f4755ab0b69cf7b4162412571aa42704
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1fe04a0e8578d96cb59af5ea52bb4ca274d386153bbe9cb47541bb6f04905ff5e47062237bb7956dfcbdf860fde3c3814344321a62e1d3d3f21cb92a99939ebf
|
7
|
+
data.tar.gz: cefba0a8a5a0ab3b7c19e706efaaac38fbde69fabcd2ae813f4fc363ce2e42babd5fbca2261cf5db9c964a05d35dc2a262d756db45e88c5ced577a5b76b8a4c5
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2014-2015 Brigade
|
2
|
+
http://www.brigade.com/
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/config/default.yml
CHANGED
data/lib/scss_lint/cli.rb
CHANGED
@@ -19,7 +19,6 @@ module SCSSLint
|
|
19
19
|
software: 70, # Internal software error
|
20
20
|
config: 78, # Configuration error
|
21
21
|
no_files: 80, # No files matched by specified glob patterns
|
22
|
-
files_filtered: 81, # All matched files were filtered by exclusions
|
23
22
|
plugin: 82, # Plugin loading error
|
24
23
|
}
|
25
24
|
|
@@ -79,9 +78,6 @@ module SCSSLint
|
|
79
78
|
when SCSSLint::Exceptions::RequiredLibraryMissingError
|
80
79
|
puts exception.message
|
81
80
|
halt :unavailable
|
82
|
-
when SCSSLint::Exceptions::AllFilesFilteredError
|
83
|
-
puts exception.message
|
84
|
-
halt :files_filtered
|
85
81
|
when SCSSLint::Exceptions::NoFilesError
|
86
82
|
puts exception.message
|
87
83
|
halt :no_files
|
data/lib/scss_lint/config.rb
CHANGED
@@ -156,7 +156,7 @@ module SCSSLint
|
|
156
156
|
else
|
157
157
|
path = File.join(File.dirname(base_config_path), relative_include_path)
|
158
158
|
# Remove double backslashes appearing in Windows paths.
|
159
|
-
path.
|
159
|
+
path.sub(%r{^//}, File::SEPARATOR)
|
160
160
|
end
|
161
161
|
end
|
162
162
|
|
data/lib/scss_lint/engine.rb
CHANGED
@@ -8,7 +8,7 @@ module SCSSLint
|
|
8
8
|
class Engine
|
9
9
|
ENGINE_OPTIONS = { cache: false, syntax: :scss }
|
10
10
|
|
11
|
-
attr_reader :contents, :filename, :lines, :tree
|
11
|
+
attr_reader :contents, :filename, :lines, :tree, :any_control_commands
|
12
12
|
|
13
13
|
# Creates a parsed representation of an SCSS document from the given string
|
14
14
|
# or file.
|
@@ -27,7 +27,8 @@ module SCSSLint
|
|
27
27
|
# Need `to_a` for Ruby 1.9.3.
|
28
28
|
@lines = @contents.force_encoding('UTF-8').lines.to_a
|
29
29
|
@tree = @engine.to_tree
|
30
|
-
|
30
|
+
find_any_control_commands
|
31
|
+
rescue Encoding::UndefinedConversionError, Sass::SyntaxError, ArgumentError => error
|
31
32
|
if error.is_a?(Encoding::UndefinedConversionError) ||
|
32
33
|
error.message.match(/invalid.*(byte sequence|character)/i)
|
33
34
|
raise FileEncodingError,
|
@@ -52,5 +53,10 @@ module SCSSLint
|
|
52
53
|
@engine = Sass::Engine.new(scss, ENGINE_OPTIONS)
|
53
54
|
@contents = scss
|
54
55
|
end
|
56
|
+
|
57
|
+
def find_any_control_commands
|
58
|
+
@any_control_commands =
|
59
|
+
@lines.any? { |line| line['scss-lint:disable'] || line['scss-line:enable'] }
|
60
|
+
end
|
55
61
|
end
|
56
62
|
end
|
data/lib/scss_lint/exceptions.rb
CHANGED
@@ -1,8 +1,4 @@
|
|
1
1
|
module SCSSLint::Exceptions
|
2
|
-
# Raised when all files matched by the specified glob patterns were filtered
|
3
|
-
# by exclude patterns.
|
4
|
-
class AllFilesFilteredError < StandardError; end
|
5
|
-
|
6
2
|
# Raised when an invalid flag is given via the command line.
|
7
3
|
class InvalidCLIOption < StandardError; end
|
8
4
|
|
@@ -28,14 +28,7 @@ module SCSSLint
|
|
28
28
|
"No SCSS files matched by the patterns: #{patterns.join(' ')}"
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
if filtered_files.empty?
|
33
|
-
raise SCSSLint::Exceptions::AllFilesFilteredError,
|
34
|
-
"All files matched by the patterns [#{patterns.join(', ')}] " \
|
35
|
-
"were excluded by the patterns: [#{@config.exclude_patterns.join(', ')}]"
|
36
|
-
end
|
37
|
-
|
38
|
-
filtered_files
|
31
|
+
matched_files.reject { |file| @config.excluded_file?(file) }
|
39
32
|
end
|
40
33
|
|
41
34
|
private
|
data/lib/scss_lint/linter.rb
CHANGED
@@ -68,7 +68,7 @@ module SCSSLint
|
|
68
68
|
actual_line = source_position.line - 1
|
69
69
|
actual_offset = source_position.offset + offset - 1
|
70
70
|
|
71
|
-
engine.lines[actual_line][actual_offset]
|
71
|
+
engine.lines.size > actual_line && engine.lines[actual_line][actual_offset]
|
72
72
|
end
|
73
73
|
|
74
74
|
# Extracts the original source code given a range.
|
@@ -127,9 +127,9 @@ module SCSSLint
|
|
127
127
|
visit_selector(node.parsed_rules)
|
128
128
|
end
|
129
129
|
|
130
|
-
@comment_processor.before_node_visit(node)
|
130
|
+
@comment_processor.before_node_visit(node) if @engine.any_control_commands
|
131
131
|
super
|
132
|
-
@comment_processor.after_node_visit(node)
|
132
|
+
@comment_processor.after_node_visit(node) if @engine.any_control_commands
|
133
133
|
end
|
134
134
|
|
135
135
|
# Redefine so we can set the `node_parent` of each node
|
@@ -3,6 +3,8 @@ module SCSSLint
|
|
3
3
|
class Linter::ColorVariable < Linter
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
|
+
COLOR_FUNCTIONS = %w[rgb rgba hsl hsla]
|
7
|
+
|
6
8
|
def visit_script_color(node)
|
7
9
|
return if in_variable_declaration?(node) ||
|
8
10
|
in_map_declaration?(node) ||
|
@@ -30,6 +32,14 @@ module SCSSLint
|
|
30
32
|
# comments, so it's easiest to just ignore them.
|
31
33
|
end
|
32
34
|
|
35
|
+
def visit_script_funcall(node)
|
36
|
+
if color_function?(node) && all_arguments_are_literals?(node)
|
37
|
+
record_lint node, node.to_sass
|
38
|
+
else
|
39
|
+
yield
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
33
43
|
private
|
34
44
|
|
35
45
|
def record_lint(node, color)
|
@@ -63,5 +73,15 @@ module SCSSLint
|
|
63
73
|
def in_map_declaration?(node)
|
64
74
|
node_ancestor(node, 2).is_a?(Sass::Script::Tree::MapLiteral)
|
65
75
|
end
|
76
|
+
|
77
|
+
def all_arguments_are_literals?(node)
|
78
|
+
node.args.all? do |arg|
|
79
|
+
arg.is_a?(Sass::Script::Tree::Literal)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def color_function?(node)
|
84
|
+
COLOR_FUNCTIONS.include?(node.name)
|
85
|
+
end
|
66
86
|
end
|
67
87
|
end
|
@@ -43,13 +43,13 @@ module SCSSLint
|
|
43
43
|
fixed_basename = orig_basename
|
44
44
|
|
45
45
|
if config['leading_underscore']
|
46
|
-
fixed_basename = '_' + fixed_basename unless fixed_basename.
|
46
|
+
fixed_basename = '_' + fixed_basename unless fixed_basename.start_with?('_')
|
47
47
|
else
|
48
48
|
fixed_basename = fixed_basename.sub(/^_/, '')
|
49
49
|
end
|
50
50
|
|
51
51
|
if config['filename_extension']
|
52
|
-
fixed_basename += '.scss' unless fixed_basename.
|
52
|
+
fixed_basename += '.scss' unless fixed_basename.end_with?('.scss')
|
53
53
|
else
|
54
54
|
fixed_basename = fixed_basename.sub(/\.scss$/, '')
|
55
55
|
end
|
@@ -59,7 +59,16 @@ module SCSSLint
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
|
62
|
+
def valid_spaces_after_comma?(spaces)
|
63
|
+
case config['style']
|
64
|
+
when 'one_space'
|
65
|
+
spaces == 1
|
66
|
+
when 'no_space'
|
67
|
+
spaces == 0
|
68
|
+
when 'at_least_one_space'
|
69
|
+
spaces >= 1
|
70
|
+
end
|
71
|
+
end
|
63
72
|
|
64
73
|
# Check the comma after each argument in a list for a space following it,
|
65
74
|
# reporting a lint using the given [arg_type].
|
@@ -79,7 +88,7 @@ module SCSSLint
|
|
79
88
|
offset += 1
|
80
89
|
end
|
81
90
|
next if char == "\n" || # Ignore trailing spaces
|
82
|
-
spaces
|
91
|
+
valid_spaces_after_comma?(spaces)
|
83
92
|
|
84
93
|
add_lint arg, "Commas in #{arg_type} should be followed by a single space"
|
85
94
|
end
|
@@ -3,20 +3,32 @@ module SCSSLint
|
|
3
3
|
class Linter::SpaceAroundOperator < Linter
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
|
-
def visit_script_operation(node)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
6
|
+
def visit_script_operation(node)
|
7
|
+
operation_sources = OperationSources.new(node, self)
|
8
|
+
operation_sources.adjust_sources
|
9
|
+
|
10
|
+
# When an operation is found interpolated within something not a String
|
11
|
+
# (only selectors?), the source ranges are offset by two (probably not
|
12
|
+
# accounting for the `#{`. Slide everything to the left by 2, and maybe
|
13
|
+
# things will look sane this time.
|
14
|
+
unless operation_sources.operator_source =~ Sass::Script::Lexer::REGULAR_EXPRESSIONS[:op]
|
15
|
+
operation_sources.adjust_for_interpolation
|
16
|
+
operation_sources.adjust_sources
|
17
|
+
end
|
18
|
+
|
19
|
+
check(node, operation_sources)
|
20
|
+
|
21
|
+
yield
|
22
|
+
end
|
23
|
+
|
24
|
+
def source_fm_range(range)
|
25
|
+
source_from_range(range)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def check(node, operation_sources)
|
31
|
+
match = operation_sources.operator_source.match(/
|
20
32
|
(?<left_space>\s*)
|
21
33
|
(?<operator>\S+)
|
22
34
|
(?<right_space>\s*)
|
@@ -24,63 +36,107 @@ module SCSSLint
|
|
24
36
|
|
25
37
|
if config['style'] == 'one_space'
|
26
38
|
if match[:left_space] != ' ' || match[:right_space] != ' '
|
27
|
-
add_lint(node,
|
39
|
+
add_lint(node, operation_sources.space_msg(match[:operator]))
|
28
40
|
end
|
29
41
|
elsif match[:left_space] != '' || match[:right_space] != ''
|
30
|
-
add_lint(node,
|
42
|
+
add_lint(node, operation_sources.no_space_msg(match[:operator]))
|
31
43
|
end
|
32
|
-
|
33
|
-
yield
|
34
44
|
end
|
35
45
|
|
36
|
-
|
46
|
+
# A helper class for storing and adjusting the sources of the different
|
47
|
+
# components of an Operation node.
|
48
|
+
class OperationSources
|
49
|
+
attr_reader :operator_source
|
37
50
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# the #chop comment above.
|
46
|
-
between_start = Sass::Source::Position.new(
|
47
|
-
range1.end_pos.line,
|
48
|
-
range1.end_pos.offset,
|
49
|
-
)
|
50
|
-
between_end = Sass::Source::Position.new(
|
51
|
-
range2.start_pos.line,
|
52
|
-
range2.start_pos.offset - 1,
|
53
|
-
)
|
54
|
-
|
55
|
-
source_from_range(Sass::Source::Range.new(between_start,
|
56
|
-
between_end,
|
57
|
-
range1.file,
|
58
|
-
range1.importer))
|
59
|
-
end
|
51
|
+
def initialize(node, linter)
|
52
|
+
@node = node
|
53
|
+
@linter = linter
|
54
|
+
@source = normalize_source(@linter.source_fm_range(@node.source_range))
|
55
|
+
@left_range = @node.operand1.source_range
|
56
|
+
@right_range = @node.operand2.source_range
|
57
|
+
end
|
60
58
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
59
|
+
def adjust_sources
|
60
|
+
# We need to #chop at the end because an operation's operand1 _always_
|
61
|
+
# includes one character past the actual operand (which is either a
|
62
|
+
# whitespace character, or the first character of the operation).
|
63
|
+
@left_source = normalize_source(@linter.source_fm_range(@left_range))
|
64
|
+
@right_source = normalize_source(@linter.source_fm_range(@right_range))
|
65
|
+
@operator_source = calculate_operator_source
|
66
|
+
adjust_left_boundary
|
67
|
+
end
|
65
68
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
left += match[0]
|
72
|
-
operator = operator[match.end(0)..-1]
|
69
|
+
def adjust_for_interpolation
|
70
|
+
@source = normalize_source(
|
71
|
+
@linter.source_fm_range(slide_to_the_left(@node.source_range)))
|
72
|
+
@left_range = slide_to_the_left(@node.operand1.source_range)
|
73
|
+
@right_range = slide_to_the_left(@node.operand2.source_range)
|
73
74
|
end
|
74
75
|
|
75
|
-
|
76
|
-
|
77
|
-
# end_pos, which is not the case with simple, non-operation operands.
|
78
|
-
if match = left.match(/\s+$/)
|
79
|
-
left = left[0..match.begin(0)]
|
80
|
-
operator = match[0] + operator
|
76
|
+
def space_msg(operator)
|
77
|
+
SPACE_MSG % [@source, @left_source, operator, @right_source]
|
81
78
|
end
|
82
79
|
|
83
|
-
|
80
|
+
def no_space_msg(operator)
|
81
|
+
NO_SPACE_MSG % [@source, @left_source, operator, @right_source]
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
SPACE_MSG = '`%s` should be written with a single space on each side of ' \
|
87
|
+
'the operator: `%s %s %s`'
|
88
|
+
|
89
|
+
NO_SPACE_MSG = '`%s` should be written without spaces around the ' \
|
90
|
+
'operator: `%s%s%s`'
|
91
|
+
|
92
|
+
def calculate_operator_source
|
93
|
+
# We don't want to add 1 to range1.end_pos.offset for the same reason as
|
94
|
+
# the #chop comment above.
|
95
|
+
between_start = Sass::Source::Position.new(
|
96
|
+
@left_range.end_pos.line,
|
97
|
+
@left_range.end_pos.offset,
|
98
|
+
)
|
99
|
+
between_end = Sass::Source::Position.new(
|
100
|
+
@right_range.start_pos.line,
|
101
|
+
@right_range.start_pos.offset - 1,
|
102
|
+
)
|
103
|
+
|
104
|
+
@linter.source_fm_range(Sass::Source::Range.new(between_start,
|
105
|
+
between_end,
|
106
|
+
@left_range.file,
|
107
|
+
@left_range.importer))
|
108
|
+
end
|
109
|
+
|
110
|
+
def adjust_left_boundary
|
111
|
+
# If the left operand is wrapped in parentheses, any right parens end up
|
112
|
+
# in the operator source. Here, we move them into the left operand
|
113
|
+
# source, which is awkward in any messaging, but it works.
|
114
|
+
if match = @operator_source.match(/^(\s*\))+/)
|
115
|
+
@left_source += match[0]
|
116
|
+
@operator_source = @operator_source[match.end(0)..-1]
|
117
|
+
end
|
118
|
+
|
119
|
+
# If the left operand is a nested operation, Sass includes any whitespace
|
120
|
+
# before the (outer) operator in the left operator's source_range's
|
121
|
+
# end_pos, which is not the case with simple, non-operation operands.
|
122
|
+
if match = @left_source.match(/\s+$/)
|
123
|
+
@left_source = @left_source[0..match.begin(0)]
|
124
|
+
@operator_source = match[0] + @operator_source
|
125
|
+
end
|
126
|
+
|
127
|
+
[@left_source, @operator_source]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Removes trailing parentheses and compacts newlines into a single space
|
131
|
+
def normalize_source(source)
|
132
|
+
source.chop.gsub(/\s*\n\s*/, ' ')
|
133
|
+
end
|
134
|
+
|
135
|
+
def slide_to_the_left(range)
|
136
|
+
start_pos = Sass::Source::Position.new(range.start_pos.line, range.start_pos.offset - 2)
|
137
|
+
end_pos = Sass::Source::Position.new(range.end_pos.line, range.end_pos.offset - 2)
|
138
|
+
Sass::Source::Range.new(start_pos, end_pos, range.file, range.importer)
|
139
|
+
end
|
84
140
|
end
|
85
141
|
end
|
86
142
|
end
|