scss-lint 0.19.0 → 0.20.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: 851fc6dcdb6402f514538367d2a2e10afa223a23
4
- data.tar.gz: f9c630b4c98aaf277a4277abb003144ba65e5bd8
3
+ metadata.gz: 9afb9a82ced2ebf876f8b1a56d829d1360c806fb
4
+ data.tar.gz: fc5bdfbcb4565f704792b0e5f0ccad90230476bd
5
5
  SHA512:
6
- metadata.gz: 9016d101642210006cda461af3c990c6c0bb04e254ff49dd50e19ee3a04dbc87db450b8d687ae47972015bdc564cb3d2bd93aea9131065a79665777e016eff45
7
- data.tar.gz: 3c9cffd85b8d147e1bcf4c7617a2cda7ea8cf81e50ff33a059b1ad0ca0634835c200802ade936fefdf6c89a283d37bc35fa41a719078fdbba4cb4147dbf15e13
6
+ metadata.gz: 64348900df0980e60f076434c46e0475924086124b1b8e5960c4e67d9c64b87519e2cd343a90643b424a57630c9cde537fd9ef7d8c23f3a580bc7245237030cc
7
+ data.tar.gz: 32a3dcb769845f9dba7ad7d07187c754d20ba4156c3c41eb06a59ef0193fe2d9df7ed9dbb19550b26ddb73bde87ef8f0b0afc3f0afcca94aec86406e0ac8d0a4
@@ -39,9 +39,11 @@ linters:
39
39
 
40
40
  LeadingZero:
41
41
  enabled: true
42
+ style: exclude_zero # or 'include_zero'
42
43
 
43
44
  NameFormat:
44
45
  enabled: true
46
+ convention: hyphenated_lowercase # or 'BEM', or a regex pattern
45
47
 
46
48
  PlaceholderInExtend:
47
49
  enabled: true
@@ -5,7 +5,9 @@ module SCSSLint
5
5
  include LinterRegistry
6
6
 
7
7
  def visit_script_color(node)
8
- add_color_lint(node, node.original_string) if color_keyword?(node.original_string)
8
+ color = source_from_range(node.source_range)[COLOR_REGEX, 1]
9
+
10
+ add_color_lint(node, color) if color_keyword?(color)
9
11
  end
10
12
 
11
13
  def visit_script_string(node)
@@ -18,6 +20,8 @@ module SCSSLint
18
20
 
19
21
  private
20
22
 
