scss-lint-bliss 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/scss-lint +6 -0
- data/config/default.yml +220 -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/bliss.rb +8 -0
- data/lib/scss_lint/linter/bliss/module.rb +101 -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 +49 -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 +189 -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 +84 -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/bliss/module_spec.rb +254 -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 +145 -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 +426 -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 +264 -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 +342 -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,84 @@
|
|
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
|
+
spaces = spaces_after_colon(node)
|
17
|
+
|
18
|
+
case config['style']
|
19
|
+
when 'no_space'
|
20
|
+
check_for_no_spaces(node, spaces)
|
21
|
+
when 'one_space'
|
22
|
+
check_for_one_space(node, spaces)
|
23
|
+
when 'at_least_one_space'
|
24
|
+
check_for_at_least_one_space(node, spaces)
|
25
|
+
end
|
26
|
+
|
27
|
+
yield # Continue linting children
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def check_for_no_spaces(node, spaces)
|
33
|
+
return if spaces == 0
|
34
|
+
add_lint(node, 'Colon after property should not be followed by any spaces')
|
35
|
+
end
|
36
|
+
|
37
|
+
def check_for_one_space(node, spaces)
|
38
|
+
return if spaces == 1
|
39
|
+
add_lint(node, 'Colon after property should be followed by one space')
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_for_at_least_one_space(node, spaces)
|
43
|
+
return if spaces >= 1
|
44
|
+
add_lint(node, 'Colon after property should be followed by at least one space')
|
45
|
+
end
|
46
|
+
|
47
|
+
def check_properties_alignment(rule_node)
|
48
|
+
properties = rule_node.children.select { |node| node.is_a?(Sass::Tree::PropNode) }
|
49
|
+
|
50
|
+
properties.each_slice(2) do |prop1, prop2|
|
51
|
+
next unless prop2
|
52
|
+
next unless value_offset(prop1) != value_offset(prop2)
|
53
|
+
add_lint(prop1, 'Property values should be aligned')
|
54
|
+
break
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Offset of value for property
|
59
|
+
def value_offset(prop)
|
60
|
+
src_range = prop.name_source_range
|
61
|
+
src_range.start_pos.offset +
|
62
|
+
(src_range.end_pos.offset - src_range.start_pos.offset) +
|
63
|
+
spaces_after_colon(prop)
|
64
|
+
end
|
65
|
+
|
66
|
+
def spaces_after_colon(node)
|
67
|
+
spaces = 0
|
68
|
+
offset = 1
|
69
|
+
|
70
|
+
# Find the colon after the property name
|
71
|
+
while character_at(node.name_source_range.start_pos, offset - 1) != ':'
|
72
|
+
offset += 1
|
73
|
+
end
|
74
|
+
|
75
|
+
# Count spaces after the colon
|
76
|
+
while character_at(node.name_source_range.start_pos, offset) == ' '
|
77
|
+
spaces += 1
|
78
|
+
offset += 1
|
79
|
+
end
|
80
|
+
|
81
|
+
spaces
|
82
|
+
end
|
83
|
+
end
|
84
|
+
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
|