aliddle-sass 1.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 (238) hide show
  1. data/.yardopts +11 -0
  2. data/CONTRIBUTING +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +201 -0
  5. data/Rakefile +347 -0
  6. data/VERSION +1 -0
  7. data/VERSION_NAME +1 -0
  8. data/bin/sass +9 -0
  9. data/bin/sass-convert +8 -0
  10. data/bin/scss +9 -0
  11. data/extra/update_watch.rb +13 -0
  12. data/init.rb +18 -0
  13. data/lib/sass.rb +95 -0
  14. data/lib/sass/cache_stores.rb +15 -0
  15. data/lib/sass/cache_stores/base.rb +88 -0
  16. data/lib/sass/cache_stores/chain.rb +33 -0
  17. data/lib/sass/cache_stores/filesystem.rb +60 -0
  18. data/lib/sass/cache_stores/memory.rb +47 -0
  19. data/lib/sass/cache_stores/null.rb +25 -0
  20. data/lib/sass/callbacks.rb +66 -0
  21. data/lib/sass/css.rb +409 -0
  22. data/lib/sass/engine.rb +928 -0
  23. data/lib/sass/environment.rb +101 -0
  24. data/lib/sass/error.rb +201 -0
  25. data/lib/sass/exec.rb +707 -0
  26. data/lib/sass/importers.rb +22 -0
  27. data/lib/sass/importers/base.rb +139 -0
  28. data/lib/sass/importers/filesystem.rb +190 -0
  29. data/lib/sass/logger.rb +15 -0
  30. data/lib/sass/logger/base.rb +32 -0
  31. data/lib/sass/logger/log_level.rb +49 -0
  32. data/lib/sass/media.rb +213 -0
  33. data/lib/sass/plugin.rb +132 -0
  34. data/lib/sass/plugin/compiler.rb +406 -0
  35. data/lib/sass/plugin/configuration.rb +123 -0
  36. data/lib/sass/plugin/generic.rb +15 -0
  37. data/lib/sass/plugin/merb.rb +48 -0
  38. data/lib/sass/plugin/rack.rb +60 -0
  39. data/lib/sass/plugin/rails.rb +47 -0
  40. data/lib/sass/plugin/staleness_checker.rb +183 -0
  41. data/lib/sass/railtie.rb +9 -0
  42. data/lib/sass/repl.rb +57 -0
  43. data/lib/sass/root.rb +7 -0
  44. data/lib/sass/script.rb +39 -0
  45. data/lib/sass/script/arg_list.rb +52 -0
  46. data/lib/sass/script/bool.rb +18 -0
  47. data/lib/sass/script/color.rb +606 -0
  48. data/lib/sass/script/css_lexer.rb +29 -0
  49. data/lib/sass/script/css_parser.rb +31 -0
  50. data/lib/sass/script/funcall.rb +237 -0
  51. data/lib/sass/script/functions.rb +1543 -0
  52. data/lib/sass/script/interpolation.rb +79 -0
  53. data/lib/sass/script/lexer.rb +348 -0
  54. data/lib/sass/script/list.rb +85 -0
  55. data/lib/sass/script/literal.rb +221 -0
  56. data/lib/sass/script/node.rb +99 -0
  57. data/lib/sass/script/null.rb +37 -0
  58. data/lib/sass/script/number.rb +453 -0
  59. data/lib/sass/script/operation.rb +110 -0
  60. data/lib/sass/script/parser.rb +495 -0
  61. data/lib/sass/script/string.rb +51 -0
  62. data/lib/sass/script/string_interpolation.rb +103 -0
  63. data/lib/sass/script/unary_operation.rb +69 -0
  64. data/lib/sass/script/variable.rb +58 -0
  65. data/lib/sass/scss.rb +16 -0
  66. data/lib/sass/scss/css_parser.rb +36 -0
  67. data/lib/sass/scss/parser.rb +1179 -0
  68. data/lib/sass/scss/rx.rb +133 -0
  69. data/lib/sass/scss/script_lexer.rb +15 -0
  70. data/lib/sass/scss/script_parser.rb +25 -0
  71. data/lib/sass/scss/static_parser.rb +54 -0
  72. data/lib/sass/selector.rb +452 -0
  73. data/lib/sass/selector/abstract_sequence.rb +94 -0
  74. data/lib/sass/selector/comma_sequence.rb +92 -0
  75. data/lib/sass/selector/sequence.rb +507 -0
  76. data/lib/sass/selector/simple.rb +119 -0
  77. data/lib/sass/selector/simple_sequence.rb +212 -0
  78. data/lib/sass/shared.rb +76 -0
  79. data/lib/sass/supports.rb +229 -0
  80. data/lib/sass/tree/charset_node.rb +22 -0
  81. data/lib/sass/tree/comment_node.rb +82 -0
  82. data/lib/sass/tree/content_node.rb +9 -0
  83. data/lib/sass/tree/css_import_node.rb +60 -0
  84. data/lib/sass/tree/debug_node.rb +18 -0
  85. data/lib/sass/tree/directive_node.rb +42 -0
  86. data/lib/sass/tree/each_node.rb +24 -0
  87. data/lib/sass/tree/extend_node.rb +36 -0
  88. data/lib/sass/tree/for_node.rb +36 -0
  89. data/lib/sass/tree/function_node.rb +34 -0
  90. data/lib/sass/tree/if_node.rb +52 -0
  91. data/lib/sass/tree/import_node.rb +75 -0
  92. data/lib/sass/tree/media_node.rb +58 -0
  93. data/lib/sass/tree/mixin_def_node.rb +38 -0
  94. data/lib/sass/tree/mixin_node.rb +39 -0
  95. data/lib/sass/tree/node.rb +196 -0
  96. data/lib/sass/tree/prop_node.rb +152 -0
  97. data/lib/sass/tree/return_node.rb +18 -0
  98. data/lib/sass/tree/root_node.rb +28 -0
  99. data/lib/sass/tree/rule_node.rb +132 -0
  100. data/lib/sass/tree/supports_node.rb +51 -0
  101. data/lib/sass/tree/trace_node.rb +32 -0
  102. data/lib/sass/tree/variable_node.rb +30 -0
  103. data/lib/sass/tree/visitors/base.rb +75 -0
  104. data/lib/sass/tree/visitors/check_nesting.rb +147 -0
  105. data/lib/sass/tree/visitors/convert.rb +316 -0
  106. data/lib/sass/tree/visitors/cssize.rb +229 -0
  107. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  108. data/lib/sass/tree/visitors/extend.rb +68 -0
  109. data/lib/sass/tree/visitors/perform.rb +446 -0
  110. data/lib/sass/tree/visitors/set_options.rb +125 -0
  111. data/lib/sass/tree/visitors/to_css.rb +230 -0
  112. data/lib/sass/tree/warn_node.rb +18 -0
  113. data/lib/sass/tree/while_node.rb +18 -0
  114. data/lib/sass/util.rb +906 -0
  115. data/lib/sass/util/multibyte_string_scanner.rb +155 -0
  116. data/lib/sass/util/subset_map.rb +109 -0
  117. data/lib/sass/util/test.rb +10 -0
  118. data/lib/sass/version.rb +126 -0
  119. data/rails/init.rb +1 -0
  120. data/test/Gemfile +3 -0
  121. data/test/Gemfile.lock +10 -0
  122. data/test/sass/cache_test.rb +89 -0
  123. data/test/sass/callbacks_test.rb +61 -0
  124. data/test/sass/conversion_test.rb +1760 -0
  125. data/test/sass/css2sass_test.rb +439 -0
  126. data/test/sass/data/hsl-rgb.txt +319 -0
  127. data/test/sass/engine_test.rb +3243 -0
  128. data/test/sass/exec_test.rb +86 -0
  129. data/test/sass/extend_test.rb +1461 -0
  130. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  131. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  132. data/test/sass/functions_test.rb +1139 -0
  133. data/test/sass/importer_test.rb +192 -0
  134. data/test/sass/logger_test.rb +58 -0
  135. data/test/sass/mock_importer.rb +49 -0
  136. data/test/sass/more_results/more1.css +9 -0
  137. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  138. data/test/sass/more_results/more_import.css +29 -0
  139. data/test/sass/more_templates/_more_partial.sass +2 -0
  140. data/test/sass/more_templates/more1.sass +23 -0
  141. data/test/sass/more_templates/more_import.sass +11 -0
  142. data/test/sass/plugin_test.rb +550 -0
  143. data/test/sass/results/alt.css +4 -0
  144. data/test/sass/results/basic.css +9 -0
  145. data/test/sass/results/cached_import_option.css +3 -0
  146. data/test/sass/results/compact.css +5 -0
  147. data/test/sass/results/complex.css +86 -0
  148. data/test/sass/results/compressed.css +1 -0
  149. data/test/sass/results/expanded.css +19 -0
  150. data/test/sass/results/filename_fn.css +3 -0
  151. data/test/sass/results/if.css +3 -0
  152. data/test/sass/results/import.css +31 -0
  153. data/test/sass/results/import_charset.css +5 -0
  154. data/test/sass/results/import_charset_1_8.css +5 -0
  155. data/test/sass/results/import_charset_ibm866.css +5 -0
  156. data/test/sass/results/import_content.css +1 -0
  157. data/test/sass/results/line_numbers.css +49 -0
  158. data/test/sass/results/mixins.css +95 -0
  159. data/test/sass/results/multiline.css +24 -0
  160. data/test/sass/results/nested.css +22 -0
  161. data/test/sass/results/options.css +1 -0
  162. data/test/sass/results/parent_ref.css +13 -0
  163. data/test/sass/results/script.css +16 -0
  164. data/test/sass/results/scss_import.css +31 -0
  165. data/test/sass/results/scss_importee.css +2 -0
  166. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  167. data/test/sass/results/subdir/subdir.css +3 -0
  168. data/test/sass/results/units.css +11 -0
  169. data/test/sass/results/warn.css +0 -0
  170. data/test/sass/results/warn_imported.css +0 -0
  171. data/test/sass/script_conversion_test.rb +299 -0
  172. data/test/sass/script_test.rb +591 -0
  173. data/test/sass/scss/css_test.rb +1093 -0
  174. data/test/sass/scss/rx_test.rb +156 -0
  175. data/test/sass/scss/scss_test.rb +2043 -0
  176. data/test/sass/scss/test_helper.rb +37 -0
  177. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  178. data/test/sass/templates/_double_import_loop2.sass +1 -0
  179. data/test/sass/templates/_filename_fn_import.scss +11 -0
  180. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  181. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  182. data/test/sass/templates/_imported_content.sass +3 -0
  183. data/test/sass/templates/_partial.sass +2 -0
  184. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  185. data/test/sass/templates/alt.sass +16 -0
  186. data/test/sass/templates/basic.sass +23 -0
  187. data/test/sass/templates/bork1.sass +2 -0
  188. data/test/sass/templates/bork2.sass +2 -0
  189. data/test/sass/templates/bork3.sass +2 -0
  190. data/test/sass/templates/bork4.sass +2 -0
  191. data/test/sass/templates/bork5.sass +3 -0
  192. data/test/sass/templates/cached_import_option.scss +3 -0
  193. data/test/sass/templates/compact.sass +17 -0
  194. data/test/sass/templates/complex.sass +305 -0
  195. data/test/sass/templates/compressed.sass +15 -0
  196. data/test/sass/templates/double_import_loop1.sass +1 -0
  197. data/test/sass/templates/expanded.sass +17 -0
  198. data/test/sass/templates/filename_fn.scss +18 -0
  199. data/test/sass/templates/if.sass +11 -0
  200. data/test/sass/templates/import.sass +12 -0
  201. data/test/sass/templates/import_charset.sass +9 -0
  202. data/test/sass/templates/import_charset_1_8.sass +6 -0
  203. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  204. data/test/sass/templates/import_content.sass +4 -0
  205. data/test/sass/templates/importee.less +2 -0
  206. data/test/sass/templates/importee.sass +19 -0
  207. data/test/sass/templates/line_numbers.sass +13 -0
  208. data/test/sass/templates/mixin_bork.sass +5 -0
  209. data/test/sass/templates/mixins.sass +76 -0
  210. data/test/sass/templates/multiline.sass +20 -0
  211. data/test/sass/templates/nested.sass +25 -0
  212. data/test/sass/templates/nested_bork1.sass +2 -0
  213. data/test/sass/templates/nested_bork2.sass +2 -0
  214. data/test/sass/templates/nested_bork3.sass +2 -0
  215. data/test/sass/templates/nested_bork4.sass +2 -0
  216. data/test/sass/templates/nested_import.sass +2 -0
  217. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  218. data/test/sass/templates/options.sass +2 -0
  219. data/test/sass/templates/parent_ref.sass +25 -0
  220. data/test/sass/templates/same_name_different_ext.sass +2 -0
  221. data/test/sass/templates/same_name_different_ext.scss +1 -0
  222. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  223. data/test/sass/templates/script.sass +101 -0
  224. data/test/sass/templates/scss_import.scss +11 -0
  225. data/test/sass/templates/scss_importee.scss +1 -0
  226. data/test/sass/templates/single_import_loop.sass +1 -0
  227. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  228. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  229. data/test/sass/templates/subdir/subdir.sass +6 -0
  230. data/test/sass/templates/units.sass +11 -0
  231. data/test/sass/templates/warn.sass +3 -0
  232. data/test/sass/templates/warn_imported.sass +4 -0
  233. data/test/sass/test_helper.rb +8 -0
  234. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  235. data/test/sass/util/subset_map_test.rb +91 -0
  236. data/test/sass/util_test.rb +313 -0
  237. data/test/test_helper.rb +80 -0
  238. metadata +348 -0
