scss-lint 0.18.0 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/scss_lint.rb +1 -1
- data/lib/scss_lint/cli.rb +26 -9
- data/lib/scss_lint/config.rb +13 -6
- data/lib/scss_lint/lint.rb +8 -3
- data/lib/scss_lint/linter.rb +26 -16
- data/lib/scss_lint/linter/property_sort_order.rb +1 -1
- data/lib/scss_lint/linter/space_after_comma.rb +18 -5
- data/lib/scss_lint/linter/string_quotes.rb +2 -2
- data/lib/scss_lint/linter/zero_unit.rb +5 -1
- data/lib/scss_lint/runner.rb +5 -2
- data/lib/scss_lint/sass/script.rb +35 -3
- data/lib/scss_lint/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 851fc6dcdb6402f514538367d2a2e10afa223a23
|
4
|
+
data.tar.gz: f9c630b4c98aaf277a4277abb003144ba65e5bd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9016d101642210006cda461af3c990c6c0bb04e254ff49dd50e19ee3a04dbc87db450b8d687ae47972015bdc564cb3d2bd93aea9131065a79665777e016eff45
|
7
|
+
data.tar.gz: 3c9cffd85b8d147e1bcf4c7617a2cda7ea8cf81e50ff33a059b1ad0ca0634835c200802ade936fefdf6c89a283d37bc35fa41a719078fdbba4cb4147dbf15e13
|
data/lib/scss_lint.rb
CHANGED
data/lib/scss_lint/cli.rb
CHANGED
@@ -9,18 +9,19 @@ module SCSSLint
|
|
9
9
|
|
10
10
|
# Subset of semantic exit codes conforming to `sysexits` documentation.
|
11
11
|
EXIT_CODES = {
|
12
|
-
ok:
|
13
|
-
usage:
|
14
|
-
data:
|
15
|
-
no_input:
|
16
|
-
software:
|
17
|
-
config:
|
12
|
+
ok: 0,
|
13
|
+
usage: 64, # Command line usage error
|
14
|
+
data: 65, # User input was incorrect (i.e. contains lints)
|
15
|
+
no_input: 66, # Input file did not exist or was not readable
|
16
|
+
software: 70, # Internal software error
|
17
|
+
config: 78, # Configuration error
|
18
18
|
}
|
19
19
|
|
20
|
+
# @param args [Array]
|
20
21
|
def initialize(args = [])
|
21
|
-
@args
|
22
|
+
@args = args
|
22
23
|
@options = {}
|
23
|
-
@config
|
24
|
+
@config = Config.default
|
24
25
|
end
|
25
26
|
|
26
27
|
def parse_arguments
|
@@ -35,12 +36,13 @@ module SCSSLint
|
|
35
36
|
|
36
37
|
begin
|
37
38
|
setup_configuration
|
38
|
-
rescue NoSuchLinter => ex
|
39
|
+
rescue InvalidConfiguration, NoSuchLinter => ex
|
39
40
|
puts ex.message
|
40
41
|
halt :config
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
45
|
+
# @return [OptionParser]
|
44
46
|
def options_parser
|
45
47
|
@options_parser ||= OptionParser.new do |opts|
|
46
48
|
opts.banner = "Usage: #{opts.program_name} [options] [scss-files]"
|
@@ -90,6 +92,9 @@ module SCSSLint
|
|
90
92
|
runner.run(files_to_lint)
|
91
93
|
report_lints(runner.lints)
|
92
94
|
halt :data if runner.lints.any?
|
95
|
+
rescue InvalidConfiguration => ex
|
96
|
+
puts ex
|
97
|
+
halt :config
|
93
98
|
rescue NoFilesError, Errno::ENOENT => ex
|
94
99
|
puts ex.message
|
95
100
|
halt :no_input
|
@@ -114,6 +119,8 @@ module SCSSLint
|
|
114
119
|
merge_command_line_flags_with_config(@config)
|
115
120
|
end
|
116
121
|
|
122
|
+
# @param config [Config]
|
123
|
+
# @return [Config]
|
117
124
|
def merge_command_line_flags_with_config(config)
|
118
125
|
if @options[:excluded_files]
|
119
126
|
@options[:excluded_files].each do |file|
|
@@ -150,6 +157,7 @@ module SCSSLint
|
|
150
157
|
end
|
151
158
|
end
|
152
159
|
|
160
|
+
# @param list [Array]
|
153
161
|
def extract_files_from(list)
|
154
162
|
files = []
|
155
163
|
list.each do |file|
|
@@ -161,12 +169,15 @@ module SCSSLint
|
|
161
169
|
end
|
162
170
|
|
163
171
|
VALID_EXTENSIONS = %w[.css .scss]
|
172
|
+
# @param file [String]
|
173
|
+
# @return [Boolean]
|
164
174
|
def scssish_file?(file)
|
165
175
|
return false unless FileTest.file?(file)
|
166
176
|
|
167
177
|
VALID_EXTENSIONS.include?(File.extname(file))
|
168
178
|
end
|
169
179
|
|
180
|
+
# @param lints [Array<Lint>]
|
170
181
|
def report_lints(lints)
|
171
182
|
sorted_lints = lints.sort_by { |l| [l.filename, l.line] }
|
172
183
|
reporter = @options.fetch(:reporter, Reporter::DefaultReporter)
|
@@ -175,6 +186,7 @@ module SCSSLint
|
|
175
186
|
print output if output
|
176
187
|
end
|
177
188
|
|
189
|
+
# @param format [String]
|
178
190
|
def set_output_format(format)
|
179
191
|
@options[:reporter] = SCSSLint::Reporter.const_get(format + 'Reporter')
|
180
192
|
rescue NameError
|
@@ -196,18 +208,23 @@ module SCSSLint
|
|
196
208
|
halt
|
197
209
|
end
|
198
210
|
|
211
|
+
# @param help_message [String]
|
212
|
+
# @param err [Exception]
|
199
213
|
def print_help(help_message, err = nil)
|
200
214
|
puts err, '' if err
|
201
215
|
puts help_message
|
202
216
|
halt(err ? :usage : :ok)
|
203
217
|
end
|
204
218
|
|
219
|
+
# @param program_name [String]
|
220
|
+
# @param version [String]
|
205
221
|
def print_version(program_name, version)
|
206
222
|
puts "#{program_name} #{version}"
|
207
223
|
halt
|
208
224
|
end
|
209
225
|
|
210
226
|
# Used for ease-of testing
|
227
|
+
# @param exit_status [Symbol]
|
211
228
|
def halt(exit_status = :ok)
|
212
229
|
exit(EXIT_CODES[exit_status])
|
213
230
|
end
|
data/lib/scss_lint/config.rb
CHANGED
@@ -2,6 +2,9 @@ require 'pathname'
|
|
2
2
|
require 'yaml'
|
3
3
|
|
4
4
|
module SCSSLint
|
5
|
+
# Raised when the configuration file is invalid for some reason.
|
6
|
+
class InvalidConfiguration < StandardError; end
|
7
|
+
|
5
8
|
# Loads and manages application configuration.
|
6
9
|
class Config
|
7
10
|
FILE_NAME = '.scss-lint.yml'
|
@@ -61,12 +64,16 @@ module SCSSLint
|
|
61
64
|
def load_options_hash_from_file(file)
|
62
65
|
file_contents = load_file_contents(file)
|
63
66
|
|
64
|
-
|
65
|
-
|
66
|
-
yaml.
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
begin
|
68
|
+
options =
|
69
|
+
if yaml = YAML.load(file_contents)
|
70
|
+
yaml.to_hash
|
71
|
+
else
|
72
|
+
{}
|
73
|
+
end
|
74
|
+
rescue => ex
|
75
|
+
raise InvalidConfiguration, "Invalid configuration: #{ex.message}"
|
76
|
+
end
|
70
77
|
|
71
78
|
options = convert_single_options_to_arrays(options)
|
72
79
|
options = extend_inherited_configs(options, file)
|
data/lib/scss_lint/lint.rb
CHANGED
@@ -3,13 +3,18 @@ module SCSSLint
|
|
3
3
|
class Lint
|
4
4
|
attr_reader :filename, :line, :description, :severity
|
5
5
|
|
6
|
+
# @param filename [String]
|
7
|
+
# @param line [Integer]
|
8
|
+
# @param description [String]
|
9
|
+
# @param severity [Symbol]
|
6
10
|
def initialize(filename, line, description, severity = :warning)
|
7
|
-
@filename
|
8
|
-
@line
|
11
|
+
@filename = filename
|
12
|
+
@line = line
|
9
13
|
@description = description
|
10
|
-
@severity
|
14
|
+
@severity = severity
|
11
15
|
end
|
12
16
|
|
17
|
+
# @return [Boolean]
|
13
18
|
def error?
|
14
19
|
severity == :error
|
15
20
|
end
|
data/lib/scss_lint/linter.rb
CHANGED
@@ -10,6 +10,8 @@ module SCSSLint
|
|
10
10
|
@lints = []
|
11
11
|
end
|
12
12
|
|
13
|
+
# @param engine [Engine]
|
14
|
+
# @param config [Config]
|
13
15
|
def run(engine, config)
|
14
16
|
@config = config
|
15
17
|
@engine = engine
|
@@ -17,11 +19,15 @@ module SCSSLint
|
|
17
19
|
end
|
18
20
|
|
19
21
|
# Define if you want a default message for your linter
|
22
|
+
# @return [String, nil]
|
20
23
|
def description
|
21
24
|
nil
|
22
25
|
end
|
23
26
|
|
24
27
|
# Helper for creating lint from a parse tree node
|
28
|
+
#
|
29
|
+
# @param node_or_line [Sass::Script::Tree::Node, Sass::Engine::Line]
|
30
|
+
# @param message [String, nil]
|
25
31
|
def add_lint(node_or_line, message = nil)
|
26
32
|
line = node_or_line.respond_to?(:line) ? node_or_line.line : node_or_line
|
27
33
|
|
@@ -30,9 +36,11 @@ module SCSSLint
|
|
30
36
|
message || description)
|
31
37
|
end
|
32
38
|
|
33
|
-
#
|
39
|
+
# @param source_position [Sass::Source::Position]
|
40
|
+
# @param offset [Integer]
|
41
|
+
# @return [String] the character at the given [Sass::Source::Position]
|
34
42
|
def character_at(source_position, offset = 0)
|
35
|
-
actual_line
|
43
|
+
actual_line = source_position.line - 1
|
36
44
|
actual_offset = source_position.offset + offset - 1
|
37
45
|
|
38
46
|
# Return a newline if offset points at the very end of the line
|
@@ -42,11 +50,19 @@ module SCSSLint
|
|
42
50
|
end
|
43
51
|
|
44
52
|
# Extracts the original source code given a range.
|
53
|
+
#
|
54
|
+
# @param source_range [Sass::Source::Range]
|
55
|
+
# @return [String] the original source code
|
45
56
|
def source_from_range(source_range)
|
46
57
|
current_line = source_range.start_pos.line - 1
|
47
|
-
last_line
|
58
|
+
last_line = source_range.end_pos.line - 1
|
59
|
+
start_pos = source_range.start_pos.offset - 1
|
48
60
|
|
49
|
-
|
61
|
+
if current_line == last_line
|
62
|
+
source = engine.lines[current_line][start_pos..(source_range.end_pos.offset - 1)]
|
63
|
+
else
|
64
|
+
source = engine.lines[current_line][start_pos..-1]
|
65
|
+
end
|
50
66
|
|
51
67
|
current_line += 1
|
52
68
|
while current_line < last_line
|
@@ -64,19 +80,10 @@ module SCSSLint
|
|
64
80
|
source
|
65
81
|
end
|
66
82
|
|
67
|
-
# Monkey-patched implementation that adds support for traversing
|
68
|
-
# Sass::Script::Nodes (original implementation only supports
|
69
|
-
# Sass::Tree::Nodes).
|
70
|
-
def self.node_name(node)
|
71
|
-
case node
|
72
|
-
when Sass::Script::Tree::Node, Sass::Script::Value::Base
|
73
|
-
"script_#{node.class.name.gsub(/.*::(.*?)$/, '\\1').downcase}"
|
74
|
-
else
|
75
|
-
super
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
83
|
# Modified so we can also visit selectors in linters
|
84
|
+
#
|
85
|
+
# @param node [Sass::Tree::Node, Sass::Script::Tree::Node,
|
86
|
+
# Sass::Script::Value::Base]
|
80
87
|
def visit(node)
|
81
88
|
# Visit the selector of a rule if parsed rules are available
|
82
89
|
if node.is_a?(Sass::Tree::RuleNode) && node.parsed_rules
|
@@ -87,6 +94,9 @@ module SCSSLint
|
|
87
94
|
end
|
88
95
|
|
89
96
|
# Redefine so we can set the `node_parent` of each node
|
97
|
+
#
|
98
|
+
# @param parent [Sass::Tree::Node, Sass::Script::Tree::Node,
|
99
|
+
# Sass::Script::Value::Base]
|
90
100
|
def visit_children(parent)
|
91
101
|
parent.children.each do |child|
|
92
102
|
child.node_parent = parent
|
@@ -13,7 +13,7 @@ module SCSSLint
|
|
13
13
|
sortable_prop_names = sortable_props.map { |child| child.name.join }
|
14
14
|
|
15
15
|
sorted_prop_names = sortable_prop_names.map do |name|
|
16
|
-
/^(?<vendor>-\w
|
16
|
+
/^(?<vendor>-\w+(-osx)?-)?(?<property>.+)/ =~ name
|
17
17
|
{ name: name, vendor: vendor, property: property }
|
18
18
|
end.sort { |a, b| compare_properties(a, b) }
|
19
19
|
.map { |fields| fields[:name] }
|
@@ -66,20 +66,33 @@ module SCSSLint
|
|
66
66
|
def check_commas_after_args(args, arg_type)
|
67
67
|
# For each arg except the last, check the character following the comma
|
68
68
|
args[0..-2].each do |arg|
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
offset = 0
|
70
|
+
|
71
|
+
# Find the comma following this argument.
|
72
|
+
# The Sass parser is unpredictable in where it marks the end of the
|
73
|
+
# source range. Thus we need to start at the indicated range, and check
|
74
|
+
# left and right of that range, gradually moving further outward until
|
75
|
+
# we find the comma.
|
76
|
+
if character_at(arg.source_range.end_pos, offset) != ','
|
77
|
+
loop do
|
78
|
+
offset += 1
|
79
|
+
break if character_at(arg.source_range.end_pos, offset) == ','
|
80
|
+
offset = -offset
|
81
|
+
break if character_at(arg.source_range.end_pos, offset) == ','
|
82
|
+
offset = -offset
|
83
|
+
end
|
84
|
+
end
|
72
85
|
|
73
86
|
# Check for space or newline after arg (we allow arguments to be split
|
74
87
|
# up over multiple lines).
|
75
88
|
spaces = 0
|
76
|
-
while character_at(arg.source_range.end_pos, offset) =~ / |\n/
|
89
|
+
while character_at(arg.source_range.end_pos, offset + 1) =~ / |\n/
|
77
90
|
spaces += 1
|
78
91
|
offset += 1
|
79
92
|
end
|
80
93
|
|
81
94
|
if spaces != EXPECTED_SPACES_AFTER_COMMA
|
82
|
-
add_lint arg,
|
95
|
+
add_lint arg, "Commas in #{arg_type} should be followed by a single space"
|
83
96
|
end
|
84
97
|
end
|
85
98
|
end
|
@@ -8,12 +8,12 @@ module SCSSLint
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def visit_import(node)
|
11
|
-
#
|
11
|
+
# `@import` source range conveniently includes only the quoted string
|
12
12
|
check_quotes(node, source_from_range(node.source_range))
|
13
13
|
end
|
14
14
|
|
15
15
|
def visit_charset(node)
|
16
|
-
#
|
16
|
+
# `@charset` source range includes entire declaration, so exclude that prefix
|
17
17
|
source = source_from_range(node.source_range)[('@charset'.length)..-1]
|
18
18
|
|
19
19
|
check_quotes(node, source)
|
@@ -10,7 +10,7 @@ module SCSSLint
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def visit_script_number(node)
|
13
|
-
if node.value == 0 &&
|
13
|
+
if node.value == 0 && zero_with_units?(source_from_range(node.source_range))
|
14
14
|
add_lint(node, MESSAGE_FORMAT % node.original_string)
|
15
15
|
end
|
16
16
|
end
|
@@ -18,5 +18,9 @@ module SCSSLint
|
|
18
18
|
private
|
19
19
|
|
20
20
|
MESSAGE_FORMAT = '`%s` should be written without units as `0`'
|
21
|
+
|
22
|
+
def zero_with_units?(string)
|
23
|
+
string =~ /^0[a-z]+/
|
24
|
+
end
|
21
25
|
end
|
22
26
|
end
|
data/lib/scss_lint/runner.rb
CHANGED
@@ -7,12 +7,14 @@ module SCSSLint
|
|
7
7
|
class Runner
|
8
8
|
attr_reader :lints
|
9
9
|
|
10
|
+
# @param config [Config]
|
10
11
|
def initialize(config)
|
11
|
-
@config
|
12
|
-
@lints
|
12
|
+
@config = config
|
13
|
+
@lints = []
|
13
14
|
@linters = LinterRegistry.linters.map(&:new)
|
14
15
|
end
|
15
16
|
|
17
|
+
# @param files [Array]
|
16
18
|
def run(files)
|
17
19
|
raise NoFilesError, 'No SCSS files specified' if files.empty?
|
18
20
|
|
@@ -27,6 +29,7 @@ module SCSSLint
|
|
27
29
|
|
28
30
|
private
|
29
31
|
|
32
|
+
# @param file [String]
|
30
33
|
def find_lints(file)
|
31
34
|
engine = Engine.new(file)
|
32
35
|
config = @config.preferred ? @config : Config.for_file(file)
|
@@ -1,7 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# rubocop:disable Documentation
|
1
|
+
# Ignore documentation lints as these aren't original implementations.
|
2
|
+
# rubocop:disable Documentation
|
4
3
|
|
4
|
+
module Sass::Script
|
5
5
|
# Redefine some of the lexer helpers in order to store the original string
|
6
6
|
# with the created object so that the original string can be inspected rather
|
7
7
|
# than a typically normalized version.
|
@@ -43,6 +43,38 @@ module Sass::Script
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
+
# Since the Sass library is already loaded at this point.
|
47
|
+
# Define the `node_name` and `visit_method` class methods for each Sass Script
|
48
|
+
# parse tree node type so that our custom visitor can seamless traverse the
|
49
|
+
# tree.
|
50
|
+
#
|
51
|
+
# This would be easier if we could just define an `inherited` callback, but
|
52
|
+
# that won't work since the Sass library will have already been loaded before
|
53
|
+
# this code gets loaded, so the `inherited` callback won't be fired.
|
54
|
+
#
|
55
|
+
# Thus we are left to manually define the methods for each type explicitly.
|
56
|
+
{
|
57
|
+
'Value' => %w[ArgList Bool Color List Map Null Number String],
|
58
|
+
'Tree' => %w[Funcall Interpolation ListLiteral Literal MapLiteral
|
59
|
+
Operation StringInterpolation UnaryOperation Variable],
|
60
|
+
}.each do |namespace, types|
|
61
|
+
types.each do |type|
|
62
|
+
node_name = type.downcase
|
63
|
+
|
64
|
+
eval <<-DECL
|
65
|
+
class #{namespace}::#{type}
|
66
|
+
def self.node_name
|
67
|
+
:script_#{node_name}
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.visit_method
|
71
|
+
:visit_script_#{node_name}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
DECL
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
46
78
|
class Value::Base
|
47
79
|
attr_accessor :node_parent
|
48
80
|
|
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.19.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-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: colorize
|
@@ -29,16 +29,16 @@ dependencies:
|
|
29
29
|
name: sass
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
|
-
- -
|
32
|
+
- - ~>
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 3.3.0
|
34
|
+
version: 3.3.0
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
|
-
- -
|
39
|
+
- - ~>
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: 3.3.0
|
41
|
+
version: 3.3.0
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: nokogiri
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- - '='
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: 2.14.1
|
70
|
-
description: Configurable tool
|
70
|
+
description: Configurable tool for writing clean and consistent SCSS
|
71
71
|
email:
|
72
72
|
- eng@causes.com
|
73
73
|
- shane@causes.com
|