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,85 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Reports the lack of empty lines between block defintions.
|
|
3
|
+
class Linter::EmptyLineBetweenBlocks < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
def visit_function(node)
|
|
7
|
+
check(node, '@function')
|
|
8
|
+
yield
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def visit_mixin(node)
|
|
12
|
+
# Ignore @includes which don't have any block content
|
|
13
|
+
check(node, '@include') if node.children
|
|
14
|
+
.any? { |child| child.is_a?(Sass::Tree::Node) }
|
|
15
|
+
yield
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def visit_mixindef(node)
|
|
19
|
+
check(node, '@mixin')
|
|
20
|
+
yield
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def visit_rule(node)
|
|
24
|
+
check(node, 'Rule')
|
|
25
|
+
yield
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
MESSAGE_FORMAT = '%s declaration should be %s by an empty line'
|
|
31
|
+
|
|
32
|
+
def check(node, type)
|
|
33
|
+
return if config['ignore_single_line_blocks'] && node_on_single_line?(node)
|
|
34
|
+
check_preceding_node(node, type)
|
|
35
|
+
check_following_node(node, type)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def check_following_node(node, type)
|
|
39
|
+
return unless (following_node = next_node(node)) &&
|
|
40
|
+
(next_start_line = following_node.line)
|
|
41
|
+
|
|
42
|
+
# Special case: ignore comments immediately after a closing brace
|
|
43
|
+
line = engine.lines[next_start_line - 1].strip
|
|
44
|
+
return if following_node.is_a?(Sass::Tree::CommentNode) &&
|
|
45
|
+
line =~ %r{\s*\}?\s*/(/|\*)}
|
|
46
|
+
|
|
47
|
+
# Otherwise check if line before the next node's starting line is blank
|
|
48
|
+
line = engine.lines[next_start_line - 2].strip
|
|
49
|
+
return if line.empty?
|
|
50
|
+
|
|
51
|
+
add_lint(next_start_line - 1, MESSAGE_FORMAT % [type, 'followed'])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# In cases where the previous node is not a block declaration, we won't
|
|
55
|
+
# have run any checks against it, so we need to check here if the previous
|
|
56
|
+
# line is an empty line
|
|
57
|
+
def check_preceding_node(node, type)
|
|
58
|
+
case prev_node(node)
|
|
59
|
+
when
|
|
60
|
+
nil,
|
|
61
|
+
Sass::Tree::FunctionNode,
|
|
62
|
+
Sass::Tree::MixinNode,
|
|
63
|
+
Sass::Tree::MixinDefNode,
|
|
64
|
+
Sass::Tree::RuleNode,
|
|
65
|
+
Sass::Tree::CommentNode
|
|
66
|
+
# Ignore
|
|
67
|
+
else
|
|
68
|
+
unless engine.lines[node.line - 2].strip.empty?
|
|
69
|
+
add_lint(node.line, MESSAGE_FORMAT % [type, 'preceded'])
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def next_node(node)
|
|
75
|
+
return unless siblings = node_siblings(node)
|
|
76
|
+
siblings[siblings.index(node) + 1] if siblings.count > 1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def prev_node(node)
|
|
80
|
+
return unless siblings = node_siblings(node)
|
|
81
|
+
index = siblings.index(node)
|
|
82
|
+
siblings[index - 1] if index > 0 && siblings.count > 1
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks for final newlines at the end of a file.
|
|
3
|
+
class Linter::FinalNewline < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
def visit_root(_node)
|
|
7
|
+
return if engine.lines.empty?
|
|
8
|
+
|
|
9
|
+
ends_with_newline = engine.lines[-1][-1] == "\n"
|
|
10
|
+
|
|
11
|
+
if config['present']
|
|
12
|
+
add_lint(engine.lines.count,
|
|
13
|
+
'Files should end with a trailing newline') unless ends_with_newline
|
|
14
|
+
else
|
|
15
|
+
add_lint(engine.lines.count,
|
|
16
|
+
'Files should not end with a trailing newline') if ends_with_newline
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks that hexadecimal colors are written in the desired number of
|
|
3
|
+
# characters.
|
|
4
|
+
class Linter::HexLength < Linter
|
|
5
|
+
include LinterRegistry
|
|
6
|
+
|
|
7
|
+
HEX_REGEX = /(#(\h{3}|\h{6}))(?!\h)/
|
|
8
|
+
|
|
9
|
+
def visit_script_color(node)
|
|
10
|
+
return unless hex = source_from_range(node.source_range)[HEX_REGEX, 1]
|
|
11
|
+
check_hex(hex, node)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def visit_script_string(node)
|
|
15
|
+
return unless node.type == :identifier
|
|
16
|
+
|
|
17
|
+
node.value.scan(HEX_REGEX) do |match|
|
|
18
|
+
check_hex(match.first, node)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def check_hex(hex, node)
|
|
25
|
+
return if expected(hex) == hex
|
|
26
|
+
|
|
27
|
+
add_lint(node, "Color `#{hex}` should be written as `#{expected(hex)}`")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def expected(hex)
|
|
31
|
+
return short_hex_form(hex) if can_be_shorter?(hex) && short_style?
|
|
32
|
+
return long_hex_form(hex) if hex.length == 4 && !short_style?
|
|
33
|
+
|
|
34
|
+
hex
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def can_be_shorter?(hex)
|
|
38
|
+
hex.length == 7 &&
|
|
39
|
+
hex[1] == hex[2] &&
|
|
40
|
+
hex[3] == hex[4] &&
|
|
41
|
+
hex[5] == hex[6]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def short_hex_form(hex)
|
|
45
|
+
[hex[0..1], hex[3], hex[5]].join
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def long_hex_form(hex)
|
|
49
|
+
[hex[0..1], hex[1], hex[2], hex[2], hex[3], hex[3]].join
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def short_style?
|
|
53
|
+
config['style'] == 'short'
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks if hexadecimal colors are written lowercase / uppercase.
|
|
3
|
+
class Linter::HexNotation < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
HEX_REGEX = /(#(\h{3}|\h{6}))(?!\h)/
|
|
7
|
+
|
|
8
|
+
def visit_script_color(node)
|
|
9
|
+
return unless hex = source_from_range(node.source_range)[HEX_REGEX, 1]
|
|
10
|
+
check_hex(hex, node)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def visit_script_string(node)
|
|
14
|
+
return unless node.type == :identifier
|
|
15
|
+
|
|
16
|
+
node.value.scan(HEX_REGEX) do |match|
|
|
17
|
+
check_hex(match.first, node)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def check_hex(hex, node)
|
|
24
|
+
return if expected(hex) == hex
|
|
25
|
+
|
|
26
|
+
add_lint(node, "Color `#{hex}` should be written as `#{expected(hex)}`")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def expected(color)
|
|
30
|
+
return color.downcase if lowercase_style?
|
|
31
|
+
color.upcase
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def lowercase_style?
|
|
35
|
+
config['style'] == 'lowercase'
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks for invalid hexadecimal colors.
|
|
3
|
+
class Linter::HexValidation < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
def visit_script_string(node)
|
|
7
|
+
return unless node.type == :identifier
|
|
8
|
+
|
|
9
|
+
node.value.scan(/(#\h+)/) do |match|
|
|
10
|
+
check_hex(match.first, node)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
HEX_REGEX = /(#(\h{3}|\h{6}|\h{8}))(?!\h)/
|
|
17
|
+
|
|
18
|
+
def check_hex(hex, node)
|
|
19
|
+
return if HEX_REGEX.match(hex)
|
|
20
|
+
add_lint(node, "Colors must have either three or six digits: `#{hex}`")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks formatting of the basenames of @imported partials
|
|
3
|
+
class Linter::ImportPath < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
def visit_import(node)
|
|
7
|
+
# Ignore CSS imports
|
|
8
|
+
return if File.extname(node.imported_filename) == '.css'
|
|
9
|
+
basename = File.basename(node.imported_filename)
|
|
10
|
+
return if underscore_ok?(basename) && extension_ok?(basename)
|
|
11
|
+
add_lint(node, compose_message(node.imported_filename))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# Checks if the presence or absence of a leading underscore
|
|
17
|
+
# on a string is ok, given config option.
|
|
18
|
+
#
|
|
19
|
+
# @param str [String] the string to check
|
|
20
|
+
# @return [Boolean]
|
|
21
|
+
def underscore_ok?(str)
|
|
22
|
+
underscore_exists = str.start_with?('_')
|
|
23
|
+
config['leading_underscore'] ? underscore_exists : !underscore_exists
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Checks if the presence or absence of an `scss` filename
|
|
27
|
+
# extension on a string is ok, given config option.
|
|
28
|
+
#
|
|
29
|
+
# @param str [String] the string to check
|
|
30
|
+
# @return [Boolean]
|
|
31
|
+
def extension_ok?(str)
|
|
32
|
+
extension_exists = str.end_with?('.scss')
|
|
33
|
+
config['filename_extension'] ? extension_exists : !extension_exists
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Composes a helpful lint message based on the original filename
|
|
37
|
+
# and the config options.
|
|
38
|
+
#
|
|
39
|
+
# @param orig_filename [String] the original filename
|
|
40
|
+
# @return [String] the helpful lint message
|
|
41
|
+
def compose_message(orig_filename)
|
|
42
|
+
orig_basename = File.basename(orig_filename)
|
|
43
|
+
fixed_basename = orig_basename
|
|
44
|
+
|
|
45
|
+
if config['leading_underscore']
|
|
46
|
+
fixed_basename = '_' + fixed_basename unless fixed_basename.match(/^_/)
|
|
47
|
+
else
|
|
48
|
+
fixed_basename = fixed_basename.sub(/^_/, '')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if config['filename_extension']
|
|
52
|
+
fixed_basename += '.scss' unless fixed_basename.match(/\.scss$/)
|
|
53
|
+
else
|
|
54
|
+
fixed_basename = fixed_basename.sub(/\.scss$/, '')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
fixed_filename = orig_filename.sub(/(.*)#{Regexp.quote(orig_basename)}/,
|
|
58
|
+
"\\1#{fixed_basename}")
|
|
59
|
+
"Imported partial `#{orig_filename}` should be written as `#{fixed_filename}`"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Reports the use of !important in properties.
|
|
3
|
+
class Linter::ImportantRule < Linter
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
def visit_prop(node)
|
|
7
|
+
return unless source_from_range(node.source_range).include?('!important')
|
|
8
|
+
|
|
9
|
+
add_lint(node, '!important should not be used')
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Checks for consistent indentation of nested declarations and rule sets.
|
|
3
|
+
class Linter::Indentation < Linter # rubocop:disable ClassLength
|
|
4
|
+
include LinterRegistry
|
|
5
|
+
|
|
6
|
+
def visit_root(_node)
|
|
7
|
+
@indent_width = config['width'].to_i
|
|
8
|
+
@indent_character = config['character'] || 'space'
|
|
9
|
+
@indent = 0
|
|
10
|
+
yield
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def check_and_visit_children(node)
|
|
14
|
+
# Don't continue checking children as the moment a parent's indentation is
|
|
15
|
+
# off it's likely the children will be as will. We don't display the child
|
|
16
|
+
# indentation problems as that would likely make the lint too noisy.
|
|
17
|
+
return if check_indentation(node)
|
|
18
|
+
|
|
19
|
+
@indent += @indent_width
|
|
20
|
+
yield
|
|
21
|
+
@indent -= @indent_width
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check_indentation(node)
|
|
25
|
+
return unless node.line
|
|
26
|
+
|
|
27
|
+
# Ignore the case where the node is on the same line as its previous
|
|
28
|
+
# sibling or its parent, as indentation isn't possible
|
|
29
|
+
return if nodes_on_same_line?(previous_node(node), node)
|
|
30
|
+
|
|
31
|
+
if @indent_character == 'tab'
|
|
32
|
+
other_character = ' '
|
|
33
|
+
other_character_name = 'space'
|
|
34
|
+
else
|
|
35
|
+
other_character = "\t"
|
|
36
|
+
other_character_name = 'tab'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
check_indent_width(node, other_character, @indent_character, other_character_name)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def check_indent_width(node, other_character, character_name, other_character_name)
|
|
43
|
+
actual_indent = node_indent(node)
|
|
44
|
+
|
|
45
|
+
if actual_indent.include?(other_character)
|
|
46
|
+
add_lint(node.line,
|
|
47
|
+
"Line should be indented with #{character_name}s, " \
|
|
48
|
+
"not #{other_character_name}s")
|
|
49
|
+
return true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if config['allow_non_nested_indentation']
|
|
53
|
+
check_arbitrary_indent(node, actual_indent.length, character_name)
|
|
54
|
+
else
|
|
55
|
+
check_regular_indent(node, actual_indent.length, character_name)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Deal with `else` statements
|
|
60
|
+
def visit_if(node, &block)
|
|
61
|
+
check_and_visit_children(node, &block)
|
|
62
|
+
visit(node.else) if node.else
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Need to define this explicitly since @at-root directives can contain
|
|
66
|
+
# inline selectors which produces the same parse tree as if the selector was
|
|
67
|
+
# nested within it. For example:
|
|
68
|
+
#
|
|
69
|
+
# @at-root {
|
|
70
|
+
# .something {
|
|
71
|
+
# ...
|
|
72
|
+
# }
|
|
73
|
+
# }
|
|
74
|
+
#
|
|
75
|
+
# ...and...
|
|
76
|
+
#
|
|
77
|
+
# @at-root .something {
|
|
78
|
+
# ...
|
|
79
|
+
# }
|
|
80
|
+
#
|
|
81
|
+
# ...produce the same parse tree, but result in different indentation
|
|
82
|
+
# levels.
|
|
83
|
+
def visit_atroot(node, &block)
|
|
84
|
+
if at_root_contains_inline_selector?(node)
|
|
85
|
+
return if check_indentation(node)
|
|
86
|
+
yield
|
|
87
|
+
else
|
|
88
|
+
check_and_visit_children(node, &block)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Define node types that increase indentation level
|
|
93
|
+
alias_method :visit_directive, :check_and_visit_children
|
|
94
|
+
alias_method :visit_each, :check_and_visit_children
|
|
95
|
+
alias_method :visit_for, :check_and_visit_children
|
|
96
|
+
alias_method :visit_function, :check_and_visit_children
|
|
97
|
+
alias_method :visit_media, :check_and_visit_children
|
|
98
|
+
alias_method :visit_mixin, :check_and_visit_children
|
|
99
|
+
alias_method :visit_mixindef, :check_and_visit_children
|
|
100
|
+
alias_method :visit_prop, :check_and_visit_children
|
|
101
|
+
alias_method :visit_rule, :check_and_visit_children
|
|
102
|
+
alias_method :visit_supports, :check_and_visit_children
|
|
103
|
+
alias_method :visit_while, :check_and_visit_children
|
|
104
|
+
|
|
105
|
+
# Define node types to check indentation of (notice comments are left out)
|
|
106
|
+
alias_method :visit_charset, :check_indentation
|
|
107
|
+
alias_method :visit_content, :check_indentation
|
|
108
|
+
alias_method :visit_cssimport, :check_indentation
|
|
109
|
+
alias_method :visit_extend, :check_indentation
|
|
110
|
+
alias_method :visit_import, :check_indentation
|
|
111
|
+
alias_method :visit_return, :check_indentation
|
|
112
|
+
alias_method :visit_variable, :check_indentation
|
|
113
|
+
alias_method :visit_warn, :check_indentation
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
def nodes_on_same_line?(node1, node2)
|
|
118
|
+
return unless node1
|
|
119
|
+
|
|
120
|
+
node1.line == node2.line ||
|
|
121
|
+
(node1.source_range && node1.source_range.end_pos.line == node2.line)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def at_root_contains_inline_selector?(node)
|
|
125
|
+
return unless node.children.any?
|
|
126
|
+
return unless first_child_source = node.children.first.source_range
|
|
127
|
+
|
|
128
|
+
same_position?(node.source_range.end_pos, first_child_source.start_pos)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def check_regular_indent(node, actual_indent, character_name)
|
|
132
|
+
return if actual_indent == @indent
|
|
133
|
+
|
|
134
|
+
add_lint(node.line,
|
|
135
|
+
"Line should be indented #{@indent} #{character_name}s, " \
|
|
136
|
+
"but was indented #{actual_indent} #{character_name}s")
|
|
137
|
+
true
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def check_arbitrary_indent(node, actual_indent, character_name) # rubocop:disable CyclomaticComplexity, MethodLength, LineLength
|
|
141
|
+
# Allow rulesets to be indented any amount when the indent is zero, as
|
|
142
|
+
# long as it's a multiple of the indent width
|
|
143
|
+
if ruleset_under_root_node?(node)
|
|
144
|
+
unless actual_indent % @indent_width == 0
|
|
145
|
+
add_lint(node.line,
|
|
146
|
+
"Line must be indented a multiple of #{@indent_width} " \
|
|
147
|
+
"#{character_name}s, but was indented #{actual_indent} #{character_name}s")
|
|
148
|
+
return true
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
if @indent == 0
|
|
153
|
+
unless node.is_a?(Sass::Tree::RuleNode) || actual_indent == 0
|
|
154
|
+
add_lint(node.line,
|
|
155
|
+
"Line should be indented 0 #{character_name}s, " \
|
|
156
|
+
"but was indented #{actual_indent} #{character_name}s")
|
|
157
|
+
return true
|
|
158
|
+
end
|
|
159
|
+
elsif !one_shift_greater_than_parent?(node, actual_indent)
|
|
160
|
+
parent_indent = node_indent(node.node_parent).length
|
|
161
|
+
expected_indent = parent_indent + @indent_width
|
|
162
|
+
|
|
163
|
+
add_lint(node.line,
|
|
164
|
+
"Line should be indented #{expected_indent} #{character_name}s, " \
|
|
165
|
+
"but was indented #{actual_indent} #{character_name}s")
|
|
166
|
+
return true
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Returns whether node is a ruleset not nested within any other ruleset.
|
|
171
|
+
#
|
|
172
|
+
# @param node [Sass::Tree::Node]
|
|
173
|
+
# @return [true,false]
|
|
174
|
+
def ruleset_under_root_node?(node)
|
|
175
|
+
@indent == 0 && node.is_a?(Sass::Tree::RuleNode)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Returns whether node is indented exactly one indent width greater than its
|
|
179
|
+
# parent.
|
|
180
|
+
#
|
|
181
|
+
# @param node [Sass::Tree::Node]
|
|
182
|
+
# @return [true,false]
|
|
183
|
+
def one_shift_greater_than_parent?(node, actual_indent)
|
|
184
|
+
parent_indent = node_indent(node.node_parent).length
|
|
185
|
+
expected_indent = parent_indent + @indent_width
|
|
186
|
+
expected_indent == actual_indent
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Return indentation of a node.
|
|
190
|
+
#
|
|
191
|
+
# @param node [Sass::Tree::Node]
|
|
192
|
+
# @return [Integer]
|
|
193
|
+
def node_indent(node)
|
|
194
|
+
engine.lines[node.line - 1][/^(\s*)/, 1]
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|