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,59 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks that all properties in a rule set are on their own distinct lines.
|
|
3
|
+
class Linter::SingleLinePerProperty < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
def visit_rule(node) # rubocop:disable CyclomaticComplexity
|
|
7
|
+
single_line = single_line_rule_set?(node)
|
|
8
|
+
return if single_line && config['allow_single_line_rule_sets']
|
|
9
|
+
|
|
10
|
+
properties = node.children.select { |child| child.is_a?(Sass::Tree::PropNode) }
|
|
11
|
+
return unless properties.any?
|
|
12
|
+
|
|
13
|
+
# Special case: if single line rule sets aren't allowed, we want to report
|
|
14
|
+
# when the first property isn't on a separate line from the selector
|
|
15
|
+
if single_line && !config['allow_single_line_rule_sets']
|
|
16
|
+
add_lint(properties.first,
|
|
17
|
+
"Property '#{properties.first.name.join}' should be placed " \
|
|
18
|
+
'on separate line from selector')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
check_adjacent_properties(properties)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# Return whether this rule set occupies a single line.
|
|
27
|
+
#
|
|
28
|
+
# Note that this allows:
|
|
29
|
+
# a,
|
|
30
|
+
# b,
|
|
31
|
+
# i { margin: 0; padding: 0; }
|
|
32
|
+
#
|
|
33
|
+
# and:
|
|
34
|
+
#
|
|
35
|
+
# p { margin: 0; padding: 0; }
|
|
36
|
+
#
|
|
37
|
+
# In other words, the line of the opening curly brace is the line that the
|
|
38
|
+
# rule set is considered to occupy.
|
|
39
|
+
def single_line_rule_set?(rule)
|
|
40
|
+
rule.children.all? { |child| child.line == rule.source_range.end_pos.line }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def first_property_not_on_own_line?(rule, properties)
|
|
44
|
+
properties.any? && properties.first.line == rule.line
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Compare each property against the next property to see if they are on
|
|
48
|
+
# the same line.
|
|
49
|
+
#
|
|
50
|
+
# @param properties [Array<Sass::Tree::PropNode>]
|
|
51
|
+
def check_adjacent_properties(properties)
|
|
52
|
+
properties[0..-2].zip(properties[1..-1]).each do |first, second|
|
|
53
|
+
next unless first.line == second.line
|
|
54
|
+
|
|
55
|
+
add_lint(second, "Property '#{second.name.join}' should be placed on own line")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks that selector sequences are split over multiple lines by comma.
|
|
3
|
+
class Linter::SingleLinePerSelector < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
MESSAGE = 'Each selector in a comma sequence should be on its own line'
|
|
7
|
+
|
|
8
|
+
def visit_comma_sequence(node)
|
|
9
|
+
return unless node.members.count > 1
|
|
10
|
+
|
|
11
|
+
check_comma_on_own_line(node)
|
|
12
|
+
|
|
13
|
+
node.members[1..-1].each_with_index do |sequence, index|
|
|
14
|
+
check_sequence_commas(node, sequence, index)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def check_comma_on_own_line(node)
|
|
21
|
+
return unless node.members[0].members[1] == "\n"
|
|
22
|
+
add_lint(node, MESSAGE)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def check_sequence_commas(node, sequence, index)
|
|
26
|
+
if sequence.members[0] != "\n"
|
|
27
|
+
# Next sequence doesn't reside on its own line
|
|
28
|
+
add_lint(node.line + index, MESSAGE)
|
|
29
|
+
elsif sequence.members[1] == "\n"
|
|
30
|
+
# Comma is on its own line
|
|
31
|
+
add_lint(node.line + index, MESSAGE)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks for spaces after commas in argument lists.
|
|
3
|
+
class Linter::SpaceAfterComma < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
def visit_mixindef(node)
|
|
7
|
+
check_definition(node, :mixin)
|
|
8
|
+
yield
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def visit_mixin(node)
|
|
12
|
+
check_invocation(node, :mixin)
|
|
13
|
+
yield
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def visit_function(node)
|
|
17
|
+
check_definition(node, :function)
|
|
18
|
+
yield
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def visit_script_funcall(node)
|
|
22
|
+
check_invocation(node, :function)
|
|
23
|
+
yield
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def visit_script_listliteral(node)
|
|
27
|
+
check_commas_after_args(node.elements, 'lists') if node.separator == :comma
|
|
28
|
+
yield
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Check parameters of a function/mixin definition
|
|
34
|
+
def check_definition(node, type)
|
|
35
|
+
# Use the default value's source range if one is defined, since that will
|
|
36
|
+
# be the item with the comma after it
|
|
37
|
+
args = node.args.map { |name, default_value| default_value || name }
|
|
38
|
+
args << node.splat if node.splat
|
|
39
|
+
|
|
40
|
+
check_commas_after_args(args, "#{type} parameters")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check arguments passed to a function/mixin invocation
|
|
44
|
+
def check_invocation(node, type)
|
|
45
|
+
args = sort_args_by_position(node.args,
|
|
46
|
+
node.splat,
|
|
47
|
+
node.keywords.values,
|
|
48
|
+
node.kwarg_splat)
|
|
49
|
+
|
|
50
|
+
check_commas_after_args(args, "#{type} arguments")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Since keyword arguments are not guaranteed to be in order, use the source
|
|
54
|
+
# range to order arguments so we check them in the order they were declared.
|
|
55
|
+
def sort_args_by_position(*args)
|
|
56
|
+
args.flatten.compact.sort_by do |arg|
|
|
57
|
+
pos = arg.source_range.end_pos
|
|
58
|
+
[pos.line, pos.offset]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
EXPECTED_SPACES_AFTER_COMMA = 1
|
|
63
|
+
|
|
64
|
+
# Check the comma after each argument in a list for a space following it,
|
|
65
|
+
# reporting a lint using the given [arg_type].
|
|
66
|
+
def check_commas_after_args(args, arg_type)
|
|
67
|
+
# For each arg except the last, check the character following the comma
|
|
68
|
+
args[0..-2].each do |arg|
|
|
69
|
+
# Sometimes the line we're looking at doesn't even contain a comma!
|
|
70
|
+
next unless engine.lines[arg.line - 1].include?(',')
|
|
71
|
+
|
|
72
|
+
offset = find_comma_offset(arg)
|
|
73
|
+
|
|
74
|
+
# Check for space or newline after comma (we allow arguments to be split
|
|
75
|
+
# up over multiple lines).
|
|
76
|
+
spaces = 0
|
|
77
|
+
while (char = character_at(arg.source_range.end_pos, offset + 1)) == ' '
|
|
78
|
+
spaces += 1
|
|
79
|
+
offset += 1
|
|
80
|
+
end
|
|
81
|
+
next if char == "\n" || # Ignore trailing spaces
|
|
82
|
+
spaces == EXPECTED_SPACES_AFTER_COMMA
|
|
83
|
+
|
|
84
|
+
add_lint arg, "Commas in #{arg_type} should be followed by a single space"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Find the comma following this argument.
|
|
89
|
+
#
|
|
90
|
+
# The Sass parser is unpredictable in where it marks the end of the
|
|
91
|
+
# source range. Thus we need to start at the indicated range, and check
|
|
92
|
+
# left and right of that range, gradually moving further outward until
|
|
93
|
+
# we find the comma.
|
|
94
|
+
def find_comma_offset(arg)
|
|
95
|
+
offset = 0
|
|
96
|
+
|
|
97
|
+
if character_at(arg.source_range.end_pos, offset) != ','
|
|
98
|
+
loop do
|
|
99
|
+
offset += 1
|
|
100
|
+
break if character_at(arg.source_range.end_pos, offset) == ','
|
|
101
|
+
offset = -offset
|
|
102
|
+
break if character_at(arg.source_range.end_pos, offset) == ','
|
|
103
|
+
offset = -offset
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
offset
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks for spaces following the colon that separates a property's name from
|
|
3
|
+
# its value.
|
|
4
|
+
class Linter::SpaceAfterPropertyColon < Linter
|
|
5
|
+
include LinterRegistry
|
|
6
|
+
|
|
7
|
+
def visit_rule(node)
|
|
8
|
+
if config['style'] == 'aligned'
|
|
9
|
+
check_properties_alignment(node)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
yield # Continue linting children
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def visit_prop(node)
|
|
16
|
+
whitespace = whitespace_after_colon(node)
|
|
17
|
+
|
|
18
|
+
case config['style']
|
|
19
|
+
when 'no_space'
|
|
20
|
+
check_for_no_spaces(node, whitespace)
|
|
21
|
+
when 'one_space'
|
|
22
|
+
check_for_one_space(node, whitespace)
|
|
23
|
+
when 'at_least_one_space'
|
|
24
|
+
check_for_at_least_one_space(node, whitespace)
|
|
25
|
+
when 'one_space_or_newline'
|
|
26
|
+
check_for_one_space_or_newline(node, whitespace)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
yield # Continue linting children
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def check_for_no_spaces(node, whitespace)
|
|
35
|
+
return if whitespace == []
|
|
36
|
+
add_lint(node, 'Colon after property should not be followed by any spaces')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def check_for_one_space(node, whitespace)
|
|
40
|
+
return if whitespace == [' ']
|
|
41
|
+
add_lint(node, 'Colon after property should be followed by one space')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def check_for_at_least_one_space(node, whitespace)
|
|
45
|
+
return if whitespace.uniq == [' ']
|
|
46
|
+
add_lint(node, 'Colon after property should be followed by at least one space')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def check_for_one_space_or_newline(node, whitespace)
|
|
50
|
+
return if whitespace == [' '] || whitespace == ["\n"]
|
|
51
|
+
return if whitespace[0] == "\n" && whitespace[1..-1].uniq == [' ']
|
|
52
|
+
add_lint(node, 'Colon after property should be followed by one space or a newline')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def check_properties_alignment(rule_node)
|
|
56
|
+
properties = rule_node.children.select { |node| node.is_a?(Sass::Tree::PropNode) }
|
|
57
|
+
|
|
58
|
+
properties.each_slice(2) do |prop1, prop2|
|
|
59
|
+
next unless prop2
|
|
60
|
+
next unless value_offset(prop1) != value_offset(prop2)
|
|
61
|
+
add_lint(prop1, 'Property values should be aligned')
|
|
62
|
+
break
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Offset of value for property
|
|
67
|
+
def value_offset(prop)
|
|
68
|
+
src_range = prop.name_source_range
|
|
69
|
+
src_range.start_pos.offset +
|
|
70
|
+
(src_range.end_pos.offset - src_range.start_pos.offset) +
|
|
71
|
+
whitespace_after_colon(prop).take_while { |w| w == ' ' }.size
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def whitespace_after_colon(node)
|
|
75
|
+
whitespace = []
|
|
76
|
+
offset = 1
|
|
77
|
+
|
|
78
|
+
# Find the colon after the property name
|
|
79
|
+
while character_at(node.name_source_range.start_pos, offset - 1) != ':'
|
|
80
|
+
offset += 1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Count spaces after the colon
|
|
84
|
+
while [' ', "\t", "\n"].include? character_at(node.name_source_range.start_pos, offset)
|
|
85
|
+
whitespace << character_at(node.name_source_range.start_pos, offset)
|
|
86
|
+
offset += 1
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
whitespace
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks for spaces following the name of a property and before the colon
|
|
3
|
+
# separating the property's name from its value.
|
|
4
|
+
class Linter::SpaceAfterPropertyName < Linter
|
|
5
|
+
include LinterRegistry
|
|
6
|
+
|
|
7
|
+
def visit_prop(node)
|
|
8
|
+
offset = property_name_colon_offset(node)
|
|
9
|
+
return unless character_at(node.name_source_range.start_pos, offset - 1) == ' '
|
|
10
|
+
add_lint node, 'Property name should be immediately followed by a colon'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# Deals with a weird Sass bug where the name_source_range of a PropNode does
|
|
16
|
+
# not start at the beginning of the property name.
|
|
17
|
+
def property_name_colon_offset(node)
|
|
18
|
+
offset = 0
|
|
19
|
+
|
|
20
|
+
while character_at(node.name_source_range.start_pos, offset) != ':'
|
|
21
|
+
offset += 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
offset
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks for the presence of a single space before an opening brace.
|
|
3
|
+
class Linter::SpaceBeforeBrace < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
def check_node(node)
|
|
7
|
+
source = source_from_range(node.source_range).strip
|
|
8
|
+
|
|
9
|
+
# Only lint `@include`s which have curly braces
|
|
10
|
+
if source[-1] == '{'
|
|
11
|
+
check_for_space(node, source)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
yield
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
alias_method :visit_function, :check_node
|
|
18
|
+
alias_method :visit_each, :check_node
|
|
19
|
+
alias_method :visit_for, :check_node
|
|
20
|
+
alias_method :visit_function, :check_node
|
|
21
|
+
alias_method :visit_mixindef, :check_node
|
|
22
|
+
alias_method :visit_mixin, :check_node
|
|
23
|
+
alias_method :visit_rule, :check_node
|
|
24
|
+
alias_method :visit_while, :check_node
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def check_for_space(node, string)
|
|
29
|
+
line = node.source_range.end_pos.line
|
|
30
|
+
|
|
31
|
+
if config['allow_single_line_padding'] && node_on_single_line?(node)
|
|
32
|
+
return unless string[-2] != ' '
|
|
33
|
+
add_lint(line, 'Opening curly brace in a single line rule set '\
|
|
34
|
+
'`{` should be preceded by at least one space')
|
|
35
|
+
else
|
|
36
|
+
return unless chars_before_incorrect(string)
|
|
37
|
+
style_message = (config['style'] == 'new_line') ? 'a new line' : 'one space'
|
|
38
|
+
add_lint(line, 'Opening curly brace `{` should be ' \
|
|
39
|
+
"preceded by #{style_message}")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check if the characters before the end of the string
|
|
44
|
+
# are not what they should be
|
|
45
|
+
def chars_before_incorrect(string)
|
|
46
|
+
if config['style'] != 'new_line'
|
|
47
|
+
return !single_space_before(string)
|
|
48
|
+
end
|
|
49
|
+
!newline_before_nonwhitespace(string)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Check if there is one space and only one
|
|
53
|
+
# space before the end of the string
|
|
54
|
+
def single_space_before(string)
|
|
55
|
+
return false if string[-2] != ' '
|
|
56
|
+
return false if string[-3] == ' '
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Check if, starting from the end of a string
|
|
61
|
+
# and moving backwards, towards the beginning,
|
|
62
|
+
# we find a new line before any non-whitespace characters
|
|
63
|
+
def newline_before_nonwhitespace(string)
|
|
64
|
+
offset = -2
|
|
65
|
+
while /\S/.match(string[offset]).nil?
|
|
66
|
+
return true if string[offset] == "\n"
|
|
67
|
+
offset -= 1
|
|
68
|
+
end
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks for the presence of spaces between parentheses.
|
|
3
|
+
class Linter::SpaceBetweenParens < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
def visit_root(_node)
|
|
7
|
+
@spaces = config['spaces']
|
|
8
|
+
|
|
9
|
+
engine.lines.each_with_index do |line, index|
|
|
10
|
+
line.gsub(%r{((//|/\*).*$)}, '').scan(/
|
|
11
|
+
(^(\t|\s)*\))? # Capture leading spaces and tabs followed by a `)`
|
|
12
|
+
(
|
|
13
|
+
\([ ]*(?!$) # Find `( ` as long as its not EOL )
|
|
14
|
+
|
|
|
15
|
+
[ ]*\)
|
|
16
|
+
)?
|
|
17
|
+
/x) do |match|
|
|
18
|
+
check(match[2], index) if match[2]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def check(str, index)
|
|
26
|
+
spaces = str.count ' '
|
|
27
|
+
return if spaces == @spaces
|
|
28
|
+
|
|
29
|
+
location = Location.new(index + 1)
|
|
30
|
+
message = "Expected #{pluralize(@spaces, 'space')} " \
|
|
31
|
+
"between parentheses instead of #{spaces}"
|
|
32
|
+
add_lint(location, message)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks the type of quotes used in string literals.
|
|
3
|
+
class Linter::StringQuotes < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
def visit_comment(_node)
|
|
7
|
+
# Sass allows you to write Sass Script in non-silent comments (/* ... */).
|
|
8
|
+
# Unfortunately, it doesn't report correct source ranges for these script
|
|
9
|
+
# nodes.
|
|
10
|
+
# It's unlikely that a developer wanted to lint the script they wrote in a
|
|
11
|
+
# comment, so just ignore this case entirely and stop traversing the
|
|
12
|
+
# children of comment nodes.
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def visit_script_stringinterpolation(node)
|
|
16
|
+
# We can't statically determine what the resultant string looks like when
|
|
17
|
+
# string interpolation is used, e.g. "one #{$var} three" could be a very
|
|
18
|
+
# different string depending on $var = `'" + "'` or $var = `two`.
|
|
19
|
+
#
|
|
20
|
+
# Thus we manually skip the substrings in the string interpolation and
|
|
21
|
+
# visit the expressions in the interpolation itself.
|
|
22
|
+
node.children.reject { |child| child.is_a?(Sass::Script::Tree::Literal) }
|
|
23
|
+
.each { |child| visit(child) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def visit_script_string(node)
|
|
27
|
+
check_quotes(node, source_from_range(node.source_range))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def visit_import(node)
|
|
31
|
+
# `@import` source range conveniently includes only the quoted string
|
|
32
|
+
check_quotes(node, source_from_range(node.source_range))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def visit_charset(node)
|
|
36
|
+
# `@charset` source range includes entire declaration, so exclude that prefix
|
|
37
|
+
source = source_from_range(node.source_range)[('@charset'.length)..-1]
|
|
38
|
+
|
|
39
|
+
check_quotes(node, source)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def check_quotes(node, source)
|
|
45
|
+
source = source.strip
|
|
46
|
+
string = extract_string_without_quotes(source)
|
|
47
|
+
return unless string
|
|
48
|
+
|
|
49
|
+
case source[0]
|
|
50
|
+
when '"'
|
|
51
|
+
check_double_quotes(node, string)
|
|
52
|
+
when "'"
|
|
53
|
+
check_single_quotes(node, string)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
STRING_WITHOUT_QUOTES_REGEX = %r{
|
|
58
|
+
\A
|
|
59
|
+
["'](.*)["'] # Extract text between quotes
|
|
60
|
+
\s*\)?\s*;?\s* # Sometimes the Sass parser includes a trailing ) or ;
|
|
61
|
+
(//.*)? # Exclude any trailing comments that might have snuck in
|
|
62
|
+
\z
|
|
63
|
+
}x
|
|
64
|
+
|
|
65
|
+
def extract_string_without_quotes(source)
|
|
66
|
+
return unless match = STRING_WITHOUT_QUOTES_REGEX.match(source)
|
|
67
|
+
match[1]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def check_double_quotes(node, string)
|
|
71
|
+
if config['style'] == 'single_quotes'
|
|
72
|
+
add_lint(node, 'Prefer single quoted strings') if string !~ /'/
|
|
73
|
+
else
|
|
74
|
+
if string =~ /(?<! \\) \\"/x && string !~ /'/
|
|
75
|
+
add_lint(node, 'Use single-quoted strings when writing double ' \
|
|
76
|
+
'quotes to avoid having to escape the double quotes')
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def check_single_quotes(node, string)
|
|
82
|
+
if config['style'] == 'single_quotes'
|
|
83
|
+
if string =~ /(?<! \\) \\'/x && string !~ /"/
|
|
84
|
+
add_lint(node, 'Use double-quoted strings when writing single ' \
|
|
85
|
+
'quotes to avoid having to escape the single quotes')
|
|
86
|
+
elsif string =~ /(?<! \\) \\"/x
|
|
87
|
+
add_lint(node, "Don't escape double quotes in single-quoted strings")
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
add_lint(node, 'Prefer double-quoted strings') if string !~ /"/
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|