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.
- checksums.yaml +7 -0
- data/bin/scss-lint +6 -0
- data/config/default.yml +205 -0
- data/data/prefixed-identifiers/base.txt +107 -0
- data/data/prefixed-identifiers/bourbon.txt +71 -0
- data/data/properties.txt +477 -0
- data/data/property-sort-orders/concentric.txt +134 -0
- data/data/property-sort-orders/recess.txt +149 -0
- data/data/property-sort-orders/smacss.txt +137 -0
- data/lib/scss_lint.rb +31 -0
- data/lib/scss_lint/cli.rb +215 -0
- data/lib/scss_lint/config.rb +251 -0
- data/lib/scss_lint/constants.rb +8 -0
- data/lib/scss_lint/control_comment_processor.rb +126 -0
- data/lib/scss_lint/engine.rb +56 -0
- data/lib/scss_lint/exceptions.rb +21 -0
- data/lib/scss_lint/file_finder.rb +68 -0
- data/lib/scss_lint/lint.rb +24 -0
- data/lib/scss_lint/linter.rb +161 -0
- data/lib/scss_lint/linter/bang_format.rb +52 -0
- data/lib/scss_lint/linter/border_zero.rb +39 -0
- data/lib/scss_lint/linter/color_keyword.rb +32 -0
- data/lib/scss_lint/linter/color_variable.rb +60 -0
- data/lib/scss_lint/linter/comment.rb +21 -0
- data/lib/scss_lint/linter/compass.rb +7 -0
- data/lib/scss_lint/linter/compass/property_with_mixin.rb +47 -0
- data/lib/scss_lint/linter/debug_statement.rb +10 -0
- data/lib/scss_lint/linter/declaration_order.rb +71 -0
- data/lib/scss_lint/linter/duplicate_property.rb +58 -0
- data/lib/scss_lint/linter/else_placement.rb +48 -0
- data/lib/scss_lint/linter/empty_line_between_blocks.rb +85 -0
- data/lib/scss_lint/linter/empty_rule.rb +11 -0
- data/lib/scss_lint/linter/final_newline.rb +20 -0
- data/lib/scss_lint/linter/hex_length.rb +56 -0
- data/lib/scss_lint/linter/hex_notation.rb +38 -0
- data/lib/scss_lint/linter/hex_validation.rb +23 -0
- data/lib/scss_lint/linter/id_selector.rb +10 -0
- data/lib/scss_lint/linter/import_path.rb +62 -0
- data/lib/scss_lint/linter/important_rule.rb +12 -0
- data/lib/scss_lint/linter/indentation.rb +197 -0
- data/lib/scss_lint/linter/leading_zero.rb +49 -0
- data/lib/scss_lint/linter/mergeable_selector.rb +60 -0
- data/lib/scss_lint/linter/name_format.rb +117 -0
- data/lib/scss_lint/linter/nesting_depth.rb +24 -0
- data/lib/scss_lint/linter/placeholder_in_extend.rb +22 -0
- data/lib/scss_lint/linter/property_count.rb +44 -0
- data/lib/scss_lint/linter/property_sort_order.rb +198 -0
- data/lib/scss_lint/linter/property_spelling.rb +49 -0
- data/lib/scss_lint/linter/property_units.rb +59 -0
- data/lib/scss_lint/linter/qualifying_element.rb +42 -0
- data/lib/scss_lint/linter/selector_depth.rb +64 -0
- data/lib/scss_lint/linter/selector_format.rb +102 -0
- data/lib/scss_lint/linter/shorthand.rb +139 -0
- data/lib/scss_lint/linter/single_line_per_property.rb +59 -0
- data/lib/scss_lint/linter/single_line_per_selector.rb +35 -0
- data/lib/scss_lint/linter/space_after_comma.rb +110 -0
- data/lib/scss_lint/linter/space_after_property_colon.rb +92 -0
- data/lib/scss_lint/linter/space_after_property_name.rb +27 -0
- data/lib/scss_lint/linter/space_before_brace.rb +72 -0
- data/lib/scss_lint/linter/space_between_parens.rb +35 -0
- data/lib/scss_lint/linter/string_quotes.rb +94 -0
- data/lib/scss_lint/linter/trailing_semicolon.rb +67 -0
- data/lib/scss_lint/linter/trailing_zero.rb +41 -0
- data/lib/scss_lint/linter/unnecessary_mantissa.rb +42 -0
- data/lib/scss_lint/linter/unnecessary_parent_reference.rb +49 -0
- data/lib/scss_lint/linter/url_format.rb +56 -0
- data/lib/scss_lint/linter/url_quotes.rb +27 -0
- data/lib/scss_lint/linter/variable_for_property.rb +30 -0
- data/lib/scss_lint/linter/vendor_prefix.rb +64 -0
- data/lib/scss_lint/linter/zero_unit.rb +39 -0
- data/lib/scss_lint/linter_registry.rb +26 -0
- data/lib/scss_lint/location.rb +38 -0
- data/lib/scss_lint/options.rb +109 -0
- data/lib/scss_lint/rake_task.rb +106 -0
- data/lib/scss_lint/reporter.rb +18 -0
- data/lib/scss_lint/reporter/config_reporter.rb +26 -0
- data/lib/scss_lint/reporter/default_reporter.rb +27 -0
- data/lib/scss_lint/reporter/files_reporter.rb +8 -0
- data/lib/scss_lint/reporter/json_reporter.rb +30 -0
- data/lib/scss_lint/reporter/xml_reporter.rb +33 -0
- data/lib/scss_lint/runner.rb +51 -0
- data/lib/scss_lint/sass/script.rb +78 -0
- data/lib/scss_lint/sass/tree.rb +168 -0
- data/lib/scss_lint/selector_visitor.rb +34 -0
- data/lib/scss_lint/utils.rb +112 -0
- data/lib/scss_lint/version.rb +4 -0
- data/spec/scss_lint/cli_spec.rb +177 -0
- data/spec/scss_lint/config_spec.rb +253 -0
- data/spec/scss_lint/engine_spec.rb +24 -0
- data/spec/scss_lint/file_finder_spec.rb +134 -0
- data/spec/scss_lint/linter/bang_format_spec.rb +121 -0
- data/spec/scss_lint/linter/border_zero_spec.rb +118 -0
- data/spec/scss_lint/linter/color_keyword_spec.rb +83 -0
- data/spec/scss_lint/linter/color_variable_spec.rb +155 -0
- data/spec/scss_lint/linter/comment_spec.rb +79 -0
- data/spec/scss_lint/linter/compass/property_with_mixin_spec.rb +55 -0
- data/spec/scss_lint/linter/debug_statement_spec.rb +21 -0
- data/spec/scss_lint/linter/declaration_order_spec.rb +575 -0
- data/spec/scss_lint/linter/duplicate_property_spec.rb +189 -0
- data/spec/scss_lint/linter/else_placement_spec.rb +106 -0
- data/spec/scss_lint/linter/empty_line_between_blocks_spec.rb +276 -0
- data/spec/scss_lint/linter/empty_rule_spec.rb +27 -0
- data/spec/scss_lint/linter/final_newline_spec.rb +49 -0
- data/spec/scss_lint/linter/hex_length_spec.rb +104 -0
- data/spec/scss_lint/linter/hex_notation_spec.rb +104 -0
- data/spec/scss_lint/linter/hex_validation_spec.rb +40 -0
- data/spec/scss_lint/linter/id_selector_spec.rb +62 -0
- data/spec/scss_lint/linter/import_path_spec.rb +300 -0
- data/spec/scss_lint/linter/important_rule_spec.rb +43 -0
- data/spec/scss_lint/linter/indentation_spec.rb +347 -0
- data/spec/scss_lint/linter/leading_zero_spec.rb +233 -0
- data/spec/scss_lint/linter/mergeable_selector_spec.rb +283 -0
- data/spec/scss_lint/linter/name_format_spec.rb +282 -0
- data/spec/scss_lint/linter/nesting_depth_spec.rb +114 -0
- data/spec/scss_lint/linter/placeholder_in_extend_spec.rb +63 -0
- data/spec/scss_lint/linter/property_count_spec.rb +104 -0
- data/spec/scss_lint/linter/property_sort_order_spec.rb +482 -0
- data/spec/scss_lint/linter/property_spelling_spec.rb +84 -0
- data/spec/scss_lint/linter/property_units_spec.rb +229 -0
- data/spec/scss_lint/linter/qualifying_element_spec.rb +125 -0
- data/spec/scss_lint/linter/selector_depth_spec.rb +159 -0
- data/spec/scss_lint/linter/selector_format_spec.rb +632 -0
- data/spec/scss_lint/linter/shorthand_spec.rb +198 -0
- data/spec/scss_lint/linter/single_line_per_property_spec.rb +73 -0
- data/spec/scss_lint/linter/single_line_per_selector_spec.rb +130 -0
- data/spec/scss_lint/linter/space_after_comma_spec.rb +332 -0
- data/spec/scss_lint/linter/space_after_property_colon_spec.rb +373 -0
- data/spec/scss_lint/linter/space_after_property_name_spec.rb +37 -0
- data/spec/scss_lint/linter/space_before_brace_spec.rb +829 -0
- data/spec/scss_lint/linter/space_between_parens_spec.rb +263 -0
- data/spec/scss_lint/linter/string_quotes_spec.rb +335 -0
- data/spec/scss_lint/linter/trailing_semicolon_spec.rb +304 -0
- data/spec/scss_lint/linter/trailing_zero_spec.rb +176 -0
- data/spec/scss_lint/linter/unnecessary_mantissa_spec.rb +67 -0
- data/spec/scss_lint/linter/unnecessary_parent_reference_spec.rb +98 -0
- data/spec/scss_lint/linter/url_format_spec.rb +55 -0
- data/spec/scss_lint/linter/url_quotes_spec.rb +73 -0
- data/spec/scss_lint/linter/variable_for_property_spec.rb +145 -0
- data/spec/scss_lint/linter/vendor_prefix_spec.rb +371 -0
- data/spec/scss_lint/linter/zero_unit_spec.rb +113 -0
- data/spec/scss_lint/linter_registry_spec.rb +50 -0
- data/spec/scss_lint/linter_spec.rb +292 -0
- data/spec/scss_lint/location_spec.rb +42 -0
- data/spec/scss_lint/options_spec.rb +34 -0
- data/spec/scss_lint/rake_task_spec.rb +43 -0
- data/spec/scss_lint/reporter/config_reporter_spec.rb +42 -0
- data/spec/scss_lint/reporter/default_reporter_spec.rb +73 -0
- data/spec/scss_lint/reporter/files_reporter_spec.rb +38 -0
- data/spec/scss_lint/reporter/json_reporter_spec.rb +96 -0
- data/spec/scss_lint/reporter/xml_reporter_spec.rb +103 -0
- data/spec/scss_lint/reporter_spec.rb +11 -0
- data/spec/scss_lint/runner_spec.rb +123 -0
- data/spec/scss_lint/selector_visitor_spec.rb +264 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/isolated_environment.rb +25 -0
- data/spec/support/matchers/report_lint.rb +48 -0
- 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
|