scss-lint-bliss 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. data/bin/scss-lint +6 -0
  2. data/config/default.yml +220 -0
  3. data/data/prefixed-identifiers/base.txt +107 -0
  4. data/data/prefixed-identifiers/bourbon.txt +71 -0
  5. data/data/properties.txt +477 -0
  6. data/data/property-sort-orders/concentric.txt +134 -0
  7. data/data/property-sort-orders/recess.txt +149 -0
  8. data/data/property-sort-orders/smacss.txt +137 -0
  9. data/lib/scss_lint.rb +31 -0
  10. data/lib/scss_lint/cli.rb +215 -0
  11. data/lib/scss_lint/config.rb +251 -0
  12. data/lib/scss_lint/constants.rb +8 -0
  13. data/lib/scss_lint/control_comment_processor.rb +126 -0
  14. data/lib/scss_lint/engine.rb +56 -0
  15. data/lib/scss_lint/exceptions.rb +21 -0
  16. data/lib/scss_lint/file_finder.rb +68 -0
  17. data/lib/scss_lint/lint.rb +24 -0
  18. data/lib/scss_lint/linter.rb +161 -0
  19. data/lib/scss_lint/linter/bang_format.rb +52 -0
  20. data/lib/scss_lint/linter/bliss.rb +8 -0
  21. data/lib/scss_lint/linter/bliss/module.rb +101 -0
  22. data/lib/scss_lint/linter/border_zero.rb +39 -0
  23. data/lib/scss_lint/linter/color_keyword.rb +32 -0
  24. data/lib/scss_lint/linter/color_variable.rb +49 -0
  25. data/lib/scss_lint/linter/comment.rb +21 -0
  26. data/lib/scss_lint/linter/compass.rb +7 -0
  27. data/lib/scss_lint/linter/compass/property_with_mixin.rb +47 -0
  28. data/lib/scss_lint/linter/debug_statement.rb +10 -0
  29. data/lib/scss_lint/linter/declaration_order.rb +71 -0
  30. data/lib/scss_lint/linter/duplicate_property.rb +58 -0
  31. data/lib/scss_lint/linter/else_placement.rb +48 -0
  32. data/lib/scss_lint/linter/empty_line_between_blocks.rb +85 -0
  33. data/lib/scss_lint/linter/empty_rule.rb +11 -0
  34. data/lib/scss_lint/linter/final_newline.rb +20 -0
  35. data/lib/scss_lint/linter/hex_length.rb +56 -0
  36. data/lib/scss_lint/linter/hex_notation.rb +38 -0
  37. data/lib/scss_lint/linter/hex_validation.rb +23 -0
  38. data/lib/scss_lint/linter/id_selector.rb +10 -0
  39. data/lib/scss_lint/linter/import_path.rb +62 -0
  40. data/lib/scss_lint/linter/important_rule.rb +12 -0
  41. data/lib/scss_lint/linter/indentation.rb +197 -0
  42. data/lib/scss_lint/linter/leading_zero.rb +49 -0
  43. data/lib/scss_lint/linter/mergeable_selector.rb +60 -0
  44. data/lib/scss_lint/linter/name_format.rb +117 -0
  45. data/lib/scss_lint/linter/nesting_depth.rb +24 -0
  46. data/lib/scss_lint/linter/placeholder_in_extend.rb +22 -0
  47. data/lib/scss_lint/linter/property_count.rb +44 -0
  48. data/lib/scss_lint/linter/property_sort_order.rb +189 -0
  49. data/lib/scss_lint/linter/property_spelling.rb +49 -0
  50. data/lib/scss_lint/linter/property_units.rb +59 -0
  51. data/lib/scss_lint/linter/qualifying_element.rb +42 -0
  52. data/lib/scss_lint/linter/selector_depth.rb +64 -0
  53. data/lib/scss_lint/linter/selector_format.rb +102 -0
  54. data/lib/scss_lint/linter/shorthand.rb +139 -0
  55. data/lib/scss_lint/linter/single_line_per_property.rb +59 -0
  56. data/lib/scss_lint/linter/single_line_per_selector.rb +35 -0
  57. data/lib/scss_lint/linter/space_after_comma.rb +110 -0
  58. data/lib/scss_lint/linter/space_after_property_colon.rb +84 -0
  59. data/lib/scss_lint/linter/space_after_property_name.rb +27 -0
  60. data/lib/scss_lint/linter/space_before_brace.rb +72 -0
  61. data/lib/scss_lint/linter/space_between_parens.rb +35 -0
  62. data/lib/scss_lint/linter/string_quotes.rb +94 -0
  63. data/lib/scss_lint/linter/trailing_semicolon.rb +67 -0
  64. data/lib/scss_lint/linter/trailing_zero.rb +41 -0
  65. data/lib/scss_lint/linter/unnecessary_mantissa.rb +42 -0
  66. data/lib/scss_lint/linter/unnecessary_parent_reference.rb +49 -0
  67. data/lib/scss_lint/linter/url_format.rb +56 -0
  68. data/lib/scss_lint/linter/url_quotes.rb +27 -0
  69. data/lib/scss_lint/linter/variable_for_property.rb +30 -0
  70. data/lib/scss_lint/linter/vendor_prefix.rb +64 -0
  71. data/lib/scss_lint/linter/zero_unit.rb +39 -0
  72. data/lib/scss_lint/linter_registry.rb +26 -0
  73. data/lib/scss_lint/location.rb +38 -0
  74. data/lib/scss_lint/options.rb +109 -0
  75. data/lib/scss_lint/rake_task.rb +106 -0
  76. data/lib/scss_lint/reporter.rb +18 -0
  77. data/lib/scss_lint/reporter/config_reporter.rb +26 -0
  78. data/lib/scss_lint/reporter/default_reporter.rb +27 -0
  79. data/lib/scss_lint/reporter/files_reporter.rb +8 -0
  80. data/lib/scss_lint/reporter/json_reporter.rb +30 -0
  81. data/lib/scss_lint/reporter/xml_reporter.rb +33 -0
  82. data/lib/scss_lint/runner.rb +51 -0
  83. data/lib/scss_lint/sass/script.rb +78 -0
  84. data/lib/scss_lint/sass/tree.rb +168 -0
  85. data/lib/scss_lint/selector_visitor.rb +34 -0
  86. data/lib/scss_lint/utils.rb +112 -0
  87. data/lib/scss_lint/version.rb +4 -0
  88. data/spec/scss_lint/cli_spec.rb +177 -0
  89. data/spec/scss_lint/config_spec.rb +253 -0
  90. data/spec/scss_lint/engine_spec.rb +24 -0
  91. data/spec/scss_lint/file_finder_spec.rb +134 -0
  92. data/spec/scss_lint/linter/bang_format_spec.rb +121 -0
  93. data/spec/scss_lint/linter/bliss/module_spec.rb +254 -0
  94. data/spec/scss_lint/linter/border_zero_spec.rb +118 -0
  95. data/spec/scss_lint/linter/color_keyword_spec.rb +83 -0
  96. data/spec/scss_lint/linter/color_variable_spec.rb +145 -0
  97. data/spec/scss_lint/linter/comment_spec.rb +79 -0
  98. data/spec/scss_lint/linter/compass/property_with_mixin_spec.rb +55 -0
  99. data/spec/scss_lint/linter/debug_statement_spec.rb +21 -0
  100. data/spec/scss_lint/linter/declaration_order_spec.rb +575 -0
  101. data/spec/scss_lint/linter/duplicate_property_spec.rb +189 -0
  102. data/spec/scss_lint/linter/else_placement_spec.rb +106 -0
  103. data/spec/scss_lint/linter/empty_line_between_blocks_spec.rb +276 -0
  104. data/spec/scss_lint/linter/empty_rule_spec.rb +27 -0
  105. data/spec/scss_lint/linter/final_newline_spec.rb +49 -0
  106. data/spec/scss_lint/linter/hex_length_spec.rb +104 -0
  107. data/spec/scss_lint/linter/hex_notation_spec.rb +104 -0
  108. data/spec/scss_lint/linter/hex_validation_spec.rb +40 -0
  109. data/spec/scss_lint/linter/id_selector_spec.rb +62 -0
  110. data/spec/scss_lint/linter/import_path_spec.rb +300 -0
  111. data/spec/scss_lint/linter/important_rule_spec.rb +43 -0
  112. data/spec/scss_lint/linter/indentation_spec.rb +347 -0
  113. data/spec/scss_lint/linter/leading_zero_spec.rb +233 -0
  114. data/spec/scss_lint/linter/mergeable_selector_spec.rb +283 -0
  115. data/spec/scss_lint/linter/name_format_spec.rb +282 -0
  116. data/spec/scss_lint/linter/nesting_depth_spec.rb +114 -0
  117. data/spec/scss_lint/linter/placeholder_in_extend_spec.rb +63 -0
  118. data/spec/scss_lint/linter/property_count_spec.rb +104 -0
  119. data/spec/scss_lint/linter/property_sort_order_spec.rb +426 -0
  120. data/spec/scss_lint/linter/property_spelling_spec.rb +84 -0
  121. data/spec/scss_lint/linter/property_units_spec.rb +229 -0
  122. data/spec/scss_lint/linter/qualifying_element_spec.rb +125 -0
  123. data/spec/scss_lint/linter/selector_depth_spec.rb +159 -0
  124. data/spec/scss_lint/linter/selector_format_spec.rb +632 -0
  125. data/spec/scss_lint/linter/shorthand_spec.rb +198 -0
  126. data/spec/scss_lint/linter/single_line_per_property_spec.rb +73 -0
  127. data/spec/scss_lint/linter/single_line_per_selector_spec.rb +130 -0
  128. data/spec/scss_lint/linter/space_after_comma_spec.rb +332 -0
  129. data/spec/scss_lint/linter/space_after_property_colon_spec.rb +264 -0
  130. data/spec/scss_lint/linter/space_after_property_name_spec.rb +37 -0
  131. data/spec/scss_lint/linter/space_before_brace_spec.rb +829 -0
  132. data/spec/scss_lint/linter/space_between_parens_spec.rb +263 -0
  133. data/spec/scss_lint/linter/string_quotes_spec.rb +335 -0
  134. data/spec/scss_lint/linter/trailing_semicolon_spec.rb +304 -0
  135. data/spec/scss_lint/linter/trailing_zero_spec.rb +176 -0
  136. data/spec/scss_lint/linter/unnecessary_mantissa_spec.rb +67 -0
  137. data/spec/scss_lint/linter/unnecessary_parent_reference_spec.rb +98 -0
  138. data/spec/scss_lint/linter/url_format_spec.rb +55 -0
  139. data/spec/scss_lint/linter/url_quotes_spec.rb +73 -0
  140. data/spec/scss_lint/linter/variable_for_property_spec.rb +145 -0
  141. data/spec/scss_lint/linter/vendor_prefix_spec.rb +371 -0
  142. data/spec/scss_lint/linter/zero_unit_spec.rb +113 -0
  143. data/spec/scss_lint/linter_registry_spec.rb +50 -0
  144. data/spec/scss_lint/linter_spec.rb +292 -0
  145. data/spec/scss_lint/location_spec.rb +42 -0
  146. data/spec/scss_lint/options_spec.rb +34 -0
  147. data/spec/scss_lint/rake_task_spec.rb +43 -0
  148. data/spec/scss_lint/reporter/config_reporter_spec.rb +42 -0
  149. data/spec/scss_lint/reporter/default_reporter_spec.rb +73 -0
  150. data/spec/scss_lint/reporter/files_reporter_spec.rb +38 -0
  151. data/spec/scss_lint/reporter/json_reporter_spec.rb +96 -0
  152. data/spec/scss_lint/reporter/xml_reporter_spec.rb +103 -0
  153. data/spec/scss_lint/reporter_spec.rb +11 -0
  154. data/spec/scss_lint/runner_spec.rb +123 -0
  155. data/spec/scss_lint/selector_visitor_spec.rb +264 -0
  156. data/spec/spec_helper.rb +34 -0
  157. data/spec/support/isolated_environment.rb +25 -0
  158. data/spec/support/matchers/report_lint.rb +48 -0
  159. 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