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,78 @@
|
|
|
1
|
+
# Ignore documentation lints as these aren't original implementations.
|
|
2
|
+
# rubocop:disable Documentation
|
|
3
|
+
|
|
4
|
+
module Sass::Script
|
|
5
|
+
# Since the Sass library is already loaded at this point.
|
|
6
|
+
# Define the `node_name` and `visit_method` class methods for each Sass Script
|
|
7
|
+
# parse tree node type so that our custom visitor can seamless traverse the
|
|
8
|
+
# tree.
|
|
9
|
+
# Define the `invalid_child_method_name` and `invalid_parent_method_name`
|
|
10
|
+
# class methods to make errors understandable.
|
|
11
|
+
#
|
|
12
|
+
# This would be easier if we could just define an `inherited` callback, but
|
|
13
|
+
# that won't work since the Sass library will have already been loaded before
|
|
14
|
+
# this code gets loaded, so the `inherited` callback won't be fired.
|
|
15
|
+
#
|
|
16
|
+
# Thus we are left to manually define the methods for each type explicitly.
|
|
17
|
+
{
|
|
18
|
+
'Value' => %w[ArgList Bool Color List Map Null Number String],
|
|
19
|
+
'Tree' => %w[Funcall Interpolation ListLiteral Literal MapLiteral
|
|
20
|
+
Operation Selector StringInterpolation UnaryOperation Variable],
|
|
21
|
+
}.each do |namespace, types|
|
|
22
|
+
types.each do |type|
|
|
23
|
+
node_name = type.downcase
|
|
24
|
+
|
|
25
|
+
eval <<-DECL
|
|
26
|
+
class #{namespace}::#{type}
|
|
27
|
+
def self.node_name
|
|
28
|
+
:script_#{node_name}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.visit_method
|
|
32
|
+
:visit_script_#{node_name}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.invalid_child_method_name
|
|
36
|
+
:"invalid_#{node_name}_child?"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.invalid_parent_method_name
|
|
40
|
+
:"invalid_#{node_name}_parent?"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
DECL
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Value::Base
|
|
48
|
+
attr_accessor :node_parent
|
|
49
|
+
|
|
50
|
+
def children
|
|
51
|
+
[]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def line
|
|
55
|
+
@line || (node_parent && node_parent.line)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def source_range
|
|
59
|
+
@source_range || (node_parent && node_parent.source_range)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Contains extensions of Sass::Script::Tree::Nodes to add support for
|
|
64
|
+
# accessing various parts of the parse tree not provided out-of-the-box.
|
|
65
|
+
module Tree
|
|
66
|
+
class Node
|
|
67
|
+
attr_accessor :node_parent
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class Literal
|
|
71
|
+
# Literals wrap their underlying values. For sake of convenience, consider
|
|
72
|
+
# the wrapped value a child of the Literal.
|
|
73
|
+
def children
|
|
74
|
+
[value]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Contains extensions of Sass::Tree::Nodes to add support for traversing the
|
|
2
|
+
# Sass::Script::Node parse trees contained within the nodes. This probably
|
|
3
|
+
# breaks the Sass compiler, but since we're only doing lints this is fine for
|
|
4
|
+
# now.
|
|
5
|
+
module Sass::Tree
|
|
6
|
+
# Ignore documentation lints as these aren't original implementations.
|
|
7
|
+
# rubocop:disable Documentation
|
|
8
|
+
|
|
9
|
+
# Define some common helper code for use in the various monkey patchings.
|
|
10
|
+
class Node
|
|
11
|
+
# Stores node for which this node is a direct child
|
|
12
|
+
attr_accessor :node_parent
|
|
13
|
+
|
|
14
|
+
# The `args` field of some Sass::Tree::Node classes returns
|
|
15
|
+
# Sass::Script::Variable nodes with no line numbers. This adds the line
|
|
16
|
+
# numbers back in so lint reporting works for those nodes.
|
|
17
|
+
def add_line_numbers_to_args(arg_list)
|
|
18
|
+
arg_list.each do |variable, _default_expr|
|
|
19
|
+
add_line_number(variable)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The Sass parser sometimes doesn't assign line numbers in cases where it
|
|
24
|
+
# should. This is a helper to easily correct that.
|
|
25
|
+
def add_line_number(node)
|
|
26
|
+
node.line ||= line if node.is_a?(::Sass::Script::Tree::Node)
|
|
27
|
+
node
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Sometimes the parse tree doesn't return a Sass::Script::Variable, but just
|
|
31
|
+
# the name of the variable. This helper takes that name and turns it back
|
|
32
|
+
# into a Sass::Script::Variable that supports lint reporting.
|
|
33
|
+
def create_variable(var_name)
|
|
34
|
+
::Sass::Script::Tree::Variable.new(var_name).tap do |v|
|
|
35
|
+
v.line = line # Use line number of the containing parse tree node
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# A number of tree nodes return lists that have strings and
|
|
40
|
+
# Sass::Script::Nodes interspersed within them. This returns a filtered list
|
|
41
|
+
# of just those nodes.
|
|
42
|
+
def extract_script_nodes(list)
|
|
43
|
+
list.select { |item| item.is_a?(::Sass::Script::Tree::Node) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Takes a list of arguments, be they arrays or individual objects, and
|
|
47
|
+
# returns a single flat list that can be passed to
|
|
48
|
+
# Sass::Tree::Visitors::Base#visit_children.
|
|
49
|
+
def concat_expr_lists(*expr_lists)
|
|
50
|
+
expr_lists.flatten.compact
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class CommentNode
|
|
55
|
+
def children
|
|
56
|
+
concat_expr_lists super, extract_script_nodes(value)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class DebugNode
|
|
61
|
+
def children
|
|
62
|
+
concat_expr_lists super, expr
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class DirectiveNode
|
|
67
|
+
def children
|
|
68
|
+
begin
|
|
69
|
+
additional_children = extract_script_nodes(value)
|
|
70
|
+
rescue NotImplementedError # rubocop:disable HandleExceptions
|
|
71
|
+
# Directive nodes may not define `value`
|
|
72
|
+
end
|
|
73
|
+
concat_expr_lists super, additional_children
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class EachNode
|
|
78
|
+
def children
|
|
79
|
+
loop_vars = vars.map { |var| create_variable(var) }
|
|
80
|
+
|
|
81
|
+
concat_expr_lists super, loop_vars, list
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class ExtendNode
|
|
86
|
+
def children
|
|
87
|
+
concat_expr_lists super, extract_script_nodes(selector)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class ForNode
|
|
92
|
+
def children
|
|
93
|
+
concat_expr_lists super, create_variable(var), from, to
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class FunctionNode
|
|
98
|
+
def children
|
|
99
|
+
add_line_numbers_to_args(args)
|
|
100
|
+
|
|
101
|
+
concat_expr_lists super, args, splat
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
class IfNode
|
|
106
|
+
def children
|
|
107
|
+
concat_expr_lists super, expr
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class MixinDefNode
|
|
112
|
+
def children
|
|
113
|
+
add_line_numbers_to_args(args)
|
|
114
|
+
|
|
115
|
+
concat_expr_lists super, args, splat
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class MixinNode
|
|
120
|
+
def children
|
|
121
|
+
add_line_numbers_to_args(args)
|
|
122
|
+
|
|
123
|
+
# Keyword mapping is String -> Expr, so convert the string to a variable
|
|
124
|
+
# node that supports lint reporting
|
|
125
|
+
keyword_exprs = keywords.as_stored.map do |var_name, var_expr|
|
|
126
|
+
[create_variable(var_name), var_expr]
|
|
127
|
+
end if keywords.any?
|
|
128
|
+
|
|
129
|
+
concat_expr_lists super, args, keyword_exprs, splat
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class PropNode
|
|
134
|
+
def children
|
|
135
|
+
concat_expr_lists super, extract_script_nodes(name), add_line_number(value)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class ReturnNode
|
|
140
|
+
def children
|
|
141
|
+
concat_expr_lists super, expr
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
class RuleNode
|
|
146
|
+
def children
|
|
147
|
+
concat_expr_lists super, extract_script_nodes(rule)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class VariableNode
|
|
152
|
+
def children
|
|
153
|
+
concat_expr_lists super, expr
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class WarnNode
|
|
158
|
+
def children
|
|
159
|
+
concat_expr_lists super, expr
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
class WhileNode
|
|
164
|
+
def children
|
|
165
|
+
concat_expr_lists super, expr
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Provides functionality for conveniently visiting a Selector sequence.
|
|
3
|
+
module SelectorVisitor
|
|
4
|
+
def visit_selector(node)
|
|
5
|
+
visit_selector_node(node)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def visit_selector_node(node)
|
|
11
|
+
method = "visit_#{selector_node_name(node)}"
|
|
12
|
+
send(method, node) if respond_to?(method, true)
|
|
13
|
+
|
|
14
|
+
visit_members(node) if node.is_a?(Sass::Selector::AbstractSequence)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def visit_members(sequence)
|
|
18
|
+
sequence.members
|
|
19
|
+
.reject { |member| member.is_a?(String) } # Skip newlines in multi-line comma seqs
|
|
20
|
+
.each do |member|
|
|
21
|
+
visit_selector(member)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def selector_node_name(node)
|
|
26
|
+
# Converts the class name of a node into snake_case form, e.g.
|
|
27
|
+
# `Sass::Selector::SimpleSequence` -> `simple_sequence`
|
|
28
|
+
node.class.name.gsub(/.*::(.*?)$/, '\\1')
|
|
29
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
30
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
31
|
+
.downcase
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module SCSSLint
|
|
2
|
+
# Collection of helpers used across a variety of linters.
|
|
3
|
+
module Utils
|
|
4
|
+
COLOR_REGEX = /^#[a-f0-9]{3,6}$/i
|
|
5
|
+
|
|
6
|
+
# Returns whether the given string is a color literal (keyword or hex code).
|
|
7
|
+
#
|
|
8
|
+
# @param string [String]
|
|
9
|
+
# @return [true,false]
|
|
10
|
+
def color?(string)
|
|
11
|
+
color_keyword?(string) || color_hex?(string)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns whether the given string is a color hexadecimal code.
|
|
15
|
+
#
|
|
16
|
+
# @param string [String]
|
|
17
|
+
# @return [true,false]
|
|
18
|
+
def color_hex?(string)
|
|
19
|
+
string =~ COLOR_REGEX
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns whether the given string is a valid color keyword.
|
|
23
|
+
#
|
|
24
|
+
# @param string [String]
|
|
25
|
+
# @return [true,false]
|
|
26
|
+
def color_keyword?(string)
|
|
27
|
+
color_keyword_to_code(string) && string != 'transparent'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the hexadecimal code for the given color keyword.
|
|
31
|
+
#
|
|
32
|
+
# @param string [String]
|
|
33
|
+
# @return [String] 7-character hexadecimal code (includes `#` prefix)
|
|
34
|
+
def color_keyword_to_code(string)
|
|
35
|
+
Sass::Script::Value::Color::COLOR_NAMES[string]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Given a selector array which is a list of strings with Sass::Script::Nodes
|
|
39
|
+
# interspersed within them, return an array of strings representing those
|
|
40
|
+
# selectors with the Sass::Script::Nodes removed (i.e., ignoring
|
|
41
|
+
# interpolation). For example:
|
|
42
|
+
#
|
|
43
|
+
# .selector-one, .selector-#{$var}-two
|
|
44
|
+
#
|
|
45
|
+
# becomes:
|
|
46
|
+
#
|
|
47
|
+
# .selector-one, .selector--two
|
|
48
|
+
#
|
|
49
|
+
# This is useful for lints that wish to ignore interpolation, since
|
|
50
|
+
# interpolation can't be resolved at this step.
|
|
51
|
+
def extract_string_selectors(selector_array)
|
|
52
|
+
selector_array.reject { |item| item.is_a? Sass::Script::Node }
|
|
53
|
+
.join
|
|
54
|
+
.split
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Takes a string like `hello "world" 'how are' you` and turns it into:
|
|
58
|
+
# `hello you`.
|
|
59
|
+
# This is useful for scanning for keywords in shorthand properties or lists
|
|
60
|
+
# which can contain quoted strings but for which you don't want to inspect
|
|
61
|
+
# quoted strings (e.g. you care about the actual color keyword `red`, not
|
|
62
|
+
# the string "red").
|
|
63
|
+
def remove_quoted_strings(string)
|
|
64
|
+
string.gsub(/"[^"]*"|'[^']*'/, '')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def previous_node(node)
|
|
68
|
+
return unless node && parent = node.node_parent
|
|
69
|
+
index = parent.children.index(node)
|
|
70
|
+
|
|
71
|
+
if index == 0
|
|
72
|
+
parent
|
|
73
|
+
else
|
|
74
|
+
parent.children[index - 1]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def node_siblings(node)
|
|
79
|
+
return unless node && node.node_parent
|
|
80
|
+
node.node_parent
|
|
81
|
+
.children
|
|
82
|
+
.select { |child| child.is_a?(Sass::Tree::Node) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Return nth-ancestor of a node, where 1 is the parent, 2 is grandparent,
|
|
86
|
+
# etc.
|
|
87
|
+
#
|
|
88
|
+
# @param node [Sass::Tree::Node, Sass::Script::Tree::Node]
|
|
89
|
+
# @param level [Integer]
|
|
90
|
+
# @return [Sass::Tree::Node, Sass::Script::Tree::Node, nil]
|
|
91
|
+
def node_ancestor(node, levels)
|
|
92
|
+
while levels > 0
|
|
93
|
+
node = node.node_parent
|
|
94
|
+
return unless node
|
|
95
|
+
levels -= 1
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
node
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def pluralize(value, word)
|
|
102
|
+
value == 1 ? "#{value} #{word}" : "#{value} #{word}s"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Sass doesn't define an equality operator for Sass::Source::Position
|
|
106
|
+
# objects, so we define a helper for our own use.
|
|
107
|
+
def same_position?(pos1, pos2)
|
|
108
|
+
return unless pos1 && pos2
|
|
109
|
+
pos1.line == pos2.line && pos1.offset == pos2.offset
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'scss_lint/cli'
|
|
3
|
+
|
|
4
|
+
describe SCSSLint::CLI do
|
|
5
|
+
let(:config_options) do
|
|
6
|
+
{
|
|
7
|
+
'linters' => {
|
|
8
|
+
'FakeTestLinter1' => { 'enabled' => true },
|
|
9
|
+
'FakeTestLinter2' => { 'enabled' => true },
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
let(:config) { SCSSLint::Config.new(config_options) }
|
|
15
|
+
|
|
16
|
+
class SCSSLint::Linter::FakeTestLinter1 < SCSSLint::Linter; end
|
|
17
|
+
class SCSSLint::Linter::FakeTestLinter2 < SCSSLint::Linter; end
|
|
18
|
+
|
|
19
|
+
before do
|
|
20
|
+
# Silence console output
|
|
21
|
+
@output = ''
|
|
22
|
+
STDOUT.stub(:write) { |*args| @output.<<(*args) }
|
|
23
|
+
|
|
24
|
+
SCSSLint::Config.stub(:load).and_return(config)
|
|
25
|
+
SCSSLint::LinterRegistry.stub(:linters)
|
|
26
|
+
.and_return([SCSSLint::Linter::FakeTestLinter1,
|
|
27
|
+
SCSSLint::Linter::FakeTestLinter2])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '#run' do
|
|
31
|
+
let(:files) { ['file1.scss', 'file2.scss'] }
|
|
32
|
+
let(:flags) { [] }
|
|
33
|
+
subject { SCSSLint::CLI.new }
|
|
34
|
+
|
|
35
|
+
before do
|
|
36
|
+
SCSSLint::FileFinder.any_instance.stub(:find).and_return(files)
|
|
37
|
+
SCSSLint::Runner.any_instance.stub(:find_lints)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def safe_run
|
|
41
|
+
subject.run(flags + files)
|
|
42
|
+
rescue SystemExit
|
|
43
|
+
# Keep running tests
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context 'when there are no lints' do
|
|
47
|
+
before do
|
|
48
|
+
SCSSLint::Runner.any_instance.stub(:lints).and_return([])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'returns a successful exit code' do
|
|
52
|
+
safe_run.should == 0
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'outputs nothing' do
|
|
56
|
+
safe_run
|
|
57
|
+
@output.should be_empty
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
context 'when there are only warnings' do
|
|
62
|
+
before do
|
|
63
|
+
SCSSLint::Runner.any_instance.stub(:lints).and_return([
|
|
64
|
+
SCSSLint::Lint.new(
|
|
65
|
+
SCSSLint::Linter::FakeTestLinter1.new,
|
|
66
|
+
'some-file.scss',
|
|
67
|
+
SCSSLint::Location.new(1, 1, 1),
|
|
68
|
+
'Some description',
|
|
69
|
+
:warning,
|
|
70
|
+
),
|
|
71
|
+
])
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'returns a exit code indicating only warnings were reported' do
|
|
75
|
+
safe_run.should == 1
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'outputs the warnings' do
|
|
79
|
+
safe_run
|
|
80
|
+
@output.should include 'Some description'
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context 'when there are errors' do
|
|
85
|
+
before do
|
|
86
|
+
SCSSLint::Runner.any_instance.stub(:lints).and_return([
|
|
87
|
+
SCSSLint::Lint.new(
|
|
88
|
+
SCSSLint::Linter::FakeTestLinter1.new,
|
|
89
|
+
'some-file.scss',
|
|
90
|
+
SCSSLint::Location.new(1, 1, 1),
|
|
91
|
+
'Some description',
|
|
92
|
+
:error,
|
|
93
|
+
),
|
|
94
|
+
])
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'exits with an error status code' do
|
|
98
|
+
safe_run.should == 2
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'outputs the errors' do
|
|
102
|
+
safe_run
|
|
103
|
+
@output.should include 'Some description'
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context 'when the runner raises an error' do
|
|
108
|
+
let(:backtrace) { %w[file1.rb file2.rb] }
|
|
109
|
+
let(:message) { 'Some error message' }
|
|
110
|
+
|
|
111
|
+
let(:error) do
|
|
112
|
+
StandardError.new(message).tap { |e| e.set_backtrace(backtrace) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
before { SCSSLint::Runner.stub(:new).and_raise(error) }
|
|
116
|
+
|
|
117
|
+
it 'exits with an internal software error status code' do
|
|
118
|
+
subject.should_receive(:halt).with(:software)
|
|
119
|
+
safe_run
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'outputs the error message' do
|
|
123
|
+
safe_run
|
|
124
|
+
@output.should include message
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'outputs the backtrace' do
|
|
128
|
+
safe_run
|
|
129
|
+
@output.should include backtrace.join("\n")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'outputs a link to the issue tracker' do
|
|
133
|
+
safe_run
|
|
134
|
+
@output.should include SCSSLint::BUG_REPORT_URL
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
context 'when a required library is not found' do
|
|
139
|
+
let(:flags) { ['--require', 'some_non_existent_library'] }
|
|
140
|
+
|
|
141
|
+
before do
|
|
142
|
+
Kernel.stub(:require).with('some_non_existent_library').and_raise(
|
|
143
|
+
SCSSLint::Exceptions::RequiredLibraryMissingError
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'exits with an appropriate status code' do
|
|
148
|
+
subject.should_receive(:halt).with(:unavailable)
|
|
149
|
+
safe_run
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
context 'when specified SCSS file globs match no files' do
|
|
154
|
+
before do
|
|
155
|
+
SCSSLint::FileFinder.any_instance.stub(:find)
|
|
156
|
+
.and_raise(SCSSLint::Exceptions::NoFilesError)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'exits with an appropriate status code' do
|
|
160
|
+
subject.should_receive(:halt).with(:no_files)
|
|
161
|
+
safe_run
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
context 'when all specified SCSS files are filtered by exclusions' do
|
|
166
|
+
before do
|
|
167
|
+
SCSSLint::FileFinder.any_instance.stub(:find)
|
|
168
|
+
.and_raise(SCSSLint::Exceptions::AllFilesFilteredError)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'exits with an appropriate status code' do
|
|
172
|
+
subject.should_receive(:halt).with(:files_filtered)
|
|
173
|
+
safe_run
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|