23
+ COLOR_REGEX = /(#?[a-f0-9]{3,6}|[a-z]+)/i
24
+
21
25
  def add_color_lint(node, original)
22
26
  hex_form = Sass::Script::Value::Color.new(color_rgb(original)).tap do |color|
23
27
  color.options = {} # `inspect` requires options to be set
@@ -4,10 +4,10 @@ module SCSSLint
4
4
  include LinterRegistry
5
5
 
6
6
  def visit_script_color(node)
7
- return unless node.original_string && node.original_string.match(HEX_REGEX)
7
+ return unless color = source_from_range(node.source_range)[HEX_REGEX, 1]
8
8
 
9
- unless valid_hex_format?(node.original_string[HEX_REGEX, 1])
10
- add_hex_lint(node, node.original_string)
9
+ unless valid_hex_format?(color)
10
+ add_hex_lint(node, color)
11
11
  end
12
12
  end
13
13
 
@@ -9,25 +9,44 @@ module SCSSLint
9
9
  non_string_values = remove_quoted_strings(node.value).split
10
10
 
11
11
  non_string_values.each do |value|
12
- if number = value[/\b(0\.\d+)/, 1]
13
- add_leading_zero_lint(node, number)
12
+ if number = value[FRACTIONAL_DIGIT_REGEX, 1]
13
+ check_number(node, number)
14
14
  end
15
15
  end
16
16
  end
17
17
 
18
18
  def visit_script_number(node)
19
- if node.original_string =~ /^0\./
20
- add_leading_zero_lint(node, node.original_string)
21
- end
19
+ return unless number = source_from_range(node.source_range)[FRACTIONAL_DIGIT_REGEX, 1]
20
+
21
+ check_number(node, number)
22
22
  end
23
23
 
24
24
  private
25
25
 
26
- def add_leading_zero_lint(node, number)
27
- trimmed_number = number[/^[^\.]+(.*)$/, 1]
26
+ FRACTIONAL_DIGIT_REGEX = /^-?(0?\.\d+)/
27
+
28
+ CONVENTIONS = {
29
+ 'exclude_zero' => {
30
+ explanation: '`%s` should be written without a leading zero as `%s`',
31
+ validator: ->(original) { original =~ /^\.\d+$/ },
32
+ converter: ->(original) { original[1..-1] },
33
+ },
34
+ 'include_zero' => {
35
+ explanation: '`%s` should be written with a leading zero as `%s`',
36
+ validator: ->(original) { original =~ /^0\.\d+$/ },
37
+ converter: ->(original) { "0#{original}" }
38
+ },
39
+ }
28
40
 
29
- add_lint(node, "`#{number}` should be written without a leading zero " <<
30
- "as `#{trimmed_number}`")
41
+ def check_number(node, original_number)
42
+ style = config.fetch('style', 'exclude_zero')
43
+ convention = CONVENTIONS[style]
44
+
45
+ unless convention[:validator].call(original_number)
46
+ corrected = convention[:converter].call(original_number)
47
+
48
+ add_lint(node, convention[:explanation] % [original_number, corrected])
49
+ end
31
50
  end
32
51
  end
33
52
  end
@@ -5,36 +5,34 @@ module SCSSLint
5
5
  include LinterRegistry
6
6
 
7
7
  def visit_extend(node)
8
- if selector_has_bad_placeholder?(node.selector)
9
- add_name_lint(node, node.selector.join, 'placeholder')
10
- end
8
+ check_placeholder(node)
11
9
  end
12
10
 
13
11
  def visit_function(node)
14
- check_declared_name(node, 'function')
12
+ check_name(node, 'function')
15
13
  yield # Continue into content block of this function definition
16
14
  end
17
15
 
18
16
  def visit_mixin(node)
19
- check_name_use(node, 'mixin')
17
+ check_name(node, 'mixin')
20
18
  yield # Continue into content block of this mixin's block
21
19
  end
22
20
 
23
21
  def visit_mixindef(node)
24
- check_declared_name(node, 'mixin')
22
+ check_name(node, 'mixin')
25
23
  yield # Continue into content block of this mixin definition
26
24
  end
27
25
 
28
26
  def visit_script_funcall(node)
29
- check_name_use(node, 'function') unless FUNCTION_WHITELIST.include?(node.name)
27
+ check_name(node, 'function') unless FUNCTION_WHITELIST.include?(node.name)
30
28
  end
31
29
 
32
30
  def visit_script_variable(node)
33
- check_name_use(node, 'variable')
31
+ check_name(node, 'variable')
34
32
  end
35
33
 
36
34
  def visit_variable(node)
37
- check_declared_name(node, 'variable')
35
+ check_name(node, 'variable')
38
36
  yield # Continue into expression tree for this variable definition
39
37
  end
40
38
 
@@ -47,32 +45,40 @@ module SCSSLint
47
45
  translateX translateY translateZ
48
46
  ].to_set
49
47
 
50
- def check_declared_name(node, node_type)
51
- if node_has_bad_name?(node)
52
- fixed_name = node.name.downcase.gsub(/_/, '-')
53
-
54
- add_lint(node, "Name of #{node_type} `#{node.name}` should " <<
55
- "be written in lowercase as `#{fixed_name}`")
48
+ def check_name(node, node_type, node_text = node.name)
49
+ if convention = violated_convention(node_text)
50
+ add_lint(node, "Name of #{node_type} `#{node_text}` should be " <<
51
+ "written #{convention[:explanation]}")
56
52
  end
57
53
  end
58
54
 
59
- def check_name_use(node, node_type)
60
- add_name_lint(node, node.name, node_type) if node_has_bad_name?(node)
55
+ def check_placeholder(node)
56
+ extract_string_selectors(node.selector).any? do |selector_str|
57
+ check_name(node, 'placeholder', selector_str.gsub('%', ''))
58
+ end
61
59
  end
62
60
 
63
- def add_name_lint(node, name, node_type)
64
- fixed_name = name.downcase.gsub(/_/, '-')
61
+ CONVENTIONS = {
62
+ 'hyphenated_lowercase' => {
63
+ explanation: 'in lowercase with hyphens instead of underscores',
64
+ validator: ->(name) { name !~ /[_A-Z]/ },
65
+ },
66
+ 'BEM' => {
67
+ explanation: 'in BEM (Block Element Modifier) format',
68
+ validator: ->(name) { name !~ /[A-Z]|-{3}|_{3}|[^_]_[^_]/ },
69
+ },
70
+ }
65
71
 
