scss-lint 0.18.0 → 0.19.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/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
|