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 ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'scss_lint/cli'
4
+
5
+ SCSSLint::CLI.new ARGV
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,11 @@
1
+ module SCSSLint
2
+ class Lint
3
+ attr_reader :filename, :line, :description
4
+
5
+ def initialize(filename, line, description)
6
+ @filename = filename
7
+ @line = line
8
+ @description = description
9
+ end
10
+ end
11
+ 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,13 @@
1
+ module SCSSLint
2
+ module LinterRegistry
3
+ @linters = []
4
+
5
+ class << self
6
+ attr_reader :linters
7
+
8
+ def included(base)
9
+ @linters << base
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,3 @@
1
+ module SCSSLint
2
+ VERSION = '0.1'
3
+ 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: []