@@ -0,0 +1,94 @@
1
+ module Sass
2
+ module Selector
3
+ # The abstract parent class of the various selector sequence classes.
4
+ #
5
+ # All subclasses should implement a `members` method that returns an array
6
+ # of object that respond to `#line=` and `#filename=`, as well as a `to_a`
7
+ # method that returns an array of strings and script nodes.
8
+ class AbstractSequence
9
+ # The line of the Sass template on which this selector was declared.
10
+ #
11
+ # @return [Fixnum]
12
+ attr_reader :line
13
+
14
+ # The name of the file in which this selector was declared.
15
+ #
16
+ # @return [String, nil]
17
+ attr_reader :filename
18
+
19
+ # Sets the line of the Sass template on which this selector was declared.
20
+ # This also sets the line for all child selectors.
21
+ #
22
+ # @param line [Fixnum]
23
+ # @return [Fixnum]
24
+ def line=(line)
25
+ members.each {|m| m.line = line}
26
+ @line = line
27
+ end
28
+
29
+ # Sets the name of the file in which this selector was declared,
30
+ # or `nil` if it was not declared in a file (e.g. on stdin).
31
+ # This also sets the filename for all child selectors.
32
+ #
33
+ # @param filename [String, nil]
34
+ # @return [String, nil]
35
+ def filename=(filename)
36
+ members.each {|m| m.filename = filename}
37
+ @filename = filename
38
+ end
39
+
40
+ # Returns a hash code for this sequence.
41
+ #
42
+ # Subclasses should define `#_hash` rather than overriding this method,
43
+ # which automatically handles memoizing the result.
44
+ #
45
+ # @return [Fixnum]
46
+ def hash
47
+ @_hash ||= _hash
48
+ end
49
+
50
+ # Checks equality between this and another object.
51
+ #
52
+ # Subclasses should define `#_eql?` rather than overriding this method,
53
+ # which handles checking class equality and hash equality.
54
+ #
55
+ # @param other [Object] The object to test equality against
56
+ # @return [Boolean] Whether or not this is equal to `other`
57
+ def eql?(other)
58
+ other.class == self.class && other.hash == self.hash && _eql?(other)
59
+ end
60
+ alias_method :==, :eql?
61
+
62
+ # Whether or not this selector sequence contains a placeholder selector.
63
+ # Checks recursively.
64
+ def has_placeholder?
65
+ @has_placeholder ||=
66
+ members.any? {|m| m.is_a?(AbstractSequence) ? m.has_placeholder? : m.is_a?(Placeholder)}
67
+ end
68
+
69
+ # Converts the selector into a string. This is the standard selector
70
+ # string, along with any SassScript interpolation that may exist.
71
+ #
72
+ # @return [String]
73
+ def to_s
74
+ to_a.map {|e| e.is_a?(Sass::Script::Node) ? "\#{#{e.to_sass}}" : e}.join
75
+ end
76
+
77
+ # Returns the specificity of the selector as an integer. The base is given
78
+ # by {Sass::Selector::SPECIFICITY_BASE}.
79
+ #
80
+ # @return [Fixnum]
81
+ def specificity
82
+ _specificity(members)
83
+ end
84
+
85
+ protected
86
+
87
+ def _specificity(arr)
88
+ spec = 0
89
+ arr.map {|m| spec += m.is_a?(String) ? 0 : m.specificity}
90
+ spec
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,92 @@
1
+ module Sass
2
+ module Selector
3
+ # A comma-separated sequence of selectors.
4
+ class CommaSequence < AbstractSequence
5
+ # The comma-separated selector sequences
6
+ # represented by this class.
7
+ #
8
+ # @return [Array<Sequence>]
9
+ attr_reader :members
10
+
11
+ # @param seqs [Array<Sequence>] See \{#members}
12
+ def initialize(seqs)
13
+ @members = seqs
14
+ end
15
+
16
+ # Resolves the {Parent} selectors within this selector
17
+ # by replacing them with the given parent selector,
18
+ # handling commas appropriately.
19
+ #
20
+ # @param super_cseq [CommaSequence] The parent selector
21
+ # @return [CommaSequence] This selector, with parent references resolved
22
+ # @raise [Sass::SyntaxError] If a parent selector is invalid
23
+ def resolve_parent_refs(super_cseq)
24
+ if super_cseq.nil?
25
+ if @members.any? do |sel|
26
+ sel.members.any? do |sel_or_op|
27
+ sel_or_op.is_a?(SimpleSequence) && sel_or_op.members.any? {|ssel| ssel.is_a?(Parent)}
28
+ end
29
+ end
30
+ raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '&'.")
31
+ end
32
+ return self
33
+ end
34
+
35
+ CommaSequence.new(
36
+ super_cseq.members.map do |super_seq|
37
+ @members.map {|seq| seq.resolve_parent_refs(super_seq)}
38
+ end.flatten)
39
+ end
40
+
41
+ # Non-destrucively extends this selector with the extensions specified in a hash
42
+ # (which should come from {Sass::Tree::Visitors::Cssize}).
43
+ #
44
+ # @todo Link this to the reference documentation on `@extend`
45
+ # when such a thing exists.
46
+ #
47
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
48
+ # Sass::Tree::Visitors::Cssize::Extend}]
49
+ # The extensions to perform on this selector
50
+ # @param parent_directives [Array<Sass::Tree::DirectiveNode>]
51
+ # The directives containing this selector.
52
+ # @return [CommaSequence] A copy of this selector,
53
+ # with extensions made according to `extends`
54
+ def do_extend(extends, parent_directives)
55
+ CommaSequence.new(members.map do |seq|
56
+ extended = seq.do_extend(extends, parent_directives)
57
+ # First Law of Extend: the result of extending a selector should
58
+ # always contain the base selector.
59
+ #
60
+ # See https://github.com/nex3/sass/issues/324.
61
+ extended.unshift seq unless seq.has_placeholder? || extended.include?(seq)
62
+ extended
63
+ end.flatten)
64
+ end
65
+
66
+ # Returns a string representation of the sequence.
67
+ # This is basically the selector string.
68
+ #
69
+ # @return [String]
70
+ def inspect
71
+ members.map {|m| m.inspect}.join(", ")
72
+ end
73
+
74
+ # @see Simple#to_a
75
+ def to_a
76
+ arr = Sass::Util.intersperse(@members.map {|m| m.to_a}, ", ").flatten
77
+ arr.delete("\n")
78
+ arr
79
+ end
80
+
81
+ private
82
+
83
+ def _hash
84
+ members.hash
85
+ end
86
+
87
+ def _eql?(other)
88
+ other.class == self.class && other.members.eql?(self.members)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,507 @@
1
+ module Sass
2
+ module Selector
3
+ # An operator-separated sequence of
4
+ # {SimpleSequence simple selector sequences}.
5
+ class Sequence < AbstractSequence
6
+ # Sets the line of the Sass template on which this selector was declared.
7
+ # This also sets the line for all child selectors.
8
+ #
9
+ # @param line [Fixnum]
10
+ # @return [Fixnum]
11
+ def line=(line)
12
+ members.each {|m| m.line = line if m.is_a?(SimpleSequence)}
13
+ line
14
+ end
15
+
16
+ # Sets the name of the file in which this selector was declared,
17
+ # or `nil` if it was not declared in a file (e.g. on stdin).
18
+ # This also sets the filename for all child selectors.
19
+ #
20
+ # @param filename [String, nil]
21
+ # @return [String, nil]
22
+ def filename=(filename)
23
+ members.each {|m| m.filename = filename if m.is_a?(SimpleSequence)}
24
+ filename
25
+ end
26
+
27
+ # The array of {SimpleSequence simple selector sequences}, operators, and
28
+ # newlines. The operators are strings such as `"+"` and `">"` representing
29
+ # the corresponding CSS operators, or interpolated SassScript. Newlines
30
+ # are also newline strings; these aren't semantically relevant, but they
31
+ # do affect formatting.
32
+ #
33
+ # @return [Array<SimpleSequence, String|Array<Sass::Tree::Node, String>>]
34
+ attr_reader :members
35
+
36
+ # @param seqs_and_ops [Array<SimpleSequence, String|Array<Sass::Tree::Node, String>>] See \{#members}
37
+ def initialize(seqs_and_ops)
38
+ @members = seqs_and_ops
39
+ end
40
+
41
+ # Resolves the {Parent} selectors within this selector
42
+ # by replacing them with the given parent selector,
43
+ # handling commas appropriately.
44
+ #
45
+ # @param super_seq [Sequence] The parent selector sequence
46
+ # @return [Sequence] This selector, with parent references resolved
47
+ # @raise [Sass::SyntaxError] If a parent selector is invalid
48
+ def resolve_parent_refs(super_seq)
49
+ members = @members.dup
50
+ nl = (members.first == "\n" && members.shift)
51
+ unless members.any? do |seq_or_op|
52
+ seq_or_op.is_a?(SimpleSequence) && seq_or_op.members.first.is_a?(Parent)
53
+ end
54
+ old_members, members = members, []
55
+ members << nl if nl
56
+ members << SimpleSequence.new([Parent.new], false)
57
+ members += old_members
58
+ end
59
+
60
+ Sequence.new(
61
+ members.map do |seq_or_op|
62
+ next seq_or_op unless seq_or_op.is_a?(SimpleSequence)
63
+ seq_or_op.resolve_parent_refs(super_seq)
64
+ end.flatten)
65
+ end
66
+
67
+ # Non-destructively extends this selector with the extensions specified in a hash
68
+ # (which should come from {Sass::Tree::Visitors::Cssize}).
69
+ #
70
+ # @overload def do_extend(extends, parent_directives)
71
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
72
+ # Sass::Tree::Visitors::Cssize::Extend}]
73
+ # The extensions to perform on this selector
74
+ # @param parent_directives [Array<Sass::Tree::DirectiveNode>]
75
+ # The directives containing this selector.
76
+ # @return [Array<Sequence>] A list of selectors generated
77
+ # by extending this selector with `extends`.
78
+ # These correspond to a {CommaSequence}'s {CommaSequence#members members array}.
79
+ # @see CommaSequence#do_extend
80
+ def do_extend(extends, parent_directives, seen = Set.new)
81
+ extended_not_expanded = members.map do |sseq_or_op|
82
+ next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)
83
+ extended = sseq_or_op.do_extend(extends, parent_directives, seen)
84
+ choices = extended.map {|seq| seq.members}
85
+ choices.unshift([sseq_or_op]) unless extended.any? {|seq| seq.superselector?(sseq_or_op)}
86
+ choices
87
+ end
88
+ weaves = Sass::Util.paths(extended_not_expanded).map {|path| weave(path)}
89
+ Sass::Util.flatten(trim(weaves), 1).map {|p| Sequence.new(p)}
90
+ end
91
+
92
+ # Returns whether or not this selector matches all elements
93
+ # that the given selector matches (as well as possibly more).
94
+ #
95
+ # @example
96
+ # (.foo).superselector?(.foo.bar) #=> true
97
+ # (.foo).superselector?(.bar) #=> false
98
+ # (.bar .foo).superselector?(.foo) #=> false
99
+ # @param sseq [SimpleSequence]
100
+ # @return [Boolean]
101
+ def superselector?(sseq)
102
+ return false unless members.size == 1
103
+ members.last.superselector?(sseq)
104
+ end
105
+
106
+ # @see Simple#to_a
107
+ def to_a
108
+ ary = @members.map {|seq_or_op| seq_or_op.is_a?(SimpleSequence) ? seq_or_op.to_a : seq_or_op}
109
+ Sass::Util.intersperse(ary, " ").flatten.compact
110
+ end
111
+
112
+ # Returns a string representation of the sequence.
113
+ # This is basically the selector string.
114
+ #
115
+ # @return [String]
116
+ def inspect
117
+ members.map {|m| m.inspect}.join(" ")
118
+ end
119
+
120
+ # Add to the {SimpleSequence#sources} sets of the child simple sequences.
121
+ # This destructively modifies this sequence's members array, but not the
122
+ # child simple sequences.
123
+ #
124
+ # @param sources [Set<Sequence>]
125
+ def add_sources!(sources)
126
+ members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m}
127
+ end
128
+
129
+ private
130
+
131
+ # Conceptually, this expands "parenthesized selectors".
132
+ # That is, if we have `.A .B {@extend .C}` and `.D .C {...}`,
133
+ # this conceptually expands into `.D .C, .D (.A .B)`,
134
+ # and this function translates `.D (.A .B)` into `.D .A .B, .A.D .B, .D .A .B`.
135
+ #
136
+ # @param path [Array<Array<SimpleSequence or String>>] A list of parenthesized selector groups.
137
+ # @return [Array<Array<SimpleSequence or String>>] A list of fully-expanded selectors.
138
+ def weave(path)
139
+ # This function works by moving through the selector path left-to-right,
140
+ # building all possible prefixes simultaneously. These prefixes are
141
+ # `befores`, while the remaining parenthesized suffixes is `afters`.
142
+ befores = [[]]
143
+ afters = path.dup
144
+
145
+ until afters.empty?
146
+ current = afters.shift.dup
147
+ last_current = [current.pop]
148
+ befores = Sass::Util.flatten(befores.map do |before|
149
+ next [] unless sub = subweave(before, current)
150
+ sub.map {|seqs| seqs + last_current}
151
+ end, 1)
152
+ end
153
+ return befores
154
+ end
155
+
156
+ # This interweaves two lists of selectors,
157
+ # returning all possible orderings of them (including using unification)
158
+ # that maintain the relative ordering of the input arrays.
159
+ #
160
+ # For example, given `.foo .bar` and `.baz .bang`,
161
+ # this would return `.foo .bar .baz .bang`, `.foo .bar.baz .bang`,
162
+ # `.foo .baz .bar .bang`, `.foo .baz .bar.bang`, `.foo .baz .bang .bar`,
163
+ # and so on until `.baz .bang .foo .bar`.
164
+ #
165
+ # Semantically, for selectors A and B, this returns all selectors `AB_i`
166
+ # such that the union over all i of elements matched by `AB_i X` is
167
+ # identical to the intersection of all elements matched by `A X` and all
168
+ # elements matched by `B X`. Some `AB_i` are elided to reduce the size of
169
+ # the output.
170
+ #
171
+ # @param seq1 [Array<SimpleSequence or String>]
172
+ # @param seq2 [Array<SimpleSequence or String>]
173
+ # @return [Array<Array<SimpleSequence or String>>]
174
+ def subweave(seq1, seq2)
175
+ return [seq2] if seq1.empty?
176
+ return [seq1] if seq2.empty?
177
+
178
+ seq1, seq2 = seq1.dup, seq2.dup
179
+ return unless init = merge_initial_ops(seq1, seq2)
180
+ return unless fin = merge_final_ops(seq1, seq2)
181
+ seq1 = group_selectors(seq1)
182
+ seq2 = group_selectors(seq2)
183
+ lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2|
184
+ next s1 if s1 == s2
185
+ next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)
186
+ next s2 if parent_superselector?(s1, s2)
187
+ next s1 if parent_superselector?(s2, s1)
188
+ end
189
+
190
+ diff = [[init]]
191
+ until lcs.empty?
192
+ diff << chunks(seq1, seq2) {|s| parent_superselector?(s.first, lcs.first)} << [lcs.shift]
193
+ seq1.shift
194
+ seq2.shift
195
+ end
196
+ diff << chunks(seq1, seq2) {|s| s.empty?}
197
+ diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]}
198
+ diff.reject! {|c| c.empty?}
199
+
200
+ Sass::Util.paths(diff).map {|p| p.flatten}.reject {|p| path_has_two_subjects?(p)}
201
+ end
202
+
203
+ # Extracts initial selector combinators (`"+"`, `">"`, `"~"`, and `"\n"`)
204
+ # from two sequences and merges them together into a single array of
205
+ # selector combinators.
206
+ #
207
+ # @param seq1 [Array<SimpleSequence or String>]
208
+ # @param seq2 [Array<SimpleSequence or String>]
209
+ # @return [Array<String>, nil] If there are no operators in the merged
210
+ # sequence, this will be the empty array. If the operators cannot be
211
+ # merged, this will be nil.
212
+ def merge_initial_ops(seq1, seq2)
213
+ ops1, ops2 = [], []
214
+ ops1 << seq1.shift while seq1.first.is_a?(String)
215
+ ops2 << seq2.shift while seq2.first.is_a?(String)
216
+
217
+ newline = false
218
+ newline ||= !!ops1.shift if ops1.first == "\n"
219
+ newline ||= !!ops2.shift if ops2.first == "\n"
220
+
221
+ # If neither sequence is a subsequence of the other, they cannot be
222
+ # merged successfully
223
+ lcs = Sass::Util.lcs(ops1, ops2)
224
+ return unless lcs == ops1 || lcs == ops2
225
+ return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)
226
+ end
227
+
228
+ # Extracts final selector combinators (`"+"`, `">"`, `"~"`) and the
229
+ # selectors to which they apply from two sequences and merges them
230
+ # together into a single array.
231
+ #
232
+ # @param seq1 [Array<SimpleSequence or String>]
233
+ # @param seq2 [Array<SimpleSequence or String>]
234
+ # @return [Array<SimpleSequence or String or
235
+ # Array<Array<SimpleSequence or String>>]
236
+ # If there are no trailing combinators to be merged, this will be the
237
+ # empty array. If the trailing combinators cannot be merged, this will
238
+ # be nil. Otherwise, this will contained the merged selector. Array
239
+ # elements are [Sass::Util#paths]-style options; conceptually, an "or"
240
+ # of multiple selectors.
241
+ def merge_final_ops(seq1, seq2, res = [])
242
+ ops1, ops2 = [], []
243
+ ops1 << seq1.pop while seq1.last.is_a?(String)
244
+ ops2 << seq2.pop while seq2.last.is_a?(String)
245
+
246
+ # Not worth the headache of trying to preserve newlines here. The most
247
+ # important use of newlines is at the beginning of the selector to wrap
248
+ # across lines anyway.
249
+ ops1.reject! {|o| o == "\n"}
250
+ ops2.reject! {|o| o == "\n"}
251
+
252
+ return res if ops1.empty? && ops2.empty?
253
+ if ops1.size > 1 || ops2.size > 1
254
+ # If there are multiple operators, something hacky's going on. If one
255
+ # is a supersequence of the other, use that, otherwise give up.
256
+ lcs = Sass::Util.lcs(ops1, ops2)
257
+ return unless lcs == ops1 || lcs == ops2
258
+ res.unshift(*(ops1.size > ops2.size ? ops1 : ops2).reverse)
259
+ return res
260
+ end
261
+
262
+ # This code looks complicated, but it's actually just a bunch of special
263
+ # cases for interactions between different combinators.
264
+ op1, op2 = ops1.first, ops2.first
265
+ if op1 && op2
266
+ sel1 = seq1.pop
267
+ sel2 = seq2.pop
268
+ if op1 == '~' && op2 == '~'
269
+ if sel1.superselector?(sel2)
270
+ res.unshift sel2, '~'
271
+ elsif sel2.superselector?(sel1)
272
+ res.unshift sel1, '~'
273
+ else
274
+ merged = sel1.unify(sel2.members, sel2.subject?)
275
+ res.unshift [
276
+ [sel1, '~', sel2, '~'],
277
+ [sel2, '~', sel1, '~'],
278
+ ([merged, '~'] if merged)
279
+ ].compact
280
+ end
281
+ elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~')
282
+ if op1 == '~'
283
+ tilde_sel, plus_sel = sel1, sel2
284
+ else
285
+ tilde_sel, plus_sel = sel2, sel1
286
+ end
287
+
288
+ if tilde_sel.superselector?(plus_sel)
289
+ res.unshift plus_sel, '+'
290
+ else
291
+ merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?)
292
+ res.unshift [
293
+ [tilde_sel, '~', plus_sel, '+'],
294
+ ([merged, '+'] if merged)
295
+ ].compact
296
+ end
297
+ elsif op1 == '>' && %w[~ +].include?(op2)
298
+ res.unshift sel2, op2
299
+ seq1.push sel1, op1
300
+ elsif op2 == '>' && %w[~ +].include?(op1)
301
+ res.unshift sel1, op1
302
+ seq2.push sel2, op2
303
+ elsif op1 == op2
304
+ return unless merged = sel1.unify(sel2.members, sel2.subject?)
305
+ res.unshift merged, op1
306
+ else
307
+ # Unknown selector combinators can't be unified
308
+ return
309
+ end
310
+ return merge_final_ops(seq1, seq2, res)
311
+ elsif op1
312
+ seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last)
313
+ res.unshift seq1.pop, op1
314
+ return merge_final_ops(seq1, seq2, res)
315
+ else # op2
316
+ seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last)
317
+ res.unshift seq2.pop, op2
318
+ return merge_final_ops(seq1, seq2, res)
319
+ end
320
+ end
321
+
322
+ # Takes initial subsequences of `seq1` and `seq2` and returns all
323
+ # orderings of those subsequences. The initial subsequences are determined
324
+ # by a block.
325
+ #
326
+ # Destructively removes the initial subsequences of `seq1` and `seq2`.
327
+ #
328
+ # For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|`
329
+ # denoting the boundary of the initial subsequence), this would return
330
+ # `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and
331
+ # `(3 4 5)`.
332
+ #
333
+ # @param seq1 [Array]
334
+ # @param seq2 [Array]
335
+ # @yield [a] Used to determine when to cut off the initial subsequences.
336
+ # Called repeatedly for each sequence until it returns true.
337
+ # @yieldparam a [Array] A final subsequence of one input sequence after
338
+ # cutting off some initial subsequence.
339
+ # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence
340
+ # here.
341
+ # @return [Array<Array>] All possible orderings of the initial subsequences.
342
+ def chunks(seq1, seq2)
343
+ chunk1 = []
344
+ chunk1 << seq1.shift until yield seq1
345
+ chunk2 = []
346
+ chunk2 << seq2.shift until yield seq2
347
+ return [] if chunk1.empty? && chunk2.empty?
348
+ return [chunk2] if chunk1.empty?
349
+ return [chunk1] if chunk2.empty?
350
+ [chunk1 + chunk2, chunk2 + chunk1]
351
+ end
352
+
353
+ # Groups a sequence into subsequences. The subsequences are determined by
354
+ # strings; adjacent non-string elements will be put into separate groups,
355
+ # but any element adjacent to a string will be grouped with that string.
356
+ #
357
+ # For example, `(A B "C" D E "F" G "H" "I" J)` will become `[(A) (B "C" D)
358
+ # (E "F" G "H" "I" J)]`.
359
+ #
360
+ # @param seq [Array]
361
+ # @return [Array<Array>]
362
+ def group_selectors(seq)
363
+ newseq = []
364
+ tail = seq.dup
365
+ until tail.empty?
366
+ head = []
367
+ begin
368
+ head << tail.shift
369
+ end while !tail.empty? && head.last.is_a?(String) || tail.first.is_a?(String)
370
+ newseq << head
371
+ end
372
+ return newseq
373
+ end
374
+
375
+ # Given two selector sequences, returns whether `seq1` is a
376
+ # superselector of `seq2`; that is, whether `seq1` matches every
377
+ # element `seq2` matches.
378
+ #
379
+ # @param seq1 [Array<SimpleSequence or String>]
380
+ # @param seq2 [Array<SimpleSequence or String>]
381
+ # @return [Boolean]
382
+ def _superselector?(seq1, seq2)
383
+ seq1 = seq1.reject {|e| e == "\n"}
384
+ seq2 = seq2.reject {|e| e == "\n"}
385
+ # Selectors with leading or trailing operators are neither
386
+ # superselectors nor subselectors.
387
+ return if seq1.last.is_a?(String) || seq2.last.is_a?(String) ||
388
+ seq1.first.is_a?(String) || seq2.first.is_a?(String)
389
+ # More complex selectors are never superselectors of less complex ones
390
+ return if seq1.size > seq2.size
391
+ return seq1.first.superselector?(seq2.last) if seq1.size == 1
392
+
393
+ _, si = Sass::Util.enum_with_index(seq2).find do |e, i|
394
+ return if i == seq2.size - 1
395
+ next if e.is_a?(String)
396
+ seq1.first.superselector?(e)
397
+ end
398
+ return unless si
399
+
400
+ if seq1[1].is_a?(String)
401
+ return unless seq2[si+1].is_a?(String)
402
+ # .foo ~ .bar is a superselector of .foo + .bar
403
+ return unless seq1[1] == "~" ? seq2[si+1] != ">" : seq1[1] == seq2[si+1]
404
+ return _superselector?(seq1[2..-1], seq2[si+2..-1])
405
+ elsif seq2[si+1].is_a?(String)
406
+ return unless seq2[si+1] == ">"
407
+ return _superselector?(seq1[1..-1], seq2[si+2..-1])
408
+ else
409
+ return _superselector?(seq1[1..-1], seq2[si+1..-1])
410
+ end
411
+ end
412
+
413
+ # Like \{#_superselector?}, but compares the selectors in the
414
+ # context of parent selectors, as though they shared an implicit
415
+ # base simple selector. For example, `B` is not normally a
416
+ # superselector of `B A`, since it doesn't match `A` elements.
417
+ # However, it is a parent superselector, since `B X` is a
418
+ # superselector of `B A X`.
419
+ #
420
+ # @param seq1 [Array<SimpleSequence or String>]
421
+ # @param seq2 [Array<SimpleSequence or String>]
422
+ # @return [Boolean]
423
+ def parent_superselector?(seq1, seq2)
424
+ base = Sass::Selector::SimpleSequence.new([Sass::Selector::Placeholder.new('<temp>')], false)
425
+ _superselector?(seq1 + [base], seq2 + [base])
426
+ end
427
+
428
+ # Removes redundant selectors from between multiple lists of
429
+ # selectors. This takes a list of lists of selector sequences;
430
+ # each individual list is assumed to have no redundancy within
431
+ # itself. A selector is only removed if it's redundant with a
432
+ # selector in another list.
433
+ #
434
+ # "Redundant" here means that one selector is a superselector of
435
+ # the other. The more specific selector is removed.
436
+ #
437
+ # @param seqses [Array<Array<Array<SimpleSequence or String>>>]
438
+ # @return [Array<Array<Array<SimpleSequence or String>>>]
439
+ def trim(seqses)
440
+ # Avoid truly horrific quadratic behavior. TODO: I think there
441
+ # may be a way to get perfect trimming without going quadratic.
442
+ return seqses if seqses.size > 100
443
+
444
+ # Keep the results in a separate array so we can be sure we aren't
445
+ # comparing against an already-trimmed selector. This ensures that two
446
+ # identical selectors don't mutually trim one another.
447
+ result = seqses.dup
448
+
449
+ # This is n^2 on the sequences, but only comparing between
450
+ # separate sequences should limit the quadratic behavior.
451
+ seqses.each_with_index do |seqs1, i|
452
+ result[i] = seqs1.reject do |seq1|
453
+ min_spec = _sources(seq1).map {|seq| seq.specificity}.min || 0
454
+ result.any? do |seqs2|
455
+ next if seqs1.equal?(seqs2)
456
+ # Second Law of Extend: the specificity of a generated selector
457
+ # should never be less than the specificity of the extending
458
+ # selector.
459
+ #
460
+ # See https://github.com/nex3/sass/issues/324.
461
+ seqs2.any? {|seq2| _specificity(seq2) >= min_spec && _superselector?(seq2, seq1)}
462
+ end
463
+ end
464
+ end
465
+ result
466
+ end
467
+
468
+ def _hash
469
+ members.reject {|m| m == "\n"}.hash
470
+ end
471
+
472
+ def _eql?(other)
473
+ other.members.reject {|m| m == "\n"}.eql?(self.members.reject {|m| m == "\n"})
474
+ end
475
+
476
+ private
477
+
478
+ def path_has_two_subjects?(path)
479
+ subject = false
480
+ path.each do |sseq_or_op|
481
+ next unless sseq_or_op.is_a?(SimpleSequence)
482
+ next unless sseq_or_op.subject?
483
+ return true if subject
484
+ subject = true
485
+ end
486
+ false
487
+ end
488
+
489
+ def _sources(seq)
490
+ s = Set.new
491
+ seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)}
492
+ s
493
+ end
494
+
495
+ def extended_not_expanded_to_s(extended_not_expanded)
496
+ extended_not_expanded.map do |choices|
497
+ choices = choices.map do |sel|
498
+ next sel.first.to_s if sel.size == 1
499
+ "#{sel.join ' '}"
500
+ end
501
+ next choices.first if choices.size == 1 && !choices.include?(' ')
502
+ "(#{choices.join ', '})"
503
+ end.join ' '
504
+ end
505
+ end
506
+ end
507
+ end