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