scss-lint 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dbee51206b106602ef0961c6c03bfb6974d4f2eb
4
- data.tar.gz: 2e87d30b614973420c6a8956ff4248325dbcc05d
3
+ metadata.gz: c4c60cb7b446fd21b69324b554345c0d2fbb63af
4
+ data.tar.gz: c12498e673473f6305b6216fa0151ef755121b5a
5
5
  SHA512:
6
- metadata.gz: 4c3cba3c53f301916ee8ebcdab2e6ee7167e3014c7dae55bcf0b48a490f5f091cbb9ef20335a4133ab20de8b1e8b031900f6e6bf46b293b3a95e92a997195083
7
- data.tar.gz: 10fc7fe143641a411666fe6ff26b434fe98d0fad0b58ac4e99113dd24e5fa09a0299a5ffdcded16bfd0eee8fff8728eeef4187e9a49aec8f74ba2f9adcc36302
6
+ metadata.gz: ba86ab4dc998719d5f11e26cb6d842c3bf9aec9535f3742a93fd8cbb975b4ff300f0aaefc8f127b99ab9ff642b5ee15a4aa0320c82b003805ba3edca5cb84408
7
+ data.tar.gz: b1e5c3b72ab4300347138727dc13963fefbde3be4c9adb88c9bc8c3791c43a695f070268b2ca20f4b9a921a662aec822df1983587bc50341e6c22eecba700bd7
data/lib/sass/script.rb CHANGED
@@ -9,4 +9,40 @@ module Sass::Script
9
9
  @name
10
10
  end
11
11
  end
