scss-lint 0.11.1 → 0.12.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 +5 -3
- data/lib/scss_lint/cli.rb +9 -2
- data/lib/scss_lint/constants.rb +5 -0
- data/lib/scss_lint/linter.rb +11 -0
- data/lib/scss_lint/linter/border_zero.rb +8 -7
- data/lib/scss_lint/linter/capitalization_in_selector.rb +1 -0
- data/lib/scss_lint/linter/color_keyword.rb +2 -0
- data/lib/scss_lint/linter/comment.rb +1 -0
- data/lib/scss_lint/linter/debug_statement.rb +1 -0
- data/lib/scss_lint/linter/declaration_order.rb +1 -0
- data/lib/scss_lint/linter/declared_name.rb +2 -0
- data/lib/scss_lint/linter/id_with_extraneous_selector.rb +1 -0
- data/lib/scss_lint/linter/indentation.rb +1 -0
- data/lib/scss_lint/linter/leading_zero.rb +1 -0
- data/lib/scss_lint/linter/placeholder_in_extend.rb +1 -0
- data/lib/scss_lint/linter/shorthand.rb +2 -0
- data/lib/scss_lint/linter/single_line_per_selector.rb +1 -0
- data/lib/scss_lint/linter/sorted_properties.rb +1 -0
- data/lib/scss_lint/linter/space_after_comma.rb +87 -0
- data/lib/scss_lint/linter/space_after_property_colon.rb +39 -0
- data/lib/scss_lint/linter/space_after_property_name.rb +13 -0
- data/lib/scss_lint/linter/space_before_brace.rb +1 -0
- data/lib/scss_lint/linter/trailing_semicolon_after_property_value.rb +29 -0
- data/lib/scss_lint/linter/zero_unit.rb +1 -0
- data/lib/scss_lint/runner.rb +18 -7
- data/lib/{sass → scss_lint/sass}/script.rb +0 -0
- data/lib/{sass → scss_lint/sass}/tree.rb +0 -0
- data/lib/scss_lint/utils.rb +5 -0
- data/lib/scss_lint/version.rb +1 -1
- metadata +10 -6
- data/lib/scss_lint/linter/property_format.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5efc8809d431b7de019112e2771839116d88df95
|
4
|
+
data.tar.gz: d54b6ba942585c4fbdc969c389af86e0da47088c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 406a4cd62baa39e5dc561c5cd84cedc7ef7b4d64a0b8813a693c22fb3192640f0382b3a839f317fdbe0f75738c6ed53b3f494bd5e9c32ed50f79833841e76f43
|
7
|
+
data.tar.gz: e0c8b74d3cf6bb80f592cfacc9ed25fe642b397e36ca9e965ee31a4889a8291ea2b88334aba45aae0c4eaf5cf52c610150bf6bc0b2ecd5b6af92aa4547ea21b5
|
data/lib/scss_lint.rb
CHANGED
@@ -10,12 +10,14 @@ module SCSSLint
|
|
10
10
|
autoload :Runner, 'scss_lint/runner'
|
11
11
|
autoload :SelectorVisitor, 'scss_lint/selector_visitor'
|
12
12
|
autoload :Utils, 'scss_lint/utils'
|
13
|
-
|
13
|
+
|
14
|
+
require 'scss_lint/constants'
|
15
|
+
require 'scss_lint/version'
|
14
16
|
|
15
17
|
# Preload Sass so we can monkey patch it
|
16
18
|
require 'sass'
|
17
|
-
require File.expand_path('sass/script', File.dirname(__FILE__))
|
18
|
-
require File.expand_path('sass/tree', File.dirname(__FILE__))
|
19
|
+
require File.expand_path('scss_lint/sass/script', File.dirname(__FILE__))
|
20
|
+
require File.expand_path('scss_lint/sass/tree', File.dirname(__FILE__))
|
19
21
|
|
20
22
|
# Load all linters
|
21
23
|
Dir[File.expand_path('scss_lint/linter/*.rb', File.dirname(__FILE__))].each do |file|
|
data/lib/scss_lint/cli.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
|
3
3
|
module SCSSLint
|
4
|
+
# Responsible for parsing command-line options and executing the appropriate
|
5
|
+
# application logic based on the options specified.
|
4
6
|
class CLI
|
5
7
|
attr_accessor :options
|
6
8
|
|
@@ -62,10 +64,15 @@ module SCSSLint
|
|
62
64
|
runner = Runner.new(options)
|
63
65
|
runner.run(find_files)
|
64
66
|
report_lints(runner.lints)
|
65
|
-
halt
|
67
|
+
halt(1) if runner.lints?
|
66
68
|
rescue NoFilesError, NoSuchLinter, Errno::ENOENT => ex
|
67
69
|
puts ex.message
|
68
|
-
halt
|
70
|
+
halt(-1)
|
71
|
+
rescue => ex
|
72
|
+
puts ex.message
|
73
|
+
puts ex.backtrace
|
74
|
+
puts 'Report this bug at '.yellow + BUG_REPORT_URL.cyan
|
75
|
+
halt(-1)
|
69
76
|
end
|
70
77
|
|
71
78
|
private
|
data/lib/scss_lint/linter.rb
CHANGED
@@ -28,6 +28,17 @@ module SCSSLint
|
|
28
28
|
message || description)
|
29
29
|
end
|
30
30
|
|
31
|
+
# Returns the character at the given [Sass::Source::Position]
|
32
|
+
def character_at(source_position, offset = 0)
|
33
|
+
actual_line = source_position.line - 1
|
34
|
+
actual_offset = source_position.offset + offset - 1
|
35
|
+
|
36
|
+
# Return a newline if offset points at the very end of the line
|
37
|
+
return "\n" if actual_offset == engine.lines[actual_line].length
|
38
|
+
|
39
|
+
engine.lines[actual_line][actual_offset]
|
40
|
+
end
|
41
|
+
|
31
42
|
# Monkey-patched implementation that adds support for traversing
|
32
43
|
# Sass::Script::Nodes (original implementation only supports
|
33
44
|
# Sass::Tree::Nodes).
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module SCSSLint
|
2
|
+
# Reports when `border: 0` can be used instead of `border: none`.
|
2
3
|
class Linter::BorderZero < Linter
|
3
4
|
include LinterRegistry
|
4
5
|
|
@@ -10,13 +11,13 @@ module SCSSLint
|
|
10
11
|
def description
|
11
12
|
'`border: 0;` is preferred over `border: none;`'
|
12
13
|
end
|
13
|
-
end
|
14
14
|
|
15
|
-
private
|
15
|
+
private
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
BORDER_PROPERTIES = %w[border
|
18
|
+
border-top
|
19
|
+
border-right
|
20
|
+
border-bottom
|
21
|
+
border-left]
|
22
|
+
end
|
22
23
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks for spaces after commas in argument lists.
|
3
|
+
class Linter::SpaceAfterComma < Linter
|
4
|
+
include LinterRegistry
|
5
|
+
|
6
|
+
def visit_mixindef(node)
|
7
|
+
check_definition(node, :mixin)
|
8
|
+
yield
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit_mixin(node)
|
12
|
+
check_invocation(node, :mixin)
|
13
|
+
yield
|
14
|
+
end
|
15
|
+
|
16
|
+
def visit_function(node)
|
17
|
+
check_definition(node, :function)
|
18
|
+
yield
|
19
|
+
end
|
20
|
+
|
21
|
+
def visit_script_funcall(node)
|
22
|
+
check_invocation(node, :function)
|
23
|
+
yield
|
24
|
+
end
|
25
|
+
|
26
|
+
def visit_script_listliteral(node)
|
27
|
+
check_commas_after_args(node.elements, 'lists') if node.separator == :comma
|
28
|
+
yield
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Check parameters of a function/mixin definition
|
34
|
+
def check_definition(node, type)
|
35
|
+
# Use the default value's source range if one is defined, since that will
|
36
|
+
# be the item with the comma after it
|
37
|
+
args = node.args.map { |name, default_value| default_value || name }
|
38
|
+
args << node.splat if node.splat
|
39
|
+
|
40
|
+
check_commas_after_args(args, "#{type} parameters")
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check arguments passed to a function/mixin invocation
|
44
|
+
def check_invocation(node, type)
|
45
|
+
args = sort_args_by_position(node.args,
|
46
|
+
node.splat,
|
47
|
+
node.keywords.values,
|
48
|
+
node.kwarg_splat)
|
49
|
+
|
50
|
+
check_commas_after_args(args, "#{type} arguments")
|
51
|
+
end
|
52
|
+
|
53
|
+
# Since keyword arguments are not guaranteed to be in order, use the source
|
54
|
+
# range to order arguments so we check them in the order they were declared.
|
55
|
+
def sort_args_by_position(*args)
|
56
|
+
args.flatten.compact.sort_by do |arg|
|
57
|
+
pos = arg.source_range.end_pos
|
58
|
+
[pos.line, pos.offset]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
EXPECTED_SPACES_AFTER_COMMA = 1
|
63
|
+
|
64
|
+
# Check the comma after each argument in a list for a space following it,
|
65
|
+
# reporting a lint using the given [arg_type].
|
66
|
+
def check_commas_after_args(args, arg_type)
|
67
|
+
# For each arg except the last, check the character following the comma
|
68
|
+
args[0..-2].each do |arg|
|
69
|
+
# Find the first comma following the arg
|
70
|
+
offset = 1
|
71
|
+
offset += 1 while character_at(arg.source_range.end_pos, offset - 1) != ','
|
72
|
+
|
73
|
+
# Check for space or newline after arg (we allow arguments to be split
|
74
|
+
# up over multiple lines).
|
75
|
+
spaces = 0
|
76
|
+
while character_at(arg.source_range.end_pos, offset) =~ / |\n/
|
77
|
+
spaces += 1
|
78
|
+
offset += 1
|
79
|
+
end
|
80
|
+
|
81
|
+
if spaces != EXPECTED_SPACES_AFTER_COMMA
|
82
|
+
add_lint arg, 'Commas in %s should be followed by a single space' % arg_type
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks for spaces following the colon that separates a property's name from
|
3
|
+
# its value.
|
4
|
+
class Linter::SpaceAfterPropertyColon < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
EXPECTED_SPACES_AFTER_COLON = 1
|
8
|
+
|
9
|
+
def visit_prop(node)
|
10
|
+
spaces = spaces_after_colon(node)
|
11
|
+
|
12
|
+
if spaces != EXPECTED_SPACES_AFTER_COLON
|
13
|
+
add_lint node, 'Colon after property should be followed by ' <<
|
14
|
+
"#{pluralize(EXPECTED_SPACES_AFTER_COLON, 'space')} instead of " <<
|
15
|
+
pluralize(spaces, 'space')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def spaces_after_colon(node)
|
22
|
+
spaces = 0
|
23
|
+
offset = 1
|
24
|
+
|
25
|
+
# Handle quirk where Sass parser doesn't include colon in source range
|
26
|
+
# when property name is followed by spaces
|
27
|
+
if character_at(node.name_source_range.end_pos, offset) == ':'
|
28
|
+
offset += 1
|
29
|
+
end
|
30
|
+
|
31
|
+
while character_at(node.name_source_range.end_pos, offset) == ' '
|
32
|
+
spaces += 1
|
33
|
+
offset += 1
|
34
|
+
end
|
35
|
+
|
36
|
+
spaces
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks for spaces following the name of a property and before the colon
|
3
|
+
# separating the property's name from its value.
|
4
|
+
class Linter::SpaceAfterPropertyName < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
def visit_prop(node)
|
8
|
+
if character_at(node.name_source_range.end_pos) != ':'
|
9
|
+
add_lint node, 'Property name should be immediately followed by a colon'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks properties for a trailing semicolon (unless that property is a
|
3
|
+
# namespace which has nested properties).
|
4
|
+
class Linter::TrailingSemicolonAfterPropertyValue < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
def visit_prop(node)
|
8
|
+
has_nested_props = has_nested_properties?(node)
|
9
|
+
|
10
|
+
if !has_nested_props && !ends_with_semicolon?(node)
|
11
|
+
add_lint node.line, 'Property declaration should end with a semicolon'
|
12
|
+
end
|
13
|
+
|
14
|
+
yield if has_nested_props
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def has_nested_properties?(node)
|
20
|
+
node.children.any? { |n| n.is_a?(Sass::Tree::PropNode) }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Checks that the property is ended by a semicolon (with no whitespace)
|
24
|
+
def ends_with_semicolon?(node)
|
25
|
+
character_at(node.source_range.end_pos) == ';' &&
|
26
|
+
character_at(node.source_range.end_pos, -1) !~ /\s/
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/scss_lint/runner.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
module SCSSLint
|
2
|
+
class LinterError < StandardError; end
|
2
3
|
class NoFilesError < StandardError; end
|
3
4
|
class NoLintersError < StandardError; end
|
4
5
|
|
6
|
+
# Finds and aggregates all lints found by running the registered linters
|
7
|
+
# against a set of SCSS files.
|
5
8
|
class Runner
|
6
9
|
attr_reader :linters, :lints
|
7
10
|
|
@@ -20,8 +23,8 @@ module SCSSLint
|
|
20
23
|
end
|
21
24
|
|
22
25
|
def run(files = [])
|
23
|
-
raise NoFilesError
|
24
|
-
raise NoLintersError
|
26
|
+
raise NoFilesError, 'No SCSS files specified' if files.empty?
|
27
|
+
raise NoLintersError, 'No linters specified' if linters.empty?
|
25
28
|
|
26
29
|
files.each do |file|
|
27
30
|
find_lints(file)
|
@@ -32,18 +35,26 @@ module SCSSLint
|
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
38
|
+
def lints?
|
39
|
+
lints.any?
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
35
44
|
def find_lints(file)
|
36
45
|
engine = Engine.new(file)
|
37
46
|
|
38
47
|
linters.each do |linter|
|
39
|
-
|
48
|
+
begin
|
49
|
+
linter.run(engine)
|
50
|
+
rescue => error
|
51
|
+
raise LinterError,
|
52
|
+
"#{linter.class} raised unexpected error: '#{error.message}'",
|
53
|
+
error.backtrace
|
54
|
+
end
|
40
55
|
end
|
41
56
|
rescue Sass::SyntaxError => ex
|
42
57
|
@lints << Lint.new(ex.sass_filename, ex.sass_line, ex.to_s, :error)
|
43
58
|
end
|
44
|
-
|
45
|
-
def lints?
|
46
|
-
lints.any?
|
47
|
-
end
|
48
59
|
end
|
49
60
|
end
|
File without changes
|
File without changes
|
data/lib/scss_lint/utils.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
module SCSSLint
|
2
|
+
# Collection of helpers used across a variety of linters.
|
2
3
|
module Utils
|
3
4
|
# Given a selector array which is a list of strings with Sass::Script::Nodes
|
4
5
|
# interspersed within them, return an array of strings representing those
|
@@ -55,6 +56,10 @@ module SCSSLint
|
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
59
|
+
def pluralize(value, word)
|
60
|
+
value == 1 ? "#{value} #{word}" : "#{value} #{word}s"
|
61
|
+
end
|
62
|
+
|
58
63
|
private
|
59
64
|
|
60
65
|
INVALID_NAME_CHARS = '[_A-Z]'
|
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.12.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: 2013-
|
12
|
+
date: 2013-11-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: colorize
|
@@ -77,6 +77,7 @@ extensions: []
|
|
77
77
|
extra_rdoc_files: []
|
78
78
|
files:
|
79
79
|
- lib/scss_lint/version.rb
|
80
|
+
- lib/scss_lint/constants.rb
|
80
81
|
- lib/scss_lint/utils.rb
|
81
82
|
- lib/scss_lint/reporter/xml_reporter.rb
|
82
83
|
- lib/scss_lint/reporter/default_reporter.rb
|
@@ -85,19 +86,24 @@ files:
|
|
85
86
|
- lib/scss_lint/lint.rb
|
86
87
|
- lib/scss_lint/linter.rb
|
87
88
|
- lib/scss_lint/selector_visitor.rb
|
88
|
-
- lib/scss_lint/
|
89
|
+
- lib/scss_lint/sass/tree.rb
|
90
|
+
- lib/scss_lint/sass/script.rb
|
91
|
+
- lib/scss_lint/linter/space_after_comma.rb
|
89
92
|
- lib/scss_lint/linter/space_before_brace.rb
|
90
93
|
- lib/scss_lint/linter/capitalization_in_selector.rb
|
91
94
|
- lib/scss_lint/linter/indentation.rb
|
92
95
|
- lib/scss_lint/linter/declared_name.rb
|
93
96
|
- lib/scss_lint/linter/sorted_properties.rb
|
97
|
+
- lib/scss_lint/linter/trailing_semicolon_after_property_value.rb
|
94
98
|
- lib/scss_lint/linter/shorthand.rb
|
95
99
|
- lib/scss_lint/linter/id_with_extraneous_selector.rb
|
96
100
|
- lib/scss_lint/linter/declaration_order.rb
|
97
101
|
- lib/scss_lint/linter/zero_unit.rb
|
98
102
|
- lib/scss_lint/linter/placeholder_in_extend.rb
|
103
|
+
- lib/scss_lint/linter/space_after_property_name.rb
|
99
104
|
- lib/scss_lint/linter/usage_name.rb
|
100
105
|
- lib/scss_lint/linter/border_zero.rb
|
106
|
+
- lib/scss_lint/linter/space_after_property_colon.rb
|
101
107
|
- lib/scss_lint/linter/debug_statement.rb
|
102
108
|
- lib/scss_lint/linter/duplicate_property.rb
|
103
109
|
- lib/scss_lint/linter/hex_format.rb
|
@@ -109,11 +115,9 @@ files:
|
|
109
115
|
- lib/scss_lint/cli.rb
|
110
116
|
- lib/scss_lint/engine.rb
|
111
117
|
- lib/scss_lint/linter_registry.rb
|
112
|
-
- lib/sass/tree.rb
|
113
|
-
- lib/sass/script.rb
|
114
118
|
- lib/scss_lint.rb
|
115
119
|
- bin/scss-lint
|
116
|
-
homepage:
|
120
|
+
homepage: https://github.com/causes/scss-lint
|
117
121
|
licenses:
|
118
122
|
- MIT
|
119
123
|
metadata: {}
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module SCSSLint
|
2
|
-
class Linter::PropertyFormat < Linter
|
3
|
-
include LinterRegistry
|
4
|
-
|
5
|
-
def visit_prop(node)
|
6
|
-
line = engine.lines[node.line - 1] if node.line
|
7
|
-
|
8
|
-
add_lint(node) unless line =~ PROPERTY_RE
|
9
|
-
end
|
10
|
-
|
11
|
-
def description
|
12
|
-
'Property declarations should always be on one line of the form ' <<
|
13
|
-
'`name: value;` or `name: [value] {` ' <<
|
14
|
-
'(are you missing a trailing semi-colon?)'
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
VALUE_RE = %r{(\S+\s)*\S+} # eg. "10px", "10px normal"
|
20
|
-
PROPERTY_RE = %r{
|
21
|
-
^\s*[^:]+(?<!\s):\s # property name, colon not preceded by a space, one space
|
22
|
-
( # followed by
|
23
|
-
#{VALUE_RE}; # property and terminating semi-colon eg. a b c;
|
24
|
-
| # or
|
25
|
-
((#{VALUE_RE}\s)?\{) # nested property, optional value, trailing curly
|
26
|
-
)
|
27
|
-
}x
|
28
|
-
end
|
29
|
-
end
|