scss-lint 0.6.7 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sass/tree.rb ADDED
@@ -0,0 +1,148 @@
1
+ # Contains extensions of Sass::Tree::Nodes to add support for traversing the
2
+ # Sass::Script::Node parse trees contained within the nodes. This probably
3
+ # breaks the Sass compiler, but since we're only doing lints this is fine for
4
+ # now.
5
+ module Sass::Tree
6
+ # Define some common helper code for use in the various monkey patchings.
7
+ class Node
8
+ # The `args` field of some Sass::Tree::Node classes returns
9
+ # Sass::Script::Variable nodes with no line numbers. This adds the line
10
+ # numbers back in so lint reporting works for those nodes.
11
+ def add_line_numbers_to_args(arg_list)
12
+ arg_list.each do |variable, default_expr|
13
+ variable.line = line # Use line number of containing parse tree node
14
+ end
15
+ end
16
+
17
+ # Sometimes the parse tree doesn't return a Sass::Script::Variable, but just
18
+ # the name of the variable. This helper takes that name and turns it back
19
+ # into a Sass::Script::Variable that supports lint reporting.
20
+ def create_variable(var_name)
21
+ Sass::Script::Variable.new(var_name).tap do |v|
22
+ v.line = line # Use line number of the containing parse tree node
23
+ end
24
+ end
25
+
26
+ # A number of tree nodes return lists that have strings and
27
+ # Sass::Script::Nodes interspersed within them. This returns a filtered list
28
+ # of just those nodes.
29
+ def extract_script_nodes(list)
30
+ list.select { |item| item.is_a? Sass::Script::Node }
31
+ end
32
+
33
+ # Takes a list of arguments, be they arrays or individual objects, and
34
+ # returns a single flat list that can be passed to
35
+ # Sass::Tree::Visitors::Base#visit_children.
36
+ def concat_expr_lists(*expr_lists)
37
+ expr_lists.flatten.compact
38
+ end
39
+ end
40
+
41
+ class CommentNode
42
+ def children
43
+ concat_expr_lists super, extract_script_nodes(value)
44
+ end
45
+ end
46
+
47
+ class DebugNode
48
+ def children
49
+ concat_expr_lists super, expr
50
+ end
51
+ end
52
+
53
+ class DirectiveNode
54
+ def children
55
+ concat_expr_lists super, extract_script_nodes(value)
56
+ end
57
+ end
58
+
59
+ class EachNode
60
+ def children
61
+ concat_expr_lists super, create_variable(var), list
62
+ end
63
+ end
64
+
65
+ class ExtendNode
66
+ def children
67
+ concat_expr_lists super, extract_script_nodes(selector)
68
+ end
69
+ end
70
+
71
+ class ForNode
72
+ def children
73
+ concat_expr_lists super, create_variable(var), from, to
74
+ end
75
+ end
76
+
77
+ class FunctionNode
78
+ def children
79
+ add_line_numbers_to_args(args)
80
+
81
+ concat_expr_lists super, args, splat
82
+ end
83
+ end
84
+
85
+ class IfNode
86
+ def children
87
+ concat_expr_lists super, expr
88
+ end
89
+ end
90
+
91
+ class MixinDefNode
92
+ def children
93
+ add_line_numbers_to_args(args)
94
+
95
+ concat_expr_lists super, args, splat
96
+ end
97
+ end
98
+
99
+ class MixinNode
100
+ def children
101
+ add_line_numbers_to_args(args)
102
+
103
+ # Keyword mapping is String -> Expr, so convert the string to a variable
104
+ # node that supports lint reporting
105
+ keyword_exprs = keywords.map do |var_name, var_expr|
106
+ [create_variable(var_name), var_expr]
107
+ end
108
+
109
+ concat_expr_lists super, args, keyword_exprs, splat
110
+ end
111
+ end
112
+
113
+ class PropNode
114
+ def children
115
+ concat_expr_lists super, extract_script_nodes(name), value
116
+ end
117
+ end
118
+
119
+ class ReturnNode
120
+ def children
121
+ concat_expr_lists super, expr
122
+ end
123
+ end
124
+
125
+ class RuleNode
126
+ def children
127
+ concat_expr_lists super, extract_script_nodes(rule)
128
+ end
129
+ end
130
+
131
+ class VariableNode
132
+ def children
133
+ concat_expr_lists super, expr
134
+ end
135
+ end
136
+
137
+ class WarnNode
138
+ def children
139
+ concat_expr_lists super, expr
140
+ end
141
+ end
142
+
143
+ class WhileNode
144
+ def children
145
+ concat_expr_lists super, expr
146
+ end
147
+ end
148
+ end
@@ -4,10 +4,11 @@ module SCSSLint
4
4
  class Engine
