scss-lint 0.1
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.
- data/bin/scss-lint +5 -0
- data/lib/scss_lint.rb +26 -0
- data/lib/scss_lint/cli.rb +37 -0
- data/lib/scss_lint/engine.rb +22 -0
- data/lib/scss_lint/lint.rb +11 -0
- data/lib/scss_lint/linter.rb +25 -0
- data/lib/scss_lint/linter/debug_linter.rb +21 -0
- data/lib/scss_lint/linter/declaration_order_linter.rb +55 -0
- data/lib/scss_lint/linter/empty_rule_linter.rb +29 -0
- data/lib/scss_lint/linter/hex_linter.rb +45 -0
- data/lib/scss_lint/linter/property_format_linter.rb +30 -0
- data/lib/scss_lint/linter/sorted_properties_linter.rb +39 -0
- data/lib/scss_lint/linter/zero_unit_linter.rb +29 -0
- data/lib/scss_lint/linter_registry.rb +13 -0
- data/lib/scss_lint/runner.rb +36 -0
- data/lib/scss_lint/version.rb +3 -0
- metadata +111 -0
data/bin/scss-lint
ADDED
data/lib/scss_lint.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'find'
|
2
|
+
|
3
|
+
module SCSSLint
|
4
|
+
autoload :Engine, 'scss_lint/engine'
|
5
|
+
autoload :Lint, 'scss_lint/lint'
|
6
|
+
autoload :LinterRegistry, 'scss_lint/linter_registry'
|
7
|
+
autoload :Linter, 'scss_lint/linter'
|
8
|
+
autoload :Runner, 'scss_lint/runner'
|
9
|
+
|
10
|
+
# Load all linters
|
11
|
+
Dir[File.expand_path('scss_lint/linter/*.rb', File.dirname(__FILE__))].each do |file|
|
12
|
+
require file
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def extract_files_from(list)
|
17
|
+
files = []
|
18
|
+
list.each do |file|
|
19
|
+
Find.find(file) do |f|
|
20
|
+
files << f
|
21
|
+
end
|
22
|
+
end
|
23
|
+
files.uniq
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'scss_lint'
|
2
|
+
require 'colorize'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module SCSSLint
|
6
|
+
class CLI
|
7
|
+
def initialize(args)
|
8
|
+
opts = OptionParser.new do |opts|
|
9
|
+
opts.banner = 'Usage: scss-lint [scss-files]'
|
10
|
+
end.parse!(args)
|
11
|
+
|
12
|
+
files = SCSSLint.extract_files_from(opts)
|
13
|
+
|
14
|
+
runner = Runner.new
|
15
|
+
begin
|
16
|
+
runner.run files
|
17
|
+
report_lints(runner.lints)
|
18
|
+
exit 1 if runner.lints?
|
19
|
+
rescue NoFilesError => ex
|
20
|
+
puts ex.message
|
21
|
+
exit -1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def report_lints(lints)
|
26
|
+
lints.sort_by { |l| [l.filename, l.line] }.each do |lint|
|
27
|
+
if lint.filename
|
28
|
+
print "#{lint.filename}:".yellow
|
29
|
+
else
|
30
|
+
print 'line'.yellow
|
31
|
+
end
|
32
|
+
|
33
|
+
puts "#{lint.line} - #{lint.description}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'sass'
|
2
|
+
|
3
|
+
module SCSSLint
|
4
|
+
class Engine
|
5
|
+
ENGINE_OPTIONS = { cache: false, syntax: :scss }
|
6
|
+
|
7
|
+
attr_reader :contents, :lines, :tree
|
8
|
+
|
9
|
+
def initialize(scss_or_filename)
|
10
|
+
if File.exists?(scss_or_filename)
|
11
|
+
@engine = Sass::Engine.for_file(scss_or_filename, ENGINE_OPTIONS)
|
12
|
+
@contents = File.open(scss_or_filename, 'r').read
|
13
|
+
else
|
14
|
+
@engine = Sass::Engine.new(scss_or_filename, ENGINE_OPTIONS)
|
15
|
+
@contents = scss_or_filename
|
16
|
+
end
|
17
|
+
|
18
|
+
@lines = @contents.split("\n")
|
19
|
+
@tree = @engine.to_tree
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
class Linter
|
3
|
+
include LinterRegistry
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def run(engine)
|
7
|
+
[] # No lints
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_lint(node)
|
11
|
+
Lint.new(node.filename, node.line, description)
|
12
|
+
end
|
13
|
+
|
14
|
+
def description
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def name
|
21
|
+
self.class.name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'sass'
|
2
|
+
|
3
|
+
module SCSSLint
|
4
|
+
class Linter::DebugLinter < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def run(engine)
|
9
|
+
lints = []
|
10
|
+
engine.tree.each do |node|
|
11
|
+
lints << create_lint(node) if node.is_a?(Sass::Tree::DebugNode)
|
12
|
+
end
|
13
|
+
lints
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
'@debug line'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'sass'
|
2
|
+
|
3
|
+
module SCSSLint
|
4
|
+
class Linter::DeclarationOrderLinter < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
DECLARATION_ORDER = [
|
8
|
+
Sass::Tree::ExtendNode,
|
9
|
+
Sass::Tree::MixinNode,
|
10
|
+
Sass::Tree::PropNode,
|
11
|
+
Sass::Tree::RuleNode,
|
12
|
+
]
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def run(engine)
|
16
|
+
lints = []
|
17
|
+
#engine.tree.each do |node|
|
18
|
+
#if node.is_a?(Sass::Tree::RuleNode)
|
19
|
+
#lints << check_order_of_declarations(node)
|
20
|
+
#end
|
21
|
+
#end
|
22
|
+
#lints.compact
|
23
|
+
end
|
24
|
+
|
25
|
+
def description
|
26
|
+
'Rule sets should start with @extend declarations, followed by ' <<
|
27
|
+
'mixins, properties, and nested rule sets, in that order'
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def important_node?(node)
|
33
|
+
case node
|
34
|
+
when *DECLARATION_ORDER
|
35
|
+
true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def check_order_of_declarations(rule_node)
|
40
|
+
children = rule_node.children.select { |node| important_node?(node) }
|
41
|
+
|
42
|
+
# Horribly inefficient, but we're not sorting thousands of declarations
|
43
|
+
sorted_children = children.sort do |x,y|
|
44
|
+
DECLARATION_ORDER.index(x.class) <=> DECLARATION_ORDER.index(y.class)
|
45
|
+
end
|
46
|
+
|
47
|
+
if children != sorted_children
|
48
|
+
return create_lint(children.first)
|
49
|
+
end
|
50
|
+
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'sass'
|
2
|
+
|
3
|
+
module SCSSLint
|
4
|
+
class Linter::EmptyRuleLinter < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def run(engine)
|
9
|
+
lints = []
|
10
|
+
engine.tree.each do |node|
|
11
|
+
if node.is_a?(Sass::Tree::RuleNode)
|
12
|
+
lints << check_empty_rule(node)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
lints.compact
|
16
|
+
end
|
17
|
+
|
18
|
+
def description
|
19
|
+
'Empty rule'
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def check_empty_rule(rule_node)
|
25
|
+
create_lint(rule_node) if rule_node.children.empty?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'sass'
|
2
|
+
|
3
|
+
module SCSSLint
|
4
|
+
class Linter::HexLinter < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def run(engine)
|
9
|
+
lints = []
|
10
|
+
engine.tree.each do |node|
|
11
|
+
if node.is_a?(Sass::Tree::PropNode)
|
12
|
+
lints << check_valid_hex_value(node)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
lints.compact
|
16
|
+
end
|
17
|
+
|
18
|
+
def description
|
19
|
+
'Hexadecimal color codes should be lowercase and in 3-digit form where possible'
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def check_valid_hex_value(prop_node)
|
25
|
+
if prop_node.value.is_a?(Sass::Script::String) &&
|
26
|
+
prop_node.value.to_s =~ /#(\h{3,6})/
|
27
|
+
return create_lint(prop_node) unless valid_hex?($1)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid_hex?(hex)
|
32
|
+
[3,6].include?(hex.length) &&
|
33
|
+
hex.downcase == hex &&
|
34
|
+
!can_be_condensed(hex)
|
35
|
+
end
|
36
|
+
|
37
|
+
def can_be_condensed(hex)
|
38
|
+
hex.length == 6 &&
|
39
|
+
hex[0] == hex[1] &&
|
40
|
+
hex[2] == hex[3] &&
|
41
|
+
hex[4] == hex[5]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'sass'
|
2
|
+
|
3
|
+
module SCSSLint
|
4
|
+
class Linter::PropertyFormatLinter < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def run(engine)
|
9
|
+
lints = []
|
10
|
+
engine.tree.each do |node|
|
11
|
+
if node.is_a?(Sass::Tree::PropNode)
|
12
|
+
lints << check_property_format(node, engine.lines[node.line - 1]) if node.line
|
13
|
+
end
|
14
|
+
end
|
15
|
+
lints.compact
|
16
|
+
end
|
17
|
+
|
18
|
+
def description
|
19
|
+
'Property declarations should always be on one line of the form ' <<
|
20
|
+
'`property-name: value;`'
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def check_property_format(prop_node, line)
|
26
|
+
return create_lint(prop_node) unless line =~ /^\s*[\w-]+: [^\s][^;]*;/
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'sass'
|
2
|
+
|
3
|
+
module SCSSLint
|
4
|
+
class Linter::SortedPropertiesLinter < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def run(engine)
|
9
|
+
lints = []
|
10
|
+
engine.tree.each do |node|
|
11
|
+
if node.is_a?(Sass::Tree::RuleNode)
|
12
|
+
lints << check_properties_sorted(node)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
lints.compact
|
16
|
+
end
|
17
|
+
|
18
|
+
def description
|
19
|
+
'Properties should be sorted in alphabetical order'
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def check_properties_sorted(rule_node)
|
25
|
+
properties = rule_node.children.select do |node|
|
26
|
+
node.is_a?(Sass::Tree::PropNode)
|
27
|
+
end
|
28
|
+
|
29
|
+
prop_names = properties.map do |prop_node|
|
30
|
+
prop_node.name.first.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
if prop_names.sort != prop_names
|
34
|
+
create_lint(properties.first)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'sass'
|
2
|
+
|
3
|
+
module SCSSLint
|
4
|
+
class Linter::ZeroUnitLinter < Linter
|
5
|
+
include LinterRegistry
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def run(engine)
|
9
|
+
lints = []
|
10
|
+
engine.tree.each do |node|
|
11
|
+
if node.is_a?(Sass::Tree::PropNode)
|
12
|
+
lints << check_zero_unit(node, engine.lines[node.line - 1]) if node.line
|
13
|
+
end
|
14
|
+
end
|
15
|
+
lints.compact
|
16
|
+
end
|
17
|
+
|
18
|
+
def description
|
19
|
+
'Properties with a value of zero should be unit-less'
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def check_zero_unit(prop_node, line)
|
25
|
+
return create_lint(prop_node) if line =~ /^\s*[\w-]+:\s*0[a-z]+;$/i
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'scss_lint'
|
2
|
+
require 'sass'
|
3
|
+
|
4
|
+
module SCSSLint
|
5
|
+
class NoFilesError < StandardError; end
|
6
|
+
class NoLintersError < StandardError; end
|
7
|
+
|
8
|
+
class Runner
|
9
|
+
attr_reader :lints
|
10
|
+
|
11
|
+
def run(files = [])
|
12
|
+
@lints = []
|
13
|
+
|
14
|
+
raise NoFilesError.new('No SCSS files specified') if files.empty?
|
15
|
+
raise NoLintersError.new('No linters specified') if LinterRegistry.linters.empty?
|
16
|
+
|
17
|
+
files.each do |file|
|
18
|
+
find_lints(file)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_lints(file)
|
23
|
+
engine = Engine.new(file)
|
24
|
+
|
25
|
+
LinterRegistry.linters.each do |linter|
|
26
|
+
@lints += linter.run(engine)
|
27
|
+
end
|
28
|
+
rescue Sass::SyntaxError => ex
|
29
|
+
@lints << Lint.new(ex.sass_filename, ex.sass_line, ex.to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
def lints?
|
33
|
+
@lints.any?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scss-lint
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Causes Engineering
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: colorize
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: sass
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Opinionated tool that tells you whether or not your SCSS is "bad"
|
63
|
+
email:
|
64
|
+
- eng@causes.com
|
65
|
+
- shane@causes.com
|
66
|
+
executables:
|
67
|
+
- scss-lint
|
68
|
+
extensions: []
|
69
|
+
extra_rdoc_files: []
|
70
|
+
files:
|
71
|
+
- lib/scss_lint/version.rb
|
72
|
+
- lib/scss_lint/runner.rb
|
73
|
+
- lib/scss_lint/lint.rb
|
74
|
+
- lib/scss_lint/linter.rb
|
75
|
+
- lib/scss_lint/linter/declaration_order_linter.rb
|
76
|
+
- lib/scss_lint/linter/empty_rule_linter.rb
|
77
|
+
- lib/scss_lint/linter/debug_linter.rb
|
78
|
+
- lib/scss_lint/linter/sorted_properties_linter.rb
|
79
|
+
- lib/scss_lint/linter/property_format_linter.rb
|
80
|
+
- lib/scss_lint/linter/zero_unit_linter.rb
|
81
|
+
- lib/scss_lint/linter/hex_linter.rb
|
82
|
+
- lib/scss_lint/cli.rb
|
83
|
+
- lib/scss_lint/engine.rb
|
84
|
+
- lib/scss_lint/linter_registry.rb
|
85
|
+
- lib/scss_lint.rb
|
86
|
+
- bin/scss-lint
|
87
|
+
homepage: http://github.com/causes/scss-lint
|
88
|
+
licenses: []
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 1.8.23
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: SCSS lint tool
|
111
|
+
test_files: []
|