12
+
13
+ # When linting colors, it's convenient to be able to inspect the original
14
+ # color string. This adds an attribute to the Color to keep track of the
15
+ # original string and provides a method which the modified lexer can use to
16
+ # set it.
17
+ class Color
18
+ attr_accessor :original
19
+
20
+ def self.from_string(string)
21
+ rgb = string.scan(/^#(..?)(..?)(..?)$/).
22
+ first.
23
+ map { |hex| hex.ljust(2, hex).to_i(16) }
24
+
25
+ color = Color.new(rgb, false)
26
+ color.original = string
27
+ color
28
+ end
29
+ end
30
+
31
+ class Lexer
32
+ # We redefine the color lexer to store the original string with the created
33
+ # `Color` object so that we can inspect the original string before it is
34
+ # normalized.
35
+ #
36
+ # This is an adapted version from the original Sass source code.
37
+ def color
38
+ return unless color_string = scan(REGULAR_EXPRESSIONS[:color])
39
+
40
+ unless [4, 7].include?(color_string.length)
41
+ raise ::Sass::SyntaxError,
42
+ "Colors must have either three or six digits: '#{color_string}'"
43
+ end
44
+
45
+ [:color, Color.from_string(color_string)]
46
+ end
47
+ end
12
48
  end
data/lib/sass/tree.rb CHANGED
@@ -10,10 +10,17 @@ module Sass::Tree
10
10
  # numbers back in so lint reporting works for those nodes.
11
11
  def add_line_numbers_to_args(arg_list)
12
12
  arg_list.each do |variable, default_expr|
13
- variable.line = line # Use line number of containing parse tree node
13
+ add_line_number(variable)
14
14
  end
15
15
  end
16
16
 
17
+ # The Sass parser sometimes doesn't assign line numbers in cases where it
18
+ # should. This is a helper to easily correct that.
19
+ def add_line_number(node)
20
+ node.line ||= line if node.is_a?(Sass::Script::Node)
21
+ node
22
+ end
23
+
17
24
  # Sometimes the parse tree doesn't return a Sass::Script::Variable, but just
18
25
  # the name of the variable. This helper takes that name and turns it back
19
26
  # into a Sass::Script::Variable that supports lint reporting.
@@ -117,7 +124,7 @@ module Sass::Tree
117
124
 
118
125
  class PropNode
119
126
  def children
120
- concat_expr_lists super, extract_script_nodes(name), value
127
+ concat_expr_lists super, extract_script_nodes(name), add_line_number(value)
121
128
  end
122
129
  end
123
130
 
@@ -2,14 +2,39 @@ module SCSSLint
2
2
  class Linter::CapitalizationInSelector < Linter
3
3
  include LinterRegistry
4
4
 
5
- def visit_rule(node)
6
- add_lint(node) if node.rule.first =~ /[A-Z]/
5
+ def visit_attribute(attribute)
6
+ check(attribute)
7
+ end
8
+
9
+ def visit_class(klass)
10
+ check(klass)
11
+ end
12
+
13
+ def visit_element(element)
14
+ check(element)
15
+ end
7
16
 
8
- yield # Continue linting children
17
+ def visit_id(id)
18
+ check(id)
9
19
  end
10
20
 
11
- def description
12
- 'Selectors should be lowercase'
21
+ def visit_placeholder(placeholder)
22
+ check(placeholder)
23
+ end
24
+
25
+ def visit_pseudo(pseudo)
26
+ check(pseudo, 'Pseudo-selector')
27
+ end
28
+
29
+ private
30
+
31
+ def check(node, selector_name = nil)
32
+ name = node.name.join
33
+ if name =~ /[A-Z]/
34
+ selector_name ||= node.class.name.split('::').last
35
+ add_lint(node, "#{selector_name} `#{name}` in selector should be " <<
36
+ "written in all lowercase as `#{name.downcase}`")
37
+ end
13
38
  end
14
39
  end
15
40
  end
@@ -0,0 +1,19 @@
1
+ module SCSSLint
2
+ class Linter::ColorKeyword < Linter
3
+ include LinterRegistry
4
+
5
+ def visit_script_string(node)
6
+ add_lint(node) if color_keyword?(node.value)
7
+ end
8
+
9
+ def description
10
+ 'Colors should be specified as hexadecimal values, not names'
11
+ end
12
+
13
+ private
14
+
15
+ def color_keyword?(string)
16
+ !!Sass::Script::Color::COLOR_NAMES[string]
17
+ end
18
+ end
19
+ end
@@ -3,34 +3,29 @@ module SCSSLint
3
3
  include LinterRegistry
4
4
 
5
5
  def visit_function(node)
6
- check(node)
6
+ check(node, 'function')
7
7
  yield # Continue into content block of this function definition
8
8
  end
9
9
 
10
10
  def visit_mixindef(node)
11
- check(node)
11
+ check(node, 'mixin')
12
12
  yield # Continue into content block of this mixin definition
13
13
  end
14
14
 
15
- def visit_rule(node)
16
- add_lint(node) if selector_has_bad_placeholder?(node.rule)
17
- yield # Continue linting into content block of this rule definition
18
- end
19
-
20
15
  def visit_variable(node)
21
- check(node)
16
+ check(node, 'variable')
22
17
  yield # Continue into expression tree for this variable definition
23
18
  end
24
19
 
25
- def description
26
- 'Names of variables, functions, mixins, and placeholders should be ' <<
27
- 'lowercase and use hyphens instead of underscores.'
28
- end
29
-
30
20
  private
31
21
 
32
- def check(node)
33
- add_lint(node) if node_has_bad_name?(node)
22
+ def check(node, node_type)
23
+ if node_has_bad_name?(node)
24
+ fixed_name = node.name.downcase.gsub(/_/, '-')
25
+
26
+ add_lint(node, "Name of #{node_type} `#{node.name}` should " <<
27
+ "be written in lowercase as `#{fixed_name}`")
28
+ end
34
29
  end
35
30
  end
36
31
  end
@@ -2,37 +2,45 @@ module SCSSLint
2
2
  class Linter::HexFormat < Linter
3
3
  include LinterRegistry
4
4
 
5
- def visit_root(node)
6
- # We can't do this via the parse tree because the parser automatically
7
- # normalizes all colors encountered in Sass script, so we lose the ability
8
- # to check the format of the color. Thus we resort to line-by-line regex
9
- # matching.
10
- engine.lines.each_with_index do |line, index|
11
- line.scan /#(\h{3,6})/ do |match|
12
- unless valid_hex?(match.first)
13
- @lints << Lint.new(engine.filename, index + 1, description)
14
- end
5
+ def visit_prop(node)
6
+ if node.value.is_a?(Sass::Script::String) &&
7
+ node.value.type == :identifier
8
+
9
+ node.value.value.scan(HEX_REGEX) do |match|
10
+ add_hex_lint(node, match.first) unless valid_hex_format?(match.first)
15
11
  end
16
12
  end
13
+
14
+ yield # Continue visiting children
17
15
  end
18
16
 
19
- def description
20
- 'Hexadecimal color codes should be lowercase and in 3-digit form where possible'
17
+ def visit_script_color(node)
18
+ unless valid_hex_format?(node.original[HEX_REGEX, 1])
19
+ add_hex_lint(node, node.original)
20
+ end
21
21
  end
22
22
 
23
23
  private
24
24
 
25
- def valid_hex?(hex)
26
- [3,6].include?(hex.length) &&
27
- hex.downcase == hex &&
28
- !can_be_condensed(hex)
25
+ HEX_REGEX = /(#\h{3,6})/
26
+
27
+ def add_hex_lint(node, hex)
28
+ add_lint(node, "Color `#{hex}` should be written as `#{shortest_form(hex)}`")
29
+ end
30
+
31
+ def valid_hex_format?(hex)
32
+ hex == shortest_form(hex)
33
+ end
34
+
35
+ def shortest_form(hex)
36
+ (can_be_condensed?(hex) ? (hex[0..1] + hex[3] + hex[5]) : hex).downcase
29
37
  end
30
38
 
31
- def can_be_condensed(hex)
32
- hex.length == 6 &&
33
- hex[0] == hex[1] &&
34
- hex[2] == hex[3] &&
35
- hex[4] == hex[5]
39
+ def can_be_condensed?(hex)
40
+ hex.length == 7 &&
41
+ hex[1] == hex[2] &&
42
+ hex[3] == hex[4] &&
43
+ hex[5] == hex[6]
36
44
  end
37
45
  end
38
46
  end
@@ -0,0 +1,15 @@
1
+ module SCSSLint
2
+ class Linter::IdWithExtraneousSelector < Linter
3
+ include LinterRegistry
4
+
5
+ def visit_simple_sequence(seq)
6
+ id_sel = seq.members.find { |simple| simple.is_a?(Sass::Selector::Id) }
7
+ return unless id_sel
8
+
9
+ if seq.members.any? { |simple| !simple.is_a?(Sass::Selector::Id) }
10
+ add_lint(seq, "Selector `#{seq}` can be simplified to `#{id_sel}`, " <<
11
+ 'since IDs should be uniquely identifying')
12
+ end
13
+ end
14
+ end
15
+ end
@@ -3,25 +3,22 @@ module SCSSLint
3
3
  include LinterRegistry
4
4
 
5
5
  def visit_extend(node)
6
- add_lint(node) if selector_has_bad_placeholder?(node.selector)
6
+ if selector_has_bad_placeholder?(node.selector)
7
+ add_name_lint(node, node.selector.join, 'placeholder')
8
+ end
7
9
  end
8
10
 
9
11
  def visit_mixin(node)
10
- check(node)
12
+ check(node, 'mixin')
11
13
  yield # Continue into content block of this mixin's block
12
14
  end
13
15
 
14
16
  def visit_script_funcall(node)
15
- check(node) unless FUNCTION_WHITELIST.include?(node.name)
17
+ check(node, 'function') unless FUNCTION_WHITELIST.include?(node.name)
16
18
  end
17
19
 
18
20
  def visit_script_variable(node)
19
- check(node)
20
- end
21
-
22
- def description
23
- 'Usages of variables, functions, mixins, and placeholders should be ' <<
24
- 'lowercase and use hyphens instead of underscores.'
21
+ check(node, 'variable')
25
22
  end
26
23
 
27
24
  private
@@ -33,8 +30,23 @@ module SCSSLint
33
30
  translateX translateY translateZ
34
31
  ].to_set
35
32
 
36
- def check(node)
37
- add_lint(node) if node_has_bad_name?(node)
33
+ def check(node, node_type)
34
+ add_name_lint(node, node.name, node_type) if node_has_bad_name?(node)
35
+ end
36
+
37
+ def add_name_lint(node, name, node_type)
38
+ fixed_name = name.downcase.gsub(/_/, '-')
39
+
40
+ add_lint(node, "All uses of #{node_type} `#{name}` should be written " <<
41
+ "in lowercase as `#{fixed_name}`")
42
+ end
43
+
44
+ # Given a selector array, returns whether it contains any placeholder
45
+ # selectors with invalid names.
46
+ def selector_has_bad_placeholder?(selector_array)
47
+ extract_string_selectors(selector_array).any? do |selector_str|
48
+ selector_str =~ /%\w*#{INVALID_NAME_CHARS}/
49
+ end
38
50
  end
39
51
  end
40
52
  end
@@ -3,12 +3,25 @@ module SCSSLint
3
3
  include LinterRegistry
4
4
 
5
5
  def visit_prop(node)
6
- line = engine.lines[node.line - 1] if node.line
7
- add_lint(node) if line =~ /^\s*[\w-]+:\s*0[a-z]+;$/i
6
+ if node.value.is_a?(Sass::Script::String) &&
7
+ node.value.type == :identifier
8
+
9
+ node.value.value.scan(/\b(0[a-z]+)\b/i) do |match|
10
+ add_lint(node, MESSAGE_FORMAT % match.first)
11
+ end
12
+ end
13
+
14
+ yield # Continue visiting children
8
15
  end
9
16
 
10
- def description
11
- 'Properties with a value of zero should be unit-less, e.g. "0" instead of "0px"'
17
+ def visit_script_number(node)
18
+ if node.value == 0 && !node.unitless?
19
+ add_lint(node, MESSAGE_FORMAT % node.original)
20
+ end
12
21
  end
22
+
23
+ private
24
+
25
+ MESSAGE_FORMAT = '`%s` should be written without units as `0`'
13
26
  end
14
27
  end
@@ -1,5 +1,6 @@
1
1
  module SCSSLint
2
2
  class Linter < Sass::Tree::Visitors::Base
3
+ include SelectorVisitor
3
4
  include Utils
4
5
 
5
6
  attr_reader :engine, :lints
@@ -13,15 +14,16 @@ module SCSSLint
13
14
  visit(engine.tree)
14
15
  end
15
16
 
17
+ # Define if you want a default message for your linter
16
18
  def description
17
19
  nil
18
20
  end
19
21
 
20
- protected
21
-
22
22
  # Helper for creating lint from a parse tree node
23
- def add_lint(node)
24
- @lints << Lint.new(engine.filename, node.line, description)
23
+ def add_lint(node, message = nil)
24
+ @lints << Lint.new(engine.filename,
25
+ node.line,
26
+ message || description)
25
27
  end
26
28
 
27
29
  # Monkey-patched implementation that adds support for traversing
@@ -34,5 +36,15 @@ module SCSSLint
34
36
  super
35
37
  end
36
38
  end
39
+
40
+ # Modified so we can also visit selectors in linters
41
+ def visit(node)
42
+ # Visit the selector of a rule if parsed rules are available
43
+ if node.is_a?(Sass::Tree::RuleNode) && node.parsed_rules
44
+ visit_selector(node.parsed_rules)
45
+ end
46
+
47
+ super
48
+ end
37
49
  end
38
50
  end
@@ -0,0 +1,32 @@
1
+ module SCSSLint
2
+ # Provides functionality for conveniently visiting a Selector sequence.
3
+ module SelectorVisitor
4
+ def visit_selector(node)
5
+ visit_selector_node(node)
6
+ end
7
+
8
+ private
9
+
10
+ def visit_selector_node(node)
11
+ method = "visit_#{selector_node_name(node)}"
12
+ send(method, node) if respond_to?(method, true)
13
+
14
+ visit_members(node) if node.is_a?(Sass::Selector::AbstractSequence)
15
+ end
16
+
17
+ def visit_members(sequence)
18
+ sequence.members.each do |member|
19
+ visit_selector(member)
20
+ end
21
+ end
22
+
23
+ def selector_node_name(node)
24
+ # Converts the class name of a node into snake_case form, e.g.
25
+ # `Sass::Selector::SimpleSequence` -> `simple_sequence`
26
+ node.class.name.gsub(/.*::(.*?)$/, '\\1').
27
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
28
+ gsub(/([a-z\d])([A-Z])/, '\1_\2').
29
+ downcase
30
+ end
31
+ end
32
+ end
@@ -19,14 +19,6 @@ module SCSSLint
19
19
  split
20
20
  end
21
21
 
22
- # Given a selector array, returns whether it contains any placeholder
23
- # selectors with invalid names.
24
- def selector_has_bad_placeholder?(selector_array)
25
- extract_string_selectors(selector_array).any? do |selector_str|
26
- selector_str =~ /%\w*#{INVALID_NAME_CHARS}/
27
- end
28
- end
29
-
30
22
  def node_has_bad_name?(node)
31
23
  node.name =~ /#{INVALID_NAME_CHARS}/
32
24
  end
@@ -1,3 +1,3 @@
1
1
  module SCSSLint
2
- VERSION = '0.9.0'
2
+ VERSION = '0.10.0'
3
3
  end
data/lib/scss_lint.rb CHANGED
@@ -8,6 +8,7 @@ module SCSSLint
8
8
  autoload :Linter, 'scss_lint/linter'
9
9
  autoload :Reporter, 'scss_lint/reporter'
10
10
  autoload :Runner, 'scss_lint/runner'
11
+ autoload :SelectorVisitor, 'scss_lint/selector_visitor'
11
12
  autoload :Utils, 'scss_lint/utils'
12
13
  autoload :VERSION, 'scss_lint/version'
13
14
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scss-lint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Causes Engineering
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-03 00:00:00.000000000 Z
11
+ date: 2013-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 3.2.9
33
+ version: 3.2.10
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 3.2.9
40
+ version: 3.2.10
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: nokogiri
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,7 +66,7 @@ dependencies:
66
66
  - - '>='
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- description: Opinionated tool that tells you whether or not your SCSS is "bad"
69
+ description: Opinionated tool to help write clean and consistent SCSS
70
70
  email:
71
71
  - eng@causes.com
72
72
  - shane@causes.com
@@ -83,13 +83,14 @@ files:
83
83
  - lib/scss_lint/reporter.rb
84
84
  - lib/scss_lint/lint.rb
85
85
  - lib/scss_lint/linter.rb
86
+ - lib/scss_lint/selector_visitor.rb
86
87
  - lib/scss_lint/linter/property_format.rb
87
88
  - lib/scss_lint/linter/space_before_brace.rb
88
89
  - lib/scss_lint/linter/capitalization_in_selector.rb
89
90
  - lib/scss_lint/linter/declared_name.rb
90
91
  - lib/scss_lint/linter/sorted_properties.rb
91
- - lib/scss_lint/linter/type_in_id_selector.rb
92
92
  - lib/scss_lint/linter/shorthand.rb
93
+ - lib/scss_lint/linter/id_with_extraneous_selector.rb
93
94
  - lib/scss_lint/linter/declaration_order.rb
94
95
  - lib/scss_lint/linter/no_zero_before_decimal.rb
95
96
  - lib/scss_lint/linter/zero_unit.rb
@@ -98,6 +99,7 @@ files:
98
99
  - lib/scss_lint/linter/border_zero.rb
99
100
  - lib/scss_lint/linter/debug_statement.rb
100
101
  - lib/scss_lint/linter/hex_format.rb
102
+ - lib/scss_lint/linter/color_keyword.rb
101
103
  - lib/scss_lint/linter/comment.rb
102
104
  - lib/scss_lint/linter/empty_rule.rb
103
105
  - lib/scss_lint/linter/single_line_per_selector.rb
@@ -133,3 +135,4 @@ signing_key:
133
135
  specification_version: 4
134
136
  summary: SCSS lint tool
135
137
  test_files: []
138
+ has_rdoc:
@@ -1,18 +0,0 @@
1
- module SCSSLint
2
- class Linter::TypeInIdSelector < Linter
3
- include LinterRegistry
4
-
5
- def visit_rule(node)
6
- selectors = node.rule.first.to_s.split(',')
7
- selectors.each do |selector|
8
- add_lint(node) if selector.strip =~ /^[a-z0-9]+#.*/i
9
- end
10
-
11
- yield # Continue linting children
12
- end
13
-
14
- def description
15
- 'Avoid ID names with unnecessary type selectors (e.g. prefer `#id` over `p#id`)'
16
- end
17
- end
18
- end