scss_lint 0.38.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.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/bin/scss-lint +6 -0
  3. data/config/default.yml +205 -0
  4. data/data/prefixed-identifiers/base.txt +107 -0
  5. data/data/prefixed-identifiers/bourbon.txt +71 -0
  6. data/data/properties.txt +477 -0
  7. data/data/property-sort-orders/concentric.txt +134 -0
  8. data/data/property-sort-orders/recess.txt +149 -0
  9. data/data/property-sort-orders/smacss.txt +137 -0
  10. data/lib/scss_lint.rb +31 -0
  11. data/lib/scss_lint/cli.rb +215 -0
  12. data/lib/scss_lint/config.rb +251 -0
  13. data/lib/scss_lint/constants.rb +8 -0
  14. data/lib/scss_lint/control_comment_processor.rb +126 -0
  15. data/lib/scss_lint/engine.rb +56 -0
  16. data/lib/scss_lint/exceptions.rb +21 -0
  17. data/lib/scss_lint/file_finder.rb +68 -0
  18. data/lib/scss_lint/lint.rb +24 -0
  19. data/lib/scss_lint/linter.rb +161 -0
  20. data/lib/scss_lint/linter/bang_format.rb +52 -0
  21. data/lib/scss_lint/linter/border_zero.rb +39 -0
  22. data/lib/scss_lint/linter/color_keyword.rb +32 -0
  23. data/lib/scss_lint/linter/color_variable.rb +60 -0
  24. data/lib/scss_lint/linter/comment.rb +21 -0
  25. data/lib/scss_lint/linter/compass.rb +7 -0
  26. data/lib/scss_lint/linter/compass/property_with_mixin.rb +47 -0
  27. data/lib/scss_lint/linter/debug_statement.rb +10 -0
  28. data/lib/scss_lint/linter/declaration_order.rb +71 -0
  29. data/lib/scss_lint/linter/duplicate_property.rb +58 -0
  30. data/lib/scss_lint/linter/else_placement.rb +48 -0
  31. data/lib/scss_lint/linter/empty_line_between_blocks.rb +85 -0
  32. data/lib/scss_lint/linter/empty_rule.rb +11 -0
  33. data/lib/scss_lint/linter/final_newline.rb +20 -0
  34. data/lib/scss_lint/linter/hex_length.rb +56 -0
  35. data/lib/scss_lint/linter/hex_notation.rb +38 -0
  36. data/lib/scss_lint/linter/hex_validation.rb +23 -0
  37. data/lib/scss_lint/linter/id_selector.rb +10 -0
  38. data/lib/scss_lint/linter/import_path.rb +62 -0
  39. data/lib/scss_lint/linter/important_rule.rb +12 -0
  40. data/lib/scss_lint/linter/indentation.rb +197 -0
  41. data/lib/scss_lint/linter/leading_zero.rb +49 -0
  42. data/lib/scss_lint/linter/mergeable_selector.rb +60 -0
  43. data/lib/scss_lint/linter/name_format.rb +117 -0
  44. data/lib/scss_lint/linter/nesting_depth.rb +24 -0
  45. data/lib/scss_lint/linter/placeholder_in_extend.rb +22 -0
  46. data/lib/scss_lint/linter/property_count.rb +44 -0
  47. data/lib/scss_lint/linter/property_sort_order.rb +198 -0
  48. data/lib/scss_lint/linter/property_spelling.rb +49 -0
  49. data/lib/scss_lint/linter/property_units.rb +59 -0
  50. data/lib/scss_lint/linter/qualifying_element.rb +42 -0
  51. data/lib/scss_lint/linter/selector_depth.rb +64 -0
  52. data/lib/scss_lint/linter/selector_format.rb +102 -0
  53. data/lib/scss_lint/linter/shorthand.rb +139 -0
  54. data/lib/scss_lint/linter/single_line_per_property.rb +59 -0
  55. data/lib/scss_lint/linter/single_line_per_selector.rb +35 -0
  56. data/lib/scss_lint/linter/space_after_comma.rb +110 -0
  57. data/lib/scss_lint/linter/space_after_property_colon.rb +92 -0
  58. data/lib/scss_lint/linter/space_after_property_name.rb +27 -0
  59. data/lib/scss_lint/linter/space_before_brace.rb +72 -0
  60. data/lib/scss_lint/linter/space_between_parens.rb +35 -0
  61. data/lib/scss_lint/linter/string_quotes.rb +94 -0
  62. data/lib/scss_lint/linter/trailing_semicolon.rb +67 -0
  63. data/lib/scss_lint/linter/trailing_zero.rb +41 -0
  64. data/lib/scss_lint/linter/unnecessary_mantissa.rb +42 -0
  65. data/lib/scss_lint/linter/unnecessary_parent_reference.rb +49 -0
  66. data/lib/scss_lint/linter/url_format.rb +56 -0
  67. data/lib/scss_lint/linter/url_quotes.rb +27 -0
  68. data/lib/scss_lint/linter/variable_for_property.rb +30 -0
  69. data/lib/scss_lint/linter/vendor_prefix.rb +64 -0
  70. data/lib/scss_lint/linter/zero_unit.rb +39 -0
  71. data/lib/scss_lint/linter_registry.rb +26 -0
  72. data/lib/scss_lint/location.rb +38 -0
  73. data/lib/scss_lint/options.rb +109 -0
  74. data/lib/scss_lint/rake_task.rb +106 -0
  75. data/lib/scss_lint/reporter.rb +18 -0
  76. data/lib/scss_lint/reporter/config_reporter.rb +26 -0
  77. data/lib/scss_lint/reporter/default_reporter.rb +27 -0
  78. data/lib/scss_lint/reporter/files_reporter.rb +8 -0
  79. data/lib/scss_lint/reporter/json_reporter.rb +30 -0
  80. data/lib/scss_lint/reporter/xml_reporter.rb +33 -0
  81. data/lib/scss_lint/runner.rb +51 -0
  82. data/lib/scss_lint/sass/script.rb +78 -0
  83. data/lib/scss_lint/sass/tree.rb +168 -0
  84. data/lib/scss_lint/selector_visitor.rb +34 -0
  85. data/lib/scss_lint/utils.rb +112 -0
  86. data/lib/scss_lint/version.rb +4 -0
  87. data/spec/scss_lint/cli_spec.rb +177 -0
  88. data/spec/scss_lint/config_spec.rb +253 -0
  89. data/spec/scss_lint/engine_spec.rb +24 -0
  90. data/spec/scss_lint/file_finder_spec.rb +134 -0
  91. data/spec/scss_lint/linter/bang_format_spec.rb +121 -0
  92. data/spec/scss_lint/linter/border_zero_spec.rb +118 -0
  93. data/spec/scss_lint/linter/color_keyword_spec.rb +83 -0
  94. data/spec/scss_lint/linter/color_variable_spec.rb +155 -0
  95. data/spec/scss_lint/linter/comment_spec.rb +79 -0
  96. data/spec/scss_lint/linter/compass/property_with_mixin_spec.rb +55 -0
  97. data/spec/scss_lint/linter/debug_statement_spec.rb +21 -0
  98. data/spec/scss_lint/linter/declaration_order_spec.rb +575 -0
  99. data/spec/scss_lint/linter/duplicate_property_spec.rb +189 -0
  100. data/spec/scss_lint/linter/else_placement_spec.rb +106 -0
  101. data/spec/scss_lint/linter/empty_line_between_blocks_spec.rb +276 -0
  102. data/spec/scss_lint/linter/empty_rule_spec.rb +27 -0
  103. data/spec/scss_lint/linter/final_newline_spec.rb +49 -0
  104. data/spec/scss_lint/linter/hex_length_spec.rb +104 -0
  105. data/spec/scss_lint/linter/hex_notation_spec.rb +104 -0
  106. data/spec/scss_lint/linter/hex_validation_spec.rb +40 -0
  107. data/spec/scss_lint/linter/id_selector_spec.rb +62 -0
  108. data/spec/scss_lint/linter/import_path_spec.rb +300 -0
  109. data/spec/scss_lint/linter/important_rule_spec.rb +43 -0
  110. data/spec/scss_lint/linter/indentation_spec.rb +347 -0
  111. data/spec/scss_lint/linter/leading_zero_spec.rb +233 -0
  112. data/spec/scss_lint/linter/mergeable_selector_spec.rb +283 -0
  113. data/spec/scss_lint/linter/name_format_spec.rb +282 -0
  114. data/spec/scss_lint/linter/nesting_depth_spec.rb +114 -0
  115. data/spec/scss_lint/linter/placeholder_in_extend_spec.rb +63 -0
  116. data/spec/scss_lint/linter/property_count_spec.rb +104 -0
  117. data/spec/scss_lint/linter/property_sort_order_spec.rb +482 -0
  118. data/spec/scss_lint/linter/property_spelling_spec.rb +84 -0
  119. data/spec/scss_lint/linter/property_units_spec.rb +229 -0
  120. data/spec/scss_lint/linter/qualifying_element_spec.rb +125 -0
  121. data/spec/scss_lint/linter/selector_depth_spec.rb +159 -0
  122. data/spec/scss_lint/linter/selector_format_spec.rb +632 -0
  123. data/spec/scss_lint/linter/shorthand_spec.rb +198 -0
  124. data/spec/scss_lint/linter/single_line_per_property_spec.rb +73 -0
  125. data/spec/scss_lint/linter/single_line_per_selector_spec.rb +130 -0
  126. data/spec/scss_lint/linter/space_after_comma_spec.rb +332 -0
  127. data/spec/scss_lint/linter/space_after_property_colon_spec.rb +373 -0
  128. data/spec/scss_lint/linter/space_after_property_name_spec.rb +37 -0
  129. data/spec/scss_lint/linter/space_before_brace_spec.rb +829 -0
  130. data/spec/scss_lint/linter/space_between_parens_spec.rb +263 -0
  131. data/spec/scss_lint/linter/string_quotes_spec.rb +335 -0
  132. data/spec/scss_lint/linter/trailing_semicolon_spec.rb +304 -0
  133. data/spec/scss_lint/linter/trailing_zero_spec.rb +176 -0
  134. data/spec/scss_lint/linter/unnecessary_mantissa_spec.rb +67 -0
  135. data/spec/scss_lint/linter/unnecessary_parent_reference_spec.rb +98 -0
  136. data/spec/scss_lint/linter/url_format_spec.rb +55 -0
  137. data/spec/scss_lint/linter/url_quotes_spec.rb +73 -0
  138. data/spec/scss_lint/linter/variable_for_property_spec.rb +145 -0
  139. data/spec/scss_lint/linter/vendor_prefix_spec.rb +371 -0
  140. data/spec/scss_lint/linter/zero_unit_spec.rb +113 -0
  141. data/spec/scss_lint/linter_registry_spec.rb +50 -0
  142. data/spec/scss_lint/linter_spec.rb +292 -0
  143. data/spec/scss_lint/location_spec.rb +42 -0
  144. data/spec/scss_lint/options_spec.rb +34 -0
  145. data/spec/scss_lint/rake_task_spec.rb +43 -0
  146. data/spec/scss_lint/reporter/config_reporter_spec.rb +42 -0
  147. data/spec/scss_lint/reporter/default_reporter_spec.rb +73 -0
  148. data/spec/scss_lint/reporter/files_reporter_spec.rb +38 -0
  149. data/spec/scss_lint/reporter/json_reporter_spec.rb +96 -0
  150. data/spec/scss_lint/reporter/xml_reporter_spec.rb +103 -0
  151. data/spec/scss_lint/reporter_spec.rb +11 -0
  152. data/spec/scss_lint/runner_spec.rb +123 -0
  153. data/spec/scss_lint/selector_visitor_spec.rb +264 -0
  154. data/spec/spec_helper.rb +34 -0
  155. data/spec/support/isolated_environment.rb +25 -0
  156. data/spec/support/matchers/report_lint.rb +48 -0
  157. metadata +328 -0