5
5
  ENGINE_OPTIONS = { cache: false, syntax: :scss }
6
6
 
7
- attr_reader :contents, :lines, :tree
7
+ attr_reader :contents, :filename, :lines, :tree
8
8
 
9
9
  def initialize(scss_or_filename)
10
10
  if File.exists?(scss_or_filename)
11
+ @filename = scss_or_filename
11
12
  @engine = Sass::Engine.for_file(scss_or_filename, ENGINE_OPTIONS)
12
13
  @contents = File.open(scss_or_filename, 'r').read
13
14
  else
@@ -0,0 +1,24 @@
1
+ require 'sass'
2
+
3
+ module SCSSLint
4
+ class Linter::BorderZeroLinter < Linter
5
+ include LinterRegistry
6
+
7
+ def visit_prop(node)
8
+ return unless BORDER_PROPERTIES.include? node.name.first.to_s
9
+ add_lint(node) if node.value.to_sass.strip == 'none'
10
+ end
11
+
12
+ def description
13
+ '`border: 0;` is preferred over `border: none;`'
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ BORDER_PROPERTIES = %w[border
20
+ border-top
21
+ border-right
22
+ border-bottom
23
+ border-left]
24
+ end
@@ -0,0 +1,15 @@
1
+ require 'sass'
2
+
3
+ module SCSSLint
4
+ class Linter::CommentLinter < Linter
5
+ include LinterRegistry
6
+
7
+ def visit_comment(node)
8
+ add_lint(node) unless node.invisible?
9
+ end
10
+
11
+ def description
12
+ 'Use // comments everywhere'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,45 @@
1
+ require 'sass'
2
+
3
+ module SCSSLint
4
+ class Linter::DeclaredNameLinter < Linter
5
+ include LinterRegistry
6
+
7
+ def visit_function(node)
8
+ check(node)
9
+ yield # Continue into content block of this function definition
10
+ end
11
+
12
+ def visit_mixin(node)
13
+ check(node)
14
+ yield # Continue into content block of this mixin's block
15
+ end
16
+
17
+ def visit_mixindef(node)
18
+ check(node)
19
+ yield # Continue into content block of this mixin definition
20
+ end
21
+
22
+ def visit_variable(node)
23
+ check(node)
24
+ yield # Continue into expression tree for this variable definition
25
+ end
26
+
27
+ def visit_script_funcall(node)
28
+ check(node)
29
+ end
30
+
31
+ def visit_script_variable(node)
32
+ check(node)
33
+ end
34
+
35
+ def description
36
+ 'Names of variables, functions, and mixins should be lowercase and not contain underscores. Use hyphens instead.'
37
+ end
38
+
39
+ private
40
+
41
+ def check(node)
42
+ add_lint(node) if node.name =~ /[_A-Z]/
43
+ end
44
+ end
45
+ end
@@ -4,10 +4,17 @@ module SCSSLint
4
4
  class Linter::HexLinter < Linter
5
5
  include LinterRegistry
6
6
 