66
- add_lint(node, "All uses of #{node_type} `#{name}` should be written " <<
67
- "in lowercase as `#{fixed_name}`")
68
- end
72
+ # Checks the given name and returns the violated convention if it failed.
73
+ def violated_convention(name_string)
74
+ convention_name = config['convention'] || 'hyphenated_lowercase'
69
75
 
70
- # Given a selector array, returns whether it contains any placeholder
71
- # selectors with invalid names.
72
- def selector_has_bad_placeholder?(selector_array)
73
- extract_string_selectors(selector_array).any? do |selector_str|
74
- selector_str =~ /%\w*#{INVALID_NAME_CHARS}/
75
- end
76
+ convention = CONVENTIONS[convention_name] || {
77
+ explanation: "must match regex /#{convention_name}/",
78
+ validator: ->(name) { name =~ /#{convention_name}/ }
79
+ }
80
+
81
+ convention unless convention[:validator].call(name_string)
76
82
  end
77
83
  end
78
84
  end
@@ -5,8 +5,9 @@ module SCSSLint
5
5
 
6
6
  def visit_root(node)
7
7
  @spaces = config['spaces']
8
+
8
9
  engine.lines.each_with_index do |line, index|
9
- line.scan(/
10
+ line.gsub(%r{((//|/\*).*$)}, '').scan(/
10
11
  (^(\t|\s)*\))? # Capture leading spaces and tabs followed by a `)`
11
12
  (
12
13
  \([ ]*(?!$) # Find `( ` as long as its not EOL )
@@ -4,19 +4,23 @@ module SCSSLint
4
4
  include LinterRegistry
5
5
 
6
6
  def visit_script_string(node)
7
- node.value.scan(/\b(0[a-z]+)\b/i) do |match|
7
+ node.value.scan(ZERO_UNIT_REGEX) do |match|
8
8
  add_lint(node, MESSAGE_FORMAT % match.first)
9
9
  end
10
10
  end
11
11
 
12
12
  def visit_script_number(node)
13
- if node.value == 0 && zero_with_units?(source_from_range(node.source_range))
14
- add_lint(node, MESSAGE_FORMAT % node.original_string)
13
+ length = source_from_range(node.source_range)[ZERO_UNIT_REGEX, 1]
14
+
15
+ if node.value == 0 && zero_with_units?(length)
16
+ add_lint(node, MESSAGE_FORMAT % length)
15
17
  end
16
18
  end
17
19
 
18
20
  private
19
21
 
22
+ ZERO_UNIT_REGEX = /\b(0[a-z]+)\b/i
23
+
20
24
  MESSAGE_FORMAT = '`%s` should be written without units as `0`'
21
25
 
22
26
  def zero_with_units?(string)
@@ -0,0 +1,43 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+
4
+ module SCSSLint
5
+ # Provide task for invoking scss-lint via Rake.
6
+ #
7
+ # @example
8
+ # require 'scss_lint/rake_task'
9
+ # SCSSLint::RakeTask.new
10
+ class RakeTask < Rake::TaskLib
11
+ # The name of the task (default 'scss-lint')
12
+ attr_accessor :name
13
+
14
+ def initialize(*args, &task_block)
15
+ @name = args.shift || :scss_lint
16
+
17
+ desc 'Run scss-lint' unless ::Rake.application.last_comment
18
+
19
+ task(name, *args) do |_, task_args|
20
+ if task_block
21
+ task_block.call(*[self, task_args].slice(0, task_block.arity))
22
+ end
23
+ run_task
24
+ end
25
+ end
26
+
27
+ def run_task
28
+ # Lazy load so task doesn't impact load time of Rakefile
29
+ require 'scss_lint'
30
+
31
+ CLI.new([]).tap do |cli|
32
+ cli.parse_arguments
33
+ cli.run
34
+ end
35
+ rescue SystemExit => ex
36
+ if ex.status == CLI::EXIT_CODES[:data]
37
+ abort('scss-lint found lints')
38
+ elsif ex.status != 0
39
+ abort('scss-lint failed with an error')
40
+ end
41
+ end
42
+ end
43
+ end
@@ -2,47 +2,6 @@
2
2
  # rubocop:disable Documentation
3
3
 
4
4
  module Sass::Script
5
- # Redefine some of the lexer helpers in order to store the original string
6
- # with the created object so that the original string can be inspected rather
7
- # than a typically normalized version.
8
- class Lexer
9
- def color
10
- return unless color_string = scan(REGULAR_EXPRESSIONS[:color])
11
-
12
- unless [4, 7].include?(color_string.length)
13
- raise ::Sass::SyntaxError,
14
- "Colors must have either three or six digits: '#{color_string}'"
15
- end
16
-
17
- [:color, Value::Color.from_string(color_string)]
18
- end
19
-
20
- def number
21
- return unless scan(REGULAR_EXPRESSIONS[:number])
22
- value = @scanner[2] ? @scanner[2].to_f : @scanner[3].to_i
23
- value = -value if @scanner[1]
24
-
25
- number = Value::Number.new(value, Array(@scanner[4])).tap do |num|
26
- num.original_string = @scanner[0]
27
- end
28
- [:number, number]
29
- end
30
- end
31
-
32
- class Parser
33
- # We redefine the ident parser to specially handle color keywords.
34
- def ident
35
- return funcall unless @lexer.peek && @lexer.peek.type == :ident
36
- return if @stop_at && @stop_at.include?(@lexer.peek.value)
37
-
38
- name = @lexer.next
39
- if (color = Value::Color::COLOR_NAMES[name.value.downcase])
40
- return literal_node(Value::Color.from_string(name.value, color), name.source_range)
41
- end
42
- literal_node(Value::String.new(name.value, :identifier), name.source_range)
43
- end
44
- end
45
-
46
5
  # Since the Sass library is already loaded at this point.
47
6
  # Define the `node_name` and `visit_method` class methods for each Sass Script
48
7
  # parse tree node type so that our custom visitor can seamless traverse the
@@ -91,30 +50,6 @@ module Sass::Script
91
50
  end
92
51
  end
93
52
 
94
- # When linting colors, it's convenient to be able to inspect the original
95
- # color string. This adds an attribute to the Color to keep track of the
96
- # original string and provides a method which the modified lexer can use to
97
- # set it.
98
- class Value::Color
99
- attr_accessor :original_string
100
-
101
- def self.from_string(string, rgb = nil)
102
- unless rgb
103
- rgb = string.scan(/^#(..?)(..?)(..?)$/).
104
- first.
105
- map { |hex| hex.ljust(2, hex).to_i(16) }
106
- end
107
-
108
- color = new(rgb, false)
109
- color.original_string = string
110
- color
111
- end
112
- end
113
-
114
- class Value::Number
115
- attr_accessor :original_string
116
- end
117
-
118
53
  # Contains extensions of Sass::Script::Tree::Nodes to add support for
119
54
  # accessing various parts of the parse tree not provided out-of-the-box.
120
55
  module Tree
@@ -20,10 +20,6 @@ module SCSSLint
20
20
  split
21
21
  end
22
22
 
23
- def node_has_bad_name?(node)
24
- node.name =~ /#{INVALID_NAME_CHARS}/
25
- end
26
-
27
23
  def shortest_hex_form(hex)
28
24
  (can_be_condensed?(hex) ? (hex[0..1] + hex[3] + hex[5]) : hex).downcase
29
25
  end
@@ -59,9 +55,5 @@ module SCSSLint
59
55
  def pluralize(value, word)
60
56
  value == 1 ? "#{value} #{word}" : "#{value} #{word}s"
61
57
  end
62
-
63
- private
64
-
65
- INVALID_NAME_CHARS = '[_A-Z]'
66
58
  end
67
59
  end
@@ -1,4 +1,4 @@
1
1
  # Defines the gem version.
2
2
  module SCSSLint
3
- VERSION = '0.19.0'
3
+ VERSION = '0.20.0'
4
4
  end
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.19.0
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Causes Engineering
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-11 00:00:00.000000000 Z
12
+ date: 2014-03-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: colorize
@@ -88,6 +88,7 @@ files:
88
88
  - lib/scss_lint/reporter.rb
89
89
  - lib/scss_lint/lint.rb
90
90
  - lib/scss_lint/linter.rb
91
+ - lib/scss_lint/rake_task.rb
91
92
  - lib/scss_lint/selector_visitor.rb
92
93
  - lib/scss_lint/sass/tree.rb
93
94
  - lib/scss_lint/sass/script.rb