@@ -0,0 +1,67 @@
1
+ module SCSSLint
2
+ # Checks for a trailing semicolon on statements within rule sets.
3
+ class Linter::TrailingSemicolon < Linter
4
+ include LinterRegistry
5
+
6
+ def visit_extend(node)
7
+ check_semicolon(node)
8
+ end
9
+
10
+ def visit_variable(node)
11
+ check_semicolon(node)
12
+ end
13
+
14
+ def visit_prop(node)
15
+ if node.children.any? { |n| n.is_a?(Sass::Tree::PropNode) }
16
+ yield # Continue checking children
17
+ else
18
+ check_semicolon(node)
19
+ end
20
+ end
21
+
22
+ def visit_mixin(node)
23
+ if node.children.any?
24
+ yield # Continue checking children
25
+ else
26
+ check_semicolon(node)
27
+ end
28
+ end
29
+
30
+ def visit_import(node)
31
+ # Ignore all but the last import for comma-separated @imports
32
+ return if source_from_range(node.source_range) =~ /,\s*$/
33
+ check_semicolon(node)
34
+ end
35
+
36
+ private
37
+
38
+ def check_semicolon(node)
39
+ if has_space_before_semicolon?(node)
40
+ line = node.source_range.start_pos.line
41
+ add_lint line,
42
+ 'Declaration should not have a space before ' \
43
+ 'the terminating semicolon'
44
+ elsif !ends_with_semicolon?(node)
45
+ line = node.source_range.start_pos.line
46
+ add_lint line, 'Declaration should be terminated by a semicolon'
47
+ elsif ends_with_multiple_semicolons?(node)
48
+ line = node.source_range.start_pos.line
49
+ add_lint line, 'Declaration should be terminated by a single semicolon'
50
+ end
51
+ end
52
+
53
+ # Checks that the node is ended by a semicolon (with no whitespace)
54
+ def ends_with_semicolon?(node)
55
+ source_from_range(node.source_range) =~ /;(\s*})?$/
56
+ end
57
+
58
+ def ends_with_multiple_semicolons?(node)
59
+ # Look one character past the end to see if there's another semicolon
60
+ character_at(node.source_range.end_pos, 1) == ';'
61
+ end
62
+
63
+ def has_space_before_semicolon?(node)
64
+ source_from_range(node.source_range) =~ /\s;(\s*})?$/
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,41 @@
1
+ module SCSSLint
2
+ # Checks for unnecessary trailing zeros in numeric values with decimal points.
3
+ class Linter::TrailingZero < Linter
4
+ include LinterRegistry
5
+
6
+ def visit_script_string(node)
7
+ return unless node.type == :identifier
8
+
9
+ non_string_values = remove_quoted_strings(node.value).split
10
+ non_string_values.each do |value|
11
+ next unless number = value[FRACTIONAL_DIGIT_REGEX, 1]
12
+ check_for_trailing_zeros(node, number)
13
+ end
14
+ end
15
+
16
+ def visit_script_number(node)
17
+ return unless number =
18
+ source_from_range(node.source_range)[FRACTIONAL_DIGIT_REGEX, 1]
19
+
20
+ check_for_trailing_zeros(node, number)
21
+ end
22
+
23
+ private
24
+
25
+ FRACTIONAL_DIGIT_REGEX = /^-?(\d*\.\d+)/
26
+
27
+ def check_for_trailing_zeros(node, original_number)
28
+ return unless match = /^(\d*\.\d*)0+$/.match(original_number)
29
+
30
+ fixed_number = match[1]
31
+
32
+ # Handle special case of 0 being the only trailing digit
33
+ fixed_number = fixed_number[0..-2] if fixed_number.end_with?('.')
34
+ fixed_number = 0 if fixed_number.empty? # Handle ".0" -> "0"
35
+
36
+ add_lint(node,
37
+ "`#{original_number}` should be written without a trailing " \
38
+ "zero as `#{fixed_number}`")
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ module SCSSLint
2
+ # Checks for the unnecessary inclusion of a zero-value mantissa in numbers.
3
+ # (e.g. `4.0` could be written as just `4`)
4
+ class Linter::UnnecessaryMantissa < Linter
5
+ include LinterRegistry
6
+
7
+ def visit_script_string(node)
8
+ return unless node.type == :identifier
9
+
10
+ node.value.scan(REAL_NUMBER_REGEX) do |number, integer, mantissa, units|
11
+ if unnecessary_mantissa?(mantissa)
12
+ add_lint(node, MESSAGE_FORMAT % [number, integer, units])
13
+ end
14
+ end
15
+ end
16
+
17
+ def visit_script_number(node)
18
+ return unless match = REAL_NUMBER_REGEX.match(source_from_range(node.source_range))
19
+ return unless unnecessary_mantissa?(match[:mantissa])
20
+
21
+ add_lint(node, MESSAGE_FORMAT % [match[:number], match[:integer],
22
+ match[:units]])
23
+ end
24
+
25
+ private
26
+
27
+ REAL_NUMBER_REGEX = /
28
+ \b(?<number>
29
+ (?<integer>\d*)
30
+ \.
31
+ (?<mantissa>\d+)
32
+ (?<units>\w*)
33
+ )\b
34
+ /ix
35
+
36
+ MESSAGE_FORMAT = '`%s` should be written without the mantissa as `%s%s`'
37
+
38
+ def unnecessary_mantissa?(mantissa)
39
+ mantissa !~ /[^0]/
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,49 @@
1
+ module SCSSLint
2
+ # Checks for unnecessary uses of the parent reference (&) in nested selectors.
3
+ class Linter::UnnecessaryParentReference < Linter
4
+ include LinterRegistry
5
+
6
+ MESSAGE = 'Unnecessary parent selector (&)'
7
+
8
+ def visit_comma_sequence(comma_sequence)
9
+ @multiple_sequences = comma_sequence.members.size > 1
10
+ end
11
+
12
+ def visit_sequence(sequence)
13
+ return unless sequence_starts_with_parent?(sequence.members.first)
14
+
15
+ # Allow concatentation, e.g.
16
+ # element {
17
+ # &.foo {}
18
+ # }
19
+ return if sequence.members.first.members.size > 1
20
+
21
+ # Allow sequences that contain multiple parent references, e.g.
22
+ # element {
23
+ # & + & { ... }
24
+ # }
25
+ return if sequence.members[1..-1].any? { |ss| sequence_starts_with_parent?(ss) }
26
+
27
+ # Special case: allow an isolated parent to appear if it is part of a
28
+ # comma sequence of more than one sequence, as this could be used to DRY
29
+ # up code.
30
+ return if @multiple_sequences && isolated_parent?(sequence)
31
+
32
+ add_lint(sequence.members.first.line, MESSAGE)
33
+ end
34
+
35
+ private
36
+
37
+ def isolated_parent?(sequence)
38
+ sequence.members.size == 1 &&
39
+ sequence_starts_with_parent?(sequence.members.first)
40
+ end
41
+
42
+ def sequence_starts_with_parent?(simple_sequence)
43
+ return unless simple_sequence.is_a?(Sass::Selector::SimpleSequence)
44
+ first = simple_sequence.members.first
45
+ first.is_a?(Sass::Selector::Parent) &&
46
+ first.suffix.nil? # Ignore concatenated selectors, like `&-something`
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,56 @@
1
+ require 'uri'
2
+
3
+ module SCSSLint
4
+ # Checks the format of URLs for unnecessary protocols or domains.
5
+ class Linter::UrlFormat < Linter
6
+ include LinterRegistry
7
+
8
+ def visit_script_funcall(node)
9
+ return unless node.name == 'url'
10
+
11
+ if url_string?(node.args[0])
12
+ url = node.args[0].value.value.to_s
13
+ check_url(url, node)
14
+ end
15
+
16
+ yield
17
+ end
18
+
19
+ def visit_prop(node)
20
+ if url_literal?(node.value)
21
+ url = node.value.to_sass.gsub(/^url\((.*)\)$/, '\\1')
22
+ check_url(url, node)
23
+ end
24
+
25
+ yield
26
+ end
27
+
28
+ private
29
+
30
+ def url_literal?(prop_value)
31
+ return unless prop_value.is_a?(Sass::Script::Tree::Literal)
32
+ return unless prop_value.value.is_a?(Sass::Script::Value::String)
33
+ return unless prop_value.value.type == :identifier
34
+
35
+ prop_value.to_sass.match(/^url\(/)
36
+ end
37
+
38
+ def url_string?(arg)
39
+ return unless arg.is_a?(Sass::Script::Tree::Literal)
40
+ return unless arg.value.is_a?(Sass::Script::Value::String)
41
+
42
+ arg.value.type == :string
43
+ end
44
+
45
+ def check_url(url, node)
46
+ return if url.match(/^data:/)
47
+ uri = URI(url)
48
+
49
+ if uri.scheme || uri.host
50
+ add_lint(node, "URL `#{url}` should not contain protocol or domain")
51
+ end
52
+ rescue URI::Error => ex
53
+ add_lint(node, "Invalid URL `#{url}`: #{ex}")
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ module SCSSLint
2
+ # Checks for quotes in URLs.
3
+ class Linter::UrlQuotes < Linter
4
+ include LinterRegistry
5
+
6
+ def visit_prop(node)
7
+ case node.value
8
+ when Sass::Script::Tree::Literal
9
+ check(node, node.value.value.to_s)
10
+ when Sass::Script::Tree::ListLiteral
11
+ node.value.children.select { |child| child.is_a?(Sass::Script::Tree::Literal) }
12
+ .each { |child| check(node, child.value.to_s) }
13
+ end
14
+
15
+ yield
16
+ end
17
+
18
+ private
19
+
20
+ def check(node, string)
21
+ return unless string =~ /^\s*url\(\s*[^"']/
22
+ return if string =~ /^\s*url\(\s*data:/ # Ignore data URIs
23
+
24
+ add_lint(node, 'URLs should be enclosed in quotes')
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ module SCSSLint
2
+ # Reports the use of literals for properties where variables are prefered.
3
+ class Linter::VariableForProperty < Linter
4
+ include LinterRegistry
5
+
6
+ IGNORED_VALUES = %w[currentColor inherit transparent]
7
+
8
+ def visit_root(_node)
9
+ @properties = Set.new(config['properties'])
10
+ yield if @properties.any?
11
+ end
12
+
13
+ def visit_prop(node)
14
+ property_name = node.name.join
15
+ return unless @properties.include?(property_name)
16
+ return if ignored_value?(node.value)
17
+ return if node.children.first.is_a?(Sass::Script::Tree::Variable)
18
+
19
+ add_lint(node, "Property #{property_name} should use " \
20
+ 'a variable rather than a literal value')
21
+ end
22
+
23
+ private
24
+
25
+ def ignored_value?(value)
26
+ value.respond_to?(:value) &&
27
+ IGNORED_VALUES.include?(value.value.to_s)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,64 @@
1
+ module SCSSLint
2
+ # Checks for vendor prefixes.
3
+ class Linter::VendorPrefix < Linter
4
+ include LinterRegistry
5
+
6
+ def visit_root(_node)
7
+ @identifiers = Set.new(extract_identifiers_from_config)
8
+ @identifiers.merge(Set.new(config['additional_identifiers']))
9
+ @exclusions = Set.new(config['excluded_identifiers'])
10
+ yield
11
+ end
12
+
13
+ def check_node(node)
14
+ name = node.name.is_a?(Array) ? node.name.join : node.name
15
+ # Ignore '@' from @keyframes node name
16
+ check_identifier(node, name.gsub(/^@/, ''))
17
+
18
+ # Check for values
19
+ return unless node.respond_to?(:value) && node.value.respond_to?(:source_range)
20
+ check_identifier(node, source_from_range(node.value.source_range))
21
+ end
22
+
23
+ alias_method :visit_prop, :check_node
24
+ alias_method :visit_pseudo, :check_node
25
+ alias_method :visit_directive, :check_node
26
+
27
+ private
28
+
29
+ def check_identifier(node, identifier)
30
+ return unless identifier =~ /^[_-]/
31
+
32
+ # Strip vendor prefix to check against identifiers.
33
+ # (Also strip closing parentheticals from values like linear-gradient.)
34
+ stripped_identifier = identifier.gsub(/(^[_-][a-zA-Z0-9_]+-|\(.*\)|;)/, '').strip
35
+ return if @exclusions.include?(stripped_identifier)
36
+ return unless @identifiers.include?(stripped_identifier)
37
+
38
+ add_lint(node, 'Avoid vendor prefixes.')
39
+ end
40
+
41
+ def extract_identifiers_from_config
42
+ case config['identifier_list']
43
+ when nil
44
+ nil
45
+ when Array
46
+ config['identifier_list']
47
+ when String
48
+ begin
49
+ file = File.open(File.join(SCSS_LINT_DATA,
50
+ 'prefixed-identifiers',
51
+ "#{config['identifier_list']}.txt"))
52
+ file.read.split("\n").reject { |line| line =~ /^(#|\s*$)/ }
53
+ rescue Errno::ENOENT
54
+ raise SCSSLint::Exceptions::LinterError,
55
+ "Identifier list '#{config['identifier_list']}' does not exist"
56
+ end
57
+ else
58
+ raise SCSSLint::Exceptions::LinterError,
59
+ 'Invalid identifier list specified -- must be the name of a '\
60
+ 'preset or an array of strings'
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,39 @@
1
+ module SCSSLint
2
+ # Checks for unnecessary units on zero values.
3
+ class Linter::ZeroUnit < Linter
4
+ include LinterRegistry
5
+
6
+ def visit_script_string(node)
7
+ return unless node.type == :identifier
8
+
9
+ node.value.scan(ZERO_UNIT_REGEX) do |match|
10
+ next unless zero_with_length_units?(match.first)
11
+ add_lint(node, MESSAGE_FORMAT % match.first)
12
+ end
13
+ end
14
+
15
+ def visit_script_number(node)
16
+ length = source_from_range(node.source_range)[ZERO_UNIT_REGEX, 1]
17
+ return unless zero_with_length_units?(length)
18
+
19
+ add_lint(node, MESSAGE_FORMAT % length)
20
+ end
21
+
22
+ private
23
+
24
+ ZERO_UNIT_REGEX = /
25
+ \b
26
+ (?<!\.|\#) # Ignore zeroes following `#` (colors) or `.` (decimals)
27
+ (0[a-z]+) # Zero followed by letters indicating some sort of unit
28
+ \b
29
+ /ix
30
+
31
+ MESSAGE_FORMAT = '`%s` should be written without units as `0`'
32
+
33
+ LENGTH_UNITS = %w[em ex ch rem vw vh vmin vmax cm mm in pt pc px].to_set
34
+
35
+ def zero_with_length_units?(string)
36
+ string =~ /^0([a-z]+)/ && LENGTH_UNITS.include?(Regexp.last_match(1))
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ module SCSSLint
2
+ class NoSuchLinter < StandardError; end
3
+
4
+ # Stores all linters available to the application.
5
+ module LinterRegistry
6
+ @linters = []
7
+
8
+ class << self
9
+ attr_reader :linters
10
+
11
+ def included(base)
12
+ @linters << base
13
+ end
14
+
15
+ def extract_linters_from(linter_names)
16
+ linter_names.map do |linter_name|
17
+ begin
18
+ Linter.const_get(linter_name)
19
+ rescue NameError
20
+ raise NoSuchLinter, "Linter #{linter_name} does not exist"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end