7
- def visit_prop(node)
8
- if node.value.is_a?(Sass::Script::String) &&
9
- node.value.to_s =~ /#(\h{3,6})/
10
- add_lint(node) unless valid_hex?($1)
7
+ def visit_root(node)
8
+ # We can't do this via the parse tree because the parser automatically
9
+ # normalizes all colors encountered in Sass script, so we lose the ability
10
+ # to check the format of the color. Thus we resort to line-by-line regex
11
+ # matching.
12
+ engine.lines.each_with_index do |line, index|
13
+ line.scan /#(\h{3,6})/ do |match|
14
+ unless valid_hex?(match.first)
15
+ @lints << Lint.new(engine.filename, index + 1, description)
16
+ end
17
+ end
11
18
  end
12
19
  end
13
20
 
@@ -6,6 +6,7 @@ module SCSSLint
6
6
 
7
7
  def visit_prop(node)
8
8
  line = engine.lines[node.line - 1] if node.line
9
+
9
10
  add_lint(node) unless line =~ PROPERTY_RE
10
11
  end
11
12
 
@@ -19,11 +20,11 @@ module SCSSLint
19
20
 
20
21
  VALUE_RE = %r{(\S+\s)*\S+} # eg. "10px", "10px normal"
21
22
  PROPERTY_RE = %r{
22
- ^\s*[\w-]+:\s # property name, colon, one space
23
- ( # followed by
24
- #{VALUE_RE}; # property and terminating semi-colon eg. a b c;
25
- | # or
26
- ((#{VALUE_RE}\s)?\{) # nested property, optional value, trailing curly
23
+ ^\s*[^:]+(?<!\s):\s # property name, colon not preceded by a space, one space
24
+ ( # followed by
25
+ #{VALUE_RE}; # property and terminating semi-colon eg. a b c;
26
+ | # or
27
+ ((#{VALUE_RE}\s)?\{) # nested property, optional value, trailing curly
27
28
  )
28
29
  }x
29
30
  end
@@ -5,12 +5,18 @@ module SCSSLint
5
5
  include LinterRegistry
6
6
 
7
7
  def visit_prop(node)
8
- unless SHORTHANDABLE_PROPERTIES.include? node.name.first.to_s
9
- return
10
- end
11
-
12
- if node.value.to_sass.strip =~ /\A(\S+\s+\S+(\s+\S+){0,2})\Z/
13
- add_lint(node) unless valid_shorthand?($1)
8
+ return unless SHORTHANDABLE_PROPERTIES.include? node.name.first.to_s
9
+
10
+ case node.value
11
+ when Sass::Script::List
12
+ items = node.value.children
13
+ if (2..4).member?(items.count)
14
+ add_lint(node) unless valid_shorthand?(*items.map(&:to_sass))
15
+ end
16
+ when Sass::Script::String
17
+ if node.value.to_sass.strip =~ /\A(\S+\s+\S+(\s+\S+){0,2})\Z/
18
+ add_lint(node) unless valid_shorthand?(*$1.split(/\s+/))
19
+ end
14
20
  end
15
21
  end
16
22
 
@@ -27,10 +33,7 @@ module SCSSLint
27
33
  margin
28
34
  padding]
29
35
 
30
- def valid_shorthand?(shorthand)
31
- values = shorthand.split(/\s+/)
32
- top, right, bottom, left = values
33
-
36
+ def valid_shorthand?(top, right, bottom = nil, left = nil)
34
37
  if top == right && right == bottom && bottom == left
35
38
  false
36
39
  elsif top == right && bottom.nil? && left.nil?
@@ -5,12 +5,36 @@ module SCSSLint
5
5
  include LinterRegistry
6
6
 
7
7
  def visit_rule(node)
8
- add_lint(node) unless node.rule.grep(/,[^\n]/).empty?
8
+ add_lint(node) if invalid_comma_placement? node
9
9
  yield # Continue linting children
10
10
  end
11
11
 
12
12
  def description
13
13
  'Each selector should be on its own line'
14
14
  end
15
+
16
+ private
17
+
18
+ # A comma is invalid if it starts the line or is not the end of the line
19
+ def invalid_comma_placement?(node)
20
+ normalize_spacing(condense_to_string(node.rule)) =~ /\n,|,[^\n]/
21
+ end
22
+
23
+ # Since RuleNode.rule returns an array containing both String and
24
+ # Sass::Script::Nodes, we need to condense it into a single string that we
25
+ # can run a regex against.
26
+ def condense_to_string(sequence_list)
27
+ sequence_list.inject('') do |combined, string_or_script|
28
+ combined + (string_or_script.is_a?(String) ? string_or_script
29
+ : string_or_script.to_sass)
30
+ end
31
+ end
32
+
33
+ # Removes extra spacing between lines in a comma-separated sequence due to
34
+ # comments being removed in the parse phase. This makes it easy to check if
35
+ # a comma is where belongs.
36
+ def normalize_spacing(string_sequence)
37
+ string_sequence.gsub(/,[^\S\n]*\n\s*/, ",\n")
38
+ end
15
39
  end
16
40
  end
@@ -21,7 +21,18 @@ module SCSSLint
21
21
 
22
22
  # Helper for creating lint from a parse tree node
23
23
  def add_lint(node)
24
- @lints << Lint.new(node.filename, node.line, description)
24
+ @lints << Lint.new(engine.filename, node.line, description)
25
+ end
26
+
27
+ # Monkey-patched implementation that adds support for traversing
28
+ # Sass::Script::Nodes (original implementation only supports
29
+ # Sass::Tree::Nodes).
30
+ def node_name(node)
31
+ if node.is_a?(Sass::Script::Node)
32
+ "script_#{node.class.name.gsub(/.*::(.*?)$/, '\\1').downcase}"
33
+ else
34
+ super
35
+ end
25
36
  end
26
37
  end
27
38
  end
@@ -1,3 +1,3 @@
1
1
  module SCSSLint
2
- VERSION = '0.6.7'
2
+ VERSION = '0.7.0'
3
3
  end
data/lib/scss_lint.rb CHANGED
@@ -10,6 +10,10 @@ module SCSSLint
10
10
  autoload :Runner, 'scss_lint/runner'
11
11
  autoload :VERSION, 'scss_lint/version'
12
12
 
13
+ # Preload Sass so we can monkey patch it
14
+ require 'sass'
15
+ require 'sass/tree'
16
+
13
17
  # Load all linters
14
18
  Dir[File.expand_path('scss_lint/linter/*.rb', File.dirname(__FILE__))].each do |file|
15
19
  require file
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.6.7
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-10 00:00:00.000000000 Z
12
+ date: 2013-06-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: colorize
@@ -84,13 +84,17 @@ executables:
84
84
  extensions: []
85
85
  extra_rdoc_files: []
86
86
  files:
87
+ - lib/sass/tree.rb
87
88
  - lib/scss_lint.rb
88
89
  - lib/scss_lint/cli.rb
89
90
  - lib/scss_lint/engine.rb
90
91
  - lib/scss_lint/lint.rb
91
92
  - lib/scss_lint/linter.rb
93
+ - lib/scss_lint/linter/border_zero_linter.rb
94
+ - lib/scss_lint/linter/comment_linter.rb
92
95
  - lib/scss_lint/linter/debug_linter.rb
93
96
  - lib/scss_lint/linter/declaration_order_linter.rb
97
+ - lib/scss_lint/linter/declared_name_linter.rb
94
98
  - lib/scss_lint/linter/empty_rule_linter.rb
95
99
  - lib/scss_lint/linter/hex_linter.rb
96
100
  - lib/scss_lint/linter/property_format_linter.rb
@@ -107,7 +111,8 @@ files:
107
111
  - lib/scss_lint/version.rb
108
112
  - bin/scss-lint
109
113
  homepage: http://github.com/causes/scss-lint
110
- licenses: []
114
+ licenses:
115
+ - MIT
111
116
  post_install_message:
112
117
  rdoc_options: []
113
118
  require_paths: