scss-lint 0.9.0 → 0.10.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 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