sass 3.1.0 → 3.3.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 (260) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING +1 -1
  3. data/MIT-LICENSE +2 -2
  4. data/README.md +29 -17
  5. data/Rakefile +43 -9
  6. data/VERSION +1 -1
  7. data/VERSION_DATE +1 -0
  8. data/VERSION_NAME +1 -1
  9. data/bin/sass +6 -1
  10. data/bin/sass-convert +6 -1
  11. data/bin/scss +6 -1
  12. data/ext/mkrf_conf.rb +27 -0
  13. data/lib/sass/cache_stores/base.rb +7 -3
  14. data/lib/sass/cache_stores/chain.rb +3 -2
  15. data/lib/sass/cache_stores/filesystem.rb +5 -7
  16. data/lib/sass/cache_stores/memory.rb +1 -1
  17. data/lib/sass/cache_stores/null.rb +2 -2
  18. data/lib/sass/callbacks.rb +2 -1
  19. data/lib/sass/css.rb +168 -53
  20. data/lib/sass/engine.rb +502 -174
  21. data/lib/sass/environment.rb +151 -111
  22. data/lib/sass/error.rb +7 -7
  23. data/lib/sass/exec.rb +176 -60
  24. data/lib/sass/features.rb +40 -0
  25. data/lib/sass/importers/base.rb +46 -7
  26. data/lib/sass/importers/deprecated_path.rb +51 -0
  27. data/lib/sass/importers/filesystem.rb +113 -30
  28. data/lib/sass/importers.rb +1 -0
  29. data/lib/sass/logger/base.rb +30 -0
  30. data/lib/sass/logger/log_level.rb +45 -0
  31. data/lib/sass/logger.rb +12 -0
  32. data/lib/sass/media.rb +213 -0
  33. data/lib/sass/plugin/compiler.rb +194 -104
  34. data/lib/sass/plugin/configuration.rb +18 -25
  35. data/lib/sass/plugin/merb.rb +1 -1
  36. data/lib/sass/plugin/staleness_checker.rb +37 -11
  37. data/lib/sass/plugin.rb +10 -13
  38. data/lib/sass/railtie.rb +2 -1
  39. data/lib/sass/repl.rb +5 -6
  40. data/lib/sass/script/css_lexer.rb +8 -4
  41. data/lib/sass/script/css_parser.rb +5 -2
  42. data/lib/sass/script/functions.rb +1547 -618
  43. data/lib/sass/script/lexer.rb +122 -72
  44. data/lib/sass/script/parser.rb +304 -135
  45. data/lib/sass/script/tree/funcall.rb +306 -0
  46. data/lib/sass/script/{interpolation.rb → tree/interpolation.rb} +43 -13
  47. data/lib/sass/script/tree/list_literal.rb +77 -0
  48. data/lib/sass/script/tree/literal.rb +45 -0
  49. data/lib/sass/script/tree/map_literal.rb +64 -0
  50. data/lib/sass/script/{node.rb → tree/node.rb} +30 -12
  51. data/lib/sass/script/{operation.rb → tree/operation.rb} +33 -21
  52. data/lib/sass/script/{string_interpolation.rb → tree/string_interpolation.rb} +14 -4
  53. data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +21 -9
  54. data/lib/sass/script/tree/variable.rb +57 -0
  55. data/lib/sass/script/tree.rb +15 -0
  56. data/lib/sass/script/value/arg_list.rb +36 -0
  57. data/lib/sass/script/value/base.rb +238 -0
  58. data/lib/sass/script/value/bool.rb +40 -0
  59. data/lib/sass/script/{color.rb → value/color.rb} +256 -74
  60. data/lib/sass/script/value/deprecated_false.rb +55 -0
  61. data/lib/sass/script/value/helpers.rb +155 -0
  62. data/lib/sass/script/value/list.rb +128 -0
  63. data/lib/sass/script/value/map.rb +70 -0
  64. data/lib/sass/script/value/null.rb +49 -0
  65. data/lib/sass/script/{number.rb → value/number.rb} +115 -62
  66. data/lib/sass/script/{string.rb → value/string.rb} +9 -11
  67. data/lib/sass/script/value.rb +12 -0
  68. data/lib/sass/script.rb +35 -9
  69. data/lib/sass/scss/css_parser.rb +2 -12
  70. data/lib/sass/scss/parser.rb +657 -230
  71. data/lib/sass/scss/rx.rb +17 -12
  72. data/lib/sass/scss/static_parser.rb +37 -6
  73. data/lib/sass/scss.rb +0 -1
  74. data/lib/sass/selector/abstract_sequence.rb +35 -3
  75. data/lib/sass/selector/comma_sequence.rb +29 -14
  76. data/lib/sass/selector/sequence.rb +371 -74
  77. data/lib/sass/selector/simple.rb +28 -13
  78. data/lib/sass/selector/simple_sequence.rb +163 -36
  79. data/lib/sass/selector.rb +138 -36
  80. data/lib/sass/shared.rb +3 -5
  81. data/lib/sass/source/map.rb +196 -0
  82. data/lib/sass/source/position.rb +39 -0
  83. data/lib/sass/source/range.rb +41 -0
  84. data/lib/sass/stack.rb +126 -0
  85. data/lib/sass/supports.rb +228 -0
  86. data/lib/sass/tree/at_root_node.rb +82 -0
  87. data/lib/sass/tree/comment_node.rb +34 -29
  88. data/lib/sass/tree/content_node.rb +9 -0
  89. data/lib/sass/tree/css_import_node.rb +60 -0
  90. data/lib/sass/tree/debug_node.rb +3 -3
  91. data/lib/sass/tree/directive_node.rb +33 -3
  92. data/lib/sass/tree/each_node.rb +9 -9
  93. data/lib/sass/tree/extend_node.rb +20 -6
  94. data/lib/sass/tree/for_node.rb +6 -6
  95. data/lib/sass/tree/function_node.rb +12 -4
  96. data/lib/sass/tree/if_node.rb +2 -15
  97. data/lib/sass/tree/import_node.rb +11 -5
  98. data/lib/sass/tree/media_node.rb +27 -11
  99. data/lib/sass/tree/mixin_def_node.rb +15 -4
  100. data/lib/sass/tree/mixin_node.rb +27 -7
  101. data/lib/sass/tree/node.rb +69 -35
  102. data/lib/sass/tree/prop_node.rb +47 -31
  103. data/lib/sass/tree/return_node.rb +4 -3
  104. data/lib/sass/tree/root_node.rb +20 -4
  105. data/lib/sass/tree/rule_node.rb +37 -26
  106. data/lib/sass/tree/supports_node.rb +38 -0
  107. data/lib/sass/tree/trace_node.rb +33 -0
  108. data/lib/sass/tree/variable_node.rb +10 -4
  109. data/lib/sass/tree/visitors/base.rb +5 -8
  110. data/lib/sass/tree/visitors/check_nesting.rb +67 -52
  111. data/lib/sass/tree/visitors/convert.rb +134 -53
  112. data/lib/sass/tree/visitors/cssize.rb +245 -51
  113. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  114. data/lib/sass/tree/visitors/extend.rb +68 -0
  115. data/lib/sass/tree/visitors/perform.rb +331 -105
  116. data/lib/sass/tree/visitors/set_options.rb +125 -0
  117. data/lib/sass/tree/visitors/to_css.rb +259 -95
  118. data/lib/sass/tree/warn_node.rb +3 -3
  119. data/lib/sass/tree/while_node.rb +3 -3
  120. data/lib/sass/util/cross_platform_random.rb +19 -0
  121. data/lib/sass/util/multibyte_string_scanner.rb +157 -0
  122. data/lib/sass/util/normalized_map.rb +130 -0
  123. data/lib/sass/util/ordered_hash.rb +192 -0
  124. data/lib/sass/util/subset_map.rb +11 -2
  125. data/lib/sass/util/test.rb +9 -0
  126. data/lib/sass/util.rb +565 -39
  127. data/lib/sass/version.rb +27 -15
  128. data/lib/sass.rb +39 -4
  129. data/test/sass/cache_test.rb +15 -0
  130. data/test/sass/compiler_test.rb +223 -0
  131. data/test/sass/conversion_test.rb +901 -107
  132. data/test/sass/css2sass_test.rb +94 -0
  133. data/test/sass/engine_test.rb +1059 -164
  134. data/test/sass/exec_test.rb +86 -0
  135. data/test/sass/extend_test.rb +933 -837
  136. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  137. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  138. data/test/sass/functions_test.rb +995 -136
  139. data/test/sass/importer_test.rb +338 -18
  140. data/test/sass/logger_test.rb +58 -0
  141. data/test/sass/more_results/more_import.css +2 -2
  142. data/test/sass/plugin_test.rb +114 -30
  143. data/test/sass/results/cached_import_option.css +3 -0
  144. data/test/sass/results/filename_fn.css +3 -0
  145. data/test/sass/results/import.css +2 -2
  146. data/test/sass/results/import_charset.css +1 -0
  147. data/test/sass/results/import_charset_1_8.css +1 -0
  148. data/test/sass/results/import_charset_ibm866.css +1 -0
  149. data/test/sass/results/import_content.css +1 -0
  150. data/test/sass/results/script.css +1 -1
  151. data/test/sass/results/scss_import.css +2 -2
  152. data/test/sass/results/units.css +2 -2
  153. data/test/sass/script_conversion_test.rb +43 -1
  154. data/test/sass/script_test.rb +380 -36
  155. data/test/sass/scss/css_test.rb +257 -75
  156. data/test/sass/scss/scss_test.rb +2322 -110
  157. data/test/sass/source_map_test.rb +887 -0
  158. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  159. data/test/sass/templates/_double_import_loop2.sass +1 -0
  160. data/test/sass/templates/_filename_fn_import.scss +11 -0
  161. data/test/sass/templates/_imported_content.sass +3 -0
  162. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  163. data/test/sass/templates/bork5.sass +3 -0
  164. data/test/sass/templates/cached_import_option.scss +3 -0
  165. data/test/sass/templates/double_import_loop1.sass +1 -0
  166. data/test/sass/templates/filename_fn.scss +18 -0
  167. data/test/sass/templates/import_charset.sass +2 -0
  168. data/test/sass/templates/import_charset_1_8.sass +2 -0
  169. data/test/sass/templates/import_charset_ibm866.sass +2 -0
  170. data/test/sass/templates/import_content.sass +4 -0
  171. data/test/sass/templates/same_name_different_ext.sass +2 -0
  172. data/test/sass/templates/same_name_different_ext.scss +1 -0
  173. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  174. data/test/sass/templates/single_import_loop.sass +1 -0
  175. data/test/sass/templates/subdir/import_up1.scss +1 -0
  176. data/test/sass/templates/subdir/import_up2.scss +1 -0
  177. data/test/sass/test_helper.rb +1 -1
  178. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  179. data/test/sass/util/normalized_map_test.rb +51 -0
  180. data/test/sass/util_test.rb +183 -0
  181. data/test/sass/value_helpers_test.rb +181 -0
  182. data/test/test_helper.rb +45 -5
  183. data/vendor/listen/CHANGELOG.md +228 -0
  184. data/vendor/listen/CONTRIBUTING.md +38 -0
  185. data/vendor/listen/Gemfile +30 -0
  186. data/vendor/listen/Guardfile +8 -0
  187. data/vendor/{fssm → listen}/LICENSE +1 -1
  188. data/vendor/listen/README.md +315 -0
  189. data/vendor/listen/Rakefile +47 -0
  190. data/vendor/listen/Vagrantfile +96 -0
  191. data/vendor/listen/lib/listen/adapter.rb +214 -0
  192. data/vendor/listen/lib/listen/adapters/bsd.rb +112 -0
  193. data/vendor/listen/lib/listen/adapters/darwin.rb +85 -0
  194. data/vendor/listen/lib/listen/adapters/linux.rb +113 -0
  195. data/vendor/listen/lib/listen/adapters/polling.rb +67 -0
  196. data/vendor/listen/lib/listen/adapters/windows.rb +87 -0
  197. data/vendor/listen/lib/listen/dependency_manager.rb +126 -0
  198. data/vendor/listen/lib/listen/directory_record.rb +371 -0
  199. data/vendor/listen/lib/listen/listener.rb +225 -0
  200. data/vendor/listen/lib/listen/multi_listener.rb +143 -0
  201. data/vendor/listen/lib/listen/turnstile.rb +28 -0
  202. data/vendor/listen/lib/listen/version.rb +3 -0
  203. data/vendor/listen/lib/listen.rb +40 -0
  204. data/vendor/listen/listen.gemspec +22 -0
  205. data/vendor/listen/spec/listen/adapter_spec.rb +183 -0
  206. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +36 -0
  207. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +37 -0
  208. data/vendor/listen/spec/listen/adapters/linux_spec.rb +47 -0
  209. data/vendor/listen/spec/listen/adapters/polling_spec.rb +68 -0
  210. data/vendor/listen/spec/listen/adapters/windows_spec.rb +30 -0
  211. data/vendor/listen/spec/listen/dependency_manager_spec.rb +107 -0
  212. data/vendor/listen/spec/listen/directory_record_spec.rb +1225 -0
  213. data/vendor/listen/spec/listen/listener_spec.rb +169 -0
  214. data/vendor/listen/spec/listen/multi_listener_spec.rb +174 -0
  215. data/vendor/listen/spec/listen/turnstile_spec.rb +56 -0
  216. data/vendor/listen/spec/listen_spec.rb +73 -0
  217. data/vendor/listen/spec/spec_helper.rb +21 -0
  218. data/vendor/listen/spec/support/adapter_helper.rb +629 -0
  219. data/vendor/listen/spec/support/directory_record_helper.rb +55 -0
  220. data/vendor/listen/spec/support/fixtures_helper.rb +29 -0
  221. data/vendor/listen/spec/support/listeners_helper.rb +156 -0
  222. data/vendor/listen/spec/support/platform_helper.rb +15 -0
  223. metadata +344 -271
  224. data/lib/sass/less.rb +0 -382
  225. data/lib/sass/script/bool.rb +0 -18
  226. data/lib/sass/script/funcall.rb +0 -162
  227. data/lib/sass/script/list.rb +0 -76
  228. data/lib/sass/script/literal.rb +0 -245
  229. data/lib/sass/script/variable.rb +0 -54
  230. data/lib/sass/scss/sass_parser.rb +0 -11
  231. data/test/sass/less_conversion_test.rb +0 -653
  232. data/vendor/fssm/README.markdown +0 -55
  233. data/vendor/fssm/Rakefile +0 -59
  234. data/vendor/fssm/VERSION.yml +0 -5
  235. data/vendor/fssm/example.rb +0 -9
  236. data/vendor/fssm/fssm.gemspec +0 -77
  237. data/vendor/fssm/lib/fssm/backends/fsevents.rb +0 -36
  238. data/vendor/fssm/lib/fssm/backends/inotify.rb +0 -26
  239. data/vendor/fssm/lib/fssm/backends/polling.rb +0 -25
  240. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +0 -131
  241. data/vendor/fssm/lib/fssm/monitor.rb +0 -26
  242. data/vendor/fssm/lib/fssm/path.rb +0 -91
  243. data/vendor/fssm/lib/fssm/pathname.rb +0 -502
  244. data/vendor/fssm/lib/fssm/state/directory.rb +0 -57
  245. data/vendor/fssm/lib/fssm/state/file.rb +0 -24
  246. data/vendor/fssm/lib/fssm/support.rb +0 -63
  247. data/vendor/fssm/lib/fssm/tree.rb +0 -176
  248. data/vendor/fssm/lib/fssm.rb +0 -33
  249. data/vendor/fssm/profile/prof-cache.rb +0 -40
  250. data/vendor/fssm/profile/prof-fssm-pathname.html +0 -1231
  251. data/vendor/fssm/profile/prof-pathname.rb +0 -68
  252. data/vendor/fssm/profile/prof-plain-pathname.html +0 -988
  253. data/vendor/fssm/profile/prof.html +0 -2379
  254. data/vendor/fssm/spec/path_spec.rb +0 -75
  255. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  256. data/vendor/fssm/spec/root/file.css +0 -0
  257. data/vendor/fssm/spec/root/file.rb +0 -0
  258. data/vendor/fssm/spec/root/file.yml +0 -0
  259. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  260. data/vendor/fssm/spec/spec_helper.rb +0 -14
@@ -24,17 +24,17 @@ module Sass
24
24
  filename
25
25
  end
26
26
 
27
- # The array of {SimpleSequence simple selector sequences}, operators, and newlines.
28
- # The operators are strings such as `"+"` and `">"`
29
- # representing the corresponding CSS operators.
30
- # Newlines are also newline strings;
31
- # these aren't semantically relevant,
32
- # but they do affect formatting.
33
- #
34
- # @return [Array<SimpleSequence, String>]
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>>]
35
34
  attr_reader :members
36
35
 
37
- # @param seqs_and_ops [Array<SimpleSequence, String>] See \{#members}
36
+ # @param seqs_and_ops [Array<SimpleSequence, String|Array<Sass::Tree::Node, String>>]
37
+ # See \{#members}
38
38
  def initialize(seqs_and_ops)
39
39
  @members = seqs_and_ops
40
40
  end
@@ -43,47 +43,61 @@ module Sass
43
43
  # by replacing them with the given parent selector,
44
44
  # handling commas appropriately.
45
45
  #
46
- # @param super_seq [Sequence] The parent selector sequence
47
- # @return [Sequence] This selector, with parent references resolved
46
+ # @param super_cseq [CommaSequence] The parent selector
47
+ # @param implicit_parent [Boolean] Whether the the parent
48
+ # selector should automatically be prepended to the resolved
49
+ # selector if it contains no parent refs.
50
+ # @return [CommaSequence] This selector, with parent references resolved
48
51
  # @raise [Sass::SyntaxError] If a parent selector is invalid
49
- def resolve_parent_refs(super_seq)
50
- members = @members
52
+ def resolve_parent_refs(super_cseq, implicit_parent)
53
+ members = @members.dup
51
54
  nl = (members.first == "\n" && members.shift)
52
- unless members.any? do |seq_or_op|
53
- seq_or_op.is_a?(SimpleSequence) && seq_or_op.members.first.is_a?(Parent)
54
- end
55
- members = []
55
+ contains_parent_ref = members.any? do |seq_or_op|
56
+ seq_or_op.is_a?(SimpleSequence) && seq_or_op.members.first.is_a?(Parent)
57
+ end
58
+ return CommaSequence.new([self]) if !implicit_parent && !contains_parent_ref
59
+
60
+ unless contains_parent_ref
61
+ old_members, members = members, []
56
62
  members << nl if nl
57
- members << SimpleSequence.new([Parent.new])
58
- members += @members
63
+ members << SimpleSequence.new([Parent.new], false)
64
+ members += old_members
59
65
  end
60
66
 
61
- Sequence.new(
62
- members.map do |seq_or_op|
63
- next seq_or_op unless seq_or_op.is_a?(SimpleSequence)
64
- seq_or_op.resolve_parent_refs(super_seq)
67
+ CommaSequence.new(Sass::Util.paths(members.map do |sseq_or_op|
68
+ next [sseq_or_op] unless sseq_or_op.is_a?(SimpleSequence)
69
+ sseq_or_op.resolve_parent_refs(super_cseq).members
70
+ end).map do |path|
71
+ Sequence.new(path.map do |seq_or_op|
72
+ next seq_or_op unless seq_or_op.is_a?(Sequence)
73
+ seq_or_op.members
65
74
  end.flatten)
75
+ end)
66
76
  end
67
77
 
68
78
  # Non-destructively extends this selector with the extensions specified in a hash
69
79
  # (which should come from {Sass::Tree::Visitors::Cssize}).
70
80
  #
71
- # @overload def do_extend(extends)
72
- # @param extends [Sass::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
81
+ # @overload def do_extend(extends, parent_directives)
82
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
83
+ # Sass::Tree::Visitors::Cssize::Extend}]
73
84
  # The extensions to perform on this selector
85
+ # @param parent_directives [Array<Sass::Tree::DirectiveNode>]
86
+ # The directives containing this selector.
74
87
  # @return [Array<Sequence>] A list of selectors generated
75
88
  # by extending this selector with `extends`.
76
89
  # These correspond to a {CommaSequence}'s {CommaSequence#members members array}.
77
90
  # @see CommaSequence#do_extend
78
- def do_extend(extends, seen = Set.new)
79
- paths = Sass::Util.paths(members.map do |sseq_or_op|
80
- next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)
81
- extended = sseq_or_op.do_extend(extends, seen)
82
- choices = extended.map {|seq| seq.members}
83
- choices.unshift([sseq_or_op]) unless extended.any? {|seq| seq.superselector?(sseq_or_op)}
84
- choices
85
- end)
86
- Sass::Util.flatten(paths.map {|path| weave(path)}, 1).map {|p| Sequence.new(p)}
91
+ def do_extend(extends, parent_directives, seen = Set.new)
92
+ extended_not_expanded = members.map do |sseq_or_op|
93
+ next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)
94
+ extended = sseq_or_op.do_extend(extends, parent_directives, seen)
95
+ choices = extended.map {|seq| seq.members}
96
+ choices.unshift([sseq_or_op]) unless extended.any? {|seq| seq.superselector?(sseq_or_op)}
97
+ choices
98
+ end
99
+ weaves = Sass::Util.paths(extended_not_expanded).map {|path| weave(path)}
100
+ trim(weaves).map {|p| Sequence.new(p)}
87
101
  end
88
102
 
89
103
  # Returns whether or not this selector matches all elements
@@ -102,7 +116,9 @@ module Sass
102
116
 
103
117
  # @see Simple#to_a
104
118
  def to_a
105
- ary = @members.map {|seq_or_op| seq_or_op.is_a?(SimpleSequence) ? seq_or_op.to_a : seq_or_op}
119
+ ary = @members.map do |seq_or_op|
120
+ seq_or_op.is_a?(SimpleSequence) ? seq_or_op.to_a : seq_or_op
121
+ end
106
122
  Sass::Util.intersperse(ary, " ").flatten.compact
107
123
  end
108
124
 
@@ -114,30 +130,42 @@ module Sass
114
130
  members.map {|m| m.inspect}.join(" ")
115
131
  end
116
132
 
133
+ # Add to the {SimpleSequence#sources} sets of the child simple sequences.
134
+ # This destructively modifies this sequence's members array, but not the
135
+ # child simple sequences.
136
+ #
137
+ # @param sources [Set<Sequence>]
138
+ def add_sources!(sources)
139
+ members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m}
140
+ end
141
+
117
142
  private
118
143
 
119
- # Conceptually, this expands "parenthesized selectors".
120
- # That is, if we have `.A .B {@extend .C}` and `.D .C {...}`,
121
- # this conceptually expands into `.D .C, .D (.A .B)`,
122
- # and this function translates `.D (.A .B)` into `.D .A .B, .A.D .B, .D .A .B`.
144
+ # Conceptually, this expands "parenthesized selectors". That is, if we
145
+ # have `.A .B {@extend .C}` and `.D .C {...}`, this conceptually expands
146
+ # into `.D .C, .D (.A .B)`, and this function translates `.D (.A .B)` into
147
+ # `.D .A .B, .A .D .B`. For thoroughness, `.A.D .B` would also be
148
+ # required, but including merged selectors results in exponential output
149
+ # for very little gain.
123
150
  #
124
- # @param path [Array<Array<SimpleSequence or String>>] A list of parenthesized selector groups.
151
+ # @param path [Array<Array<SimpleSequence or String>>]
152
+ # A list of parenthesized selector groups.
125
153
  # @return [Array<Array<SimpleSequence or String>>] A list of fully-expanded selectors.
126
154
  def weave(path)
127
- befores = [[]]
128
- afters = path.dup
155
+ # This function works by moving through the selector path left-to-right,
156
+ # building all possible prefixes simultaneously.
157
+ prefixes = [[]]
129
158
 
130
- until afters.empty?
131
- current = afters.shift.dup
159
+ path.each do |current|
160
+ current = current.dup
132
161
  last_current = [current.pop]
133
- while !current.empty? && last_current.first.is_a?(String) || current.last.is_a?(String)
134
- last_current.unshift(current.pop)
135
- end
136
- befores = Sass::Util.flatten(befores.map do |before|
137
- subweave(before, current).map {|seqs| seqs + last_current}
138
- end, 1)
139
- return befores if afters.empty?
162
+ prefixes = Sass::Util.flatten(prefixes.map do |prefix|
163
+ sub = subweave(prefix, current)
164
+ next [] unless sub
165
+ sub.map {|seqs| seqs + last_current}
166
+ end, 1)
140
167
  end
168
+ prefixes
141
169
  end
142
170
 
143
171
  # This interweaves two lists of selectors,
@@ -149,35 +177,190 @@ module Sass
149
177
  # `.foo .baz .bar .bang`, `.foo .baz .bar.bang`, `.foo .baz .bang .bar`,
150
178
  # and so on until `.baz .bang .foo .bar`.
151
179
  #
152
- # @overload def subweave(seq1, seq2)
180
+ # Semantically, for selectors A and B, this returns all selectors `AB_i`
181
+ # such that the union over all i of elements matched by `AB_i X` is
182
+ # identical to the intersection of all elements matched by `A X` and all
183
+ # elements matched by `B X`. Some `AB_i` are elided to reduce the size of
184
+ # the output.
185
+ #
153
186
  # @param seq1 [Array<SimpleSequence or String>]
154
187
  # @param seq2 [Array<SimpleSequence or String>]
155
188
  # @return [Array<Array<SimpleSequence or String>>]
156
- def subweave(seq1, seq2, cache = {})
189
+ def subweave(seq1, seq2)
157
190
  return [seq2] if seq1.empty?
158
191
  return [seq1] if seq2.empty?
159
192
 
193
+ seq1, seq2 = seq1.dup, seq2.dup
194
+ init = merge_initial_ops(seq1, seq2)
195
+ return unless init
196
+ fin = merge_final_ops(seq1, seq2)
197
+ return unless fin
160
198
  seq1 = group_selectors(seq1)
161
199
  seq2 = group_selectors(seq2)
162
200
  lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2|
163
201
  next s1 if s1 == s2
164
202
  next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)
165
- next s2 if subweave_superselector?(s1, s2)
166
- next s1 if subweave_superselector?(s2, s1)
203
+ next s2 if parent_superselector?(s1, s2)
204
+ next s1 if parent_superselector?(s2, s1)
167
205
  end
168
206
 
169
- diff = []
207
+ diff = [[init]]
170
208
  until lcs.empty?
171
- diff << chunks(seq1, seq2) {|s| subweave_superselector?(s.first, lcs.first)} << [lcs.shift]
209
+ diff << chunks(seq1, seq2) {|s| parent_superselector?(s.first, lcs.first)} << [lcs.shift]
172
210
  seq1.shift
173
211
  seq2.shift
174
212
  end
175
213
  diff << chunks(seq1, seq2) {|s| s.empty?}
214
+ diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]}
176
215
  diff.reject! {|c| c.empty?}
177
216
 
178
- Sass::Util.paths(diff).map {|p| p.flatten}
217
+ Sass::Util.paths(diff).map {|p| p.flatten}.reject {|p| path_has_two_subjects?(p)}
218
+ end
219
+
220
+ # Extracts initial selector combinators (`"+"`, `">"`, `"~"`, and `"\n"`)
221
+ # from two sequences and merges them together into a single array of
222
+ # selector combinators.
223
+ #
224
+ # @param seq1 [Array<SimpleSequence or String>]
225
+ # @param seq2 [Array<SimpleSequence or String>]
226
+ # @return [Array<String>, nil] If there are no operators in the merged
227
+ # sequence, this will be the empty array. If the operators cannot be
228
+ # merged, this will be nil.
229
+ def merge_initial_ops(seq1, seq2)
230
+ ops1, ops2 = [], []
231
+ ops1 << seq1.shift while seq1.first.is_a?(String)
232
+ ops2 << seq2.shift while seq2.first.is_a?(String)
233
+
234
+ newline = false
235
+ newline ||= !!ops1.shift if ops1.first == "\n"
236
+ newline ||= !!ops2.shift if ops2.first == "\n"
237
+
238
+ # If neither sequence is a subsequence of the other, they cannot be
239
+ # merged successfully
240
+ lcs = Sass::Util.lcs(ops1, ops2)
241
+ return unless lcs == ops1 || lcs == ops2
242
+ (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)
179
243
  end
180
244
 
245
+ # Extracts final selector combinators (`"+"`, `">"`, `"~"`) and the
246
+ # selectors to which they apply from two sequences and merges them
247
+ # together into a single array.
248
+ #
249
+ # @param seq1 [Array<SimpleSequence or String>]
250
+ # @param seq2 [Array<SimpleSequence or String>]
251
+ # @return [Array<SimpleSequence or String or
252
+ # Array<Array<SimpleSequence or String>>]
253
+ # If there are no trailing combinators to be merged, this will be the
254
+ # empty array. If the trailing combinators cannot be merged, this will
255
+ # be nil. Otherwise, this will contained the merged selector. Array
256
+ # elements are [Sass::Util#paths]-style options; conceptually, an "or"
257
+ # of multiple selectors.
258
+ # @comment
259
+ # rubocop:disable MethodLength
260
+ def merge_final_ops(seq1, seq2, res = [])
261
+ ops1, ops2 = [], []
262
+ ops1 << seq1.pop while seq1.last.is_a?(String)
263
+ ops2 << seq2.pop while seq2.last.is_a?(String)
264
+
265
+ # Not worth the headache of trying to preserve newlines here. The most
266
+ # important use of newlines is at the beginning of the selector to wrap
267
+ # across lines anyway.
268
+ ops1.reject! {|o| o == "\n"}
269
+ ops2.reject! {|o| o == "\n"}
270
+
271
+ return res if ops1.empty? && ops2.empty?
272
+ if ops1.size > 1 || ops2.size > 1
273
+ # If there are multiple operators, something hacky's going on. If one
274
+ # is a supersequence of the other, use that, otherwise give up.
275
+ lcs = Sass::Util.lcs(ops1, ops2)
276
+ return unless lcs == ops1 || lcs == ops2
277
+ res.unshift(*(ops1.size > ops2.size ? ops1 : ops2).reverse)
278
+ return res
279
+ end
280
+
281
+ # This code looks complicated, but it's actually just a bunch of special
282
+ # cases for interactions between different combinators.
283
+ op1, op2 = ops1.first, ops2.first
284
+ if op1 && op2
285
+ sel1 = seq1.pop
286
+ sel2 = seq2.pop
287
+ if op1 == '~' && op2 == '~'
288
+ if sel1.superselector?(sel2)
289
+ res.unshift sel2, '~'
290
+ elsif sel2.superselector?(sel1)
291
+ res.unshift sel1, '~'
292
+ else
293
+ merged = sel1.unify(sel2.members, sel2.subject?)
294
+ res.unshift [
295
+ [sel1, '~', sel2, '~'],
296
+ [sel2, '~', sel1, '~'],
297
+ ([merged, '~'] if merged)
298
+ ].compact
299
+ end
300
+ elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~')
301
+ if op1 == '~'
302
+ tilde_sel, plus_sel = sel1, sel2
303
+ else
304
+ tilde_sel, plus_sel = sel2, sel1
305
+ end
306
+
307
+ if tilde_sel.superselector?(plus_sel)
308
+ res.unshift plus_sel, '+'
309
+ else
310
+ merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?)
311
+ res.unshift [
312
+ [tilde_sel, '~', plus_sel, '+'],
313
+ ([merged, '+'] if merged)
314
+ ].compact
315
+ end
316
+ elsif op1 == '>' && %w[~ +].include?(op2)
317
+ res.unshift sel2, op2
318
+ seq1.push sel1, op1
319
+ elsif op2 == '>' && %w[~ +].include?(op1)
320
+ res.unshift sel1, op1
321
+ seq2.push sel2, op2
322
+ elsif op1 == op2
323
+ merged = sel1.unify(sel2.members, sel2.subject?)
324
+ return unless merged
325
+ res.unshift merged, op1
326
+ else
327
+ # Unknown selector combinators can't be unified
328
+ return
329
+ end
330
+ return merge_final_ops(seq1, seq2, res)
331
+ elsif op1
332
+ seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last)
333
+ res.unshift seq1.pop, op1
334
+ return merge_final_ops(seq1, seq2, res)
335
+ else # op2
336
+ seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last)
337
+ res.unshift seq2.pop, op2
338
+ return merge_final_ops(seq1, seq2, res)
339
+ end
340
+ end
341
+ # @comment
342
+ # rubocop:enable MethodLength
343
+
344
+ # Takes initial subsequences of `seq1` and `seq2` and returns all
345
+ # orderings of those subsequences. The initial subsequences are determined
346
+ # by a block.
347
+ #
348
+ # Destructively removes the initial subsequences of `seq1` and `seq2`.
349
+ #
350
+ # For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|`
351
+ # denoting the boundary of the initial subsequence), this would return
352
+ # `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and
353
+ # `(3 4 5)`.
354
+ #
355
+ # @param seq1 [Array]
356
+ # @param seq2 [Array]
357
+ # @yield [a] Used to determine when to cut off the initial subsequences.
358
+ # Called repeatedly for each sequence until it returns true.
359
+ # @yieldparam a [Array] A final subsequence of one input sequence after
360
+ # cutting off some initial subsequence.
361
+ # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence
362
+ # here.
363
+ # @return [Array<Array>] All possible orderings of the initial subsequences.
181
364
  def chunks(seq1, seq2)
182
365
  chunk1 = []
183
366
  chunk1 << seq1.shift until yield seq1
@@ -189,6 +372,15 @@ module Sass
189
372
  [chunk1 + chunk2, chunk2 + chunk1]
190
373
  end
191
374
 
375
+ # Groups a sequence into subsequences. The subsequences are determined by
376
+ # strings; adjacent non-string elements will be put into separate groups,
377
+ # but any element adjacent to a string will be grouped with that string.
378
+ #
379
+ # For example, `(A B "C" D E "F" G "H" "I" J)` will become `[(A) (B "C" D)
380
+ # (E "F" G "H" "I" J)]`.
381
+ #
382
+ # @param seq [Array]
383
+ # @return [Array<Array>]
192
384
  def group_selectors(seq)
193
385
  newseq = []
194
386
  tail = seq.dup
@@ -199,34 +391,139 @@ module Sass
199
391
  end while !tail.empty? && head.last.is_a?(String) || tail.first.is_a?(String)
200
392
  newseq << head
201
393
  end
202
- return newseq
394
+ newseq
203
395
  end
204
396
 
205
- def subweave_superselector?(sseq1, sseq2)
206
- if sseq1.size > 1
207
- # More complex selectors are never superselectors of less complex ones
208
- return unless sseq2.size > 1
397
+ # Given two selector sequences, returns whether `seq1` is a
398
+ # superselector of `seq2`; that is, whether `seq1` matches every
399
+ # element `seq2` matches.
400
+ #
401
+ # @param seq1 [Array<SimpleSequence or String>]
402
+ # @param seq2 [Array<SimpleSequence or String>]
403
+ # @return [Boolean]
404
+ def _superselector?(seq1, seq2)
405
+ seq1 = seq1.reject {|e| e == "\n"}
406
+ seq2 = seq2.reject {|e| e == "\n"}
407
+ # Selectors with leading or trailing operators are neither
408
+ # superselectors nor subselectors.
409
+ return if seq1.last.is_a?(String) || seq2.last.is_a?(String) ||
410
+ seq1.first.is_a?(String) || seq2.first.is_a?(String)
411
+ # More complex selectors are never superselectors of less complex ones
412
+ return if seq1.size > seq2.size
413
+ return seq1.first.superselector?(seq2.last) if seq1.size == 1
414
+
415
+ _, si = Sass::Util.enum_with_index(seq2).find do |e, i|
416
+ return if i == seq2.size - 1
417
+ next if e.is_a?(String)
418
+ seq1.first.superselector?(e)
419
+ end
420
+ return unless si
421
+
422
+ if seq1[1].is_a?(String)
423
+ return unless seq2[si + 1].is_a?(String)
209
424
  # .foo ~ .bar is a superselector of .foo + .bar
210
- return unless sseq1[1] == "~" ? sseq2[1] != ">" : sseq2[1] == sseq1[1]
211
- return unless sseq1.first.superselector?(sseq2.first)
212
- return true if sseq1.size == 2
213
- return false if sseq2.size == 2
214
- return subweave_superselector?(sseq1[2..-1], sseq2[2..-1])
215
- elsif sseq2.size > 1
216
- return true if sseq2[1] == ">" && sseq1.first.superselector?(sseq2.first)
217
- return false if sseq2.size == 2
218
- return subweave_superselector?(sseq1, sseq2[2..-1])
425
+ return unless seq1[1] == "~" ? seq2[si + 1] != ">" : seq1[1] == seq2[si + 1]
426
+ return _superselector?(seq1[2..-1], seq2[si + 2..-1])
427
+ elsif seq2[si + 1].is_a?(String)
428
+ return unless seq2[si + 1] == ">"
429
+ return _superselector?(seq1[1..-1], seq2[si + 2..-1])
219
430
  else
220
- sseq1.first.superselector?(sseq2.first)
431
+ return _superselector?(seq1[1..-1], seq2[si + 1..-1])
221
432
  end
222
433
  end
223
434
 
435
+ # Like \{#_superselector?}, but compares the selectors in the
436
+ # context of parent selectors, as though they shared an implicit
437
+ # base simple selector. For example, `B` is not normally a
438
+ # superselector of `B A`, since it doesn't match `A` elements.
439
+ # However, it is a parent superselector, since `B X` is a
440
+ # superselector of `B A X`.
441
+ #
442
+ # @param seq1 [Array<SimpleSequence or String>]
443
+ # @param seq2 [Array<SimpleSequence or String>]
444
+ # @return [Boolean]
445
+ def parent_superselector?(seq1, seq2)
446
+ base = Sass::Selector::SimpleSequence.new([Sass::Selector::Placeholder.new('<temp>')],
447
+ false)
448
+ _superselector?(seq1 + [base], seq2 + [base])
449
+ end
450
+
451
+ # Removes redundant selectors from between multiple lists of
452
+ # selectors. This takes a list of lists of selector sequences;
453
+ # each individual list is assumed to have no redundancy within
454
+ # itself. A selector is only removed if it's redundant with a
455
+ # selector in another list.
456
+ #
457
+ # "Redundant" here means that one selector is a superselector of
458
+ # the other. The more specific selector is removed.
459
+ #
460
+ # @param seqses [Array<Array<Array<SimpleSequence or String>>>]
461
+ # @return [Array<Array<SimpleSequence or String>>]
462
+ def trim(seqses)
463
+ # Avoid truly horrific quadratic behavior. TODO: I think there
464
+ # may be a way to get perfect trimming without going quadratic.
465
+ return Sass::Util.flatten(seqses, 1) if seqses.size > 100
466
+
467
+ # Keep the results in a separate array so we can be sure we aren't
468
+ # comparing against an already-trimmed selector. This ensures that two
469
+ # identical selectors don't mutually trim one another.
470
+ result = seqses.dup
471
+
472
+ # This is n^2 on the sequences, but only comparing between
473
+ # separate sequences should limit the quadratic behavior.
474
+ seqses.each_with_index do |seqs1, i|
475
+ result[i] = seqs1.reject do |seq1|
476
+ max_spec = _sources(seq1).map {|seq| seq.specificity}.max || 0
477
+ result.any? do |seqs2|
478
+ next if seqs1.equal?(seqs2)
479
+ # Second Law of Extend: the specificity of a generated selector
480
+ # should never be less than the specificity of the extending
481
+ # selector.
482
+ #
483
+ # See https://github.com/nex3/sass/issues/324.
484
+ seqs2.any? {|seq2| _specificity(seq2) >= max_spec && _superselector?(seq2, seq1)}
485
+ end
486
+ end
487
+ end
488
+ Sass::Util.flatten(result, 1)
489
+ end
490
+
224
491
  def _hash
225
492
  members.reject {|m| m == "\n"}.hash
226
493
  end
227
494
 
228
495
  def _eql?(other)
229
- other.members.reject {|m| m == "\n"}.eql?(self.members.reject {|m| m == "\n"})
496
+ other.members.reject {|m| m == "\n"}.eql?(members.reject {|m| m == "\n"})
497
+ end
498
+
499
+ private
500
+
501
+ def path_has_two_subjects?(path)
502
+ subject = false
503
+ path.each do |sseq_or_op|
504
+ next unless sseq_or_op.is_a?(SimpleSequence)
505
+ next unless sseq_or_op.subject?
506
+ return true if subject
507
+ subject = true
508
+ end
509
+ false
510
+ end
511
+
512
+ def _sources(seq)
513
+ s = Set.new
514
+ seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)}
515
+ s
516
+ end
517
+
518
+ def extended_not_expanded_to_s(extended_not_expanded)
519
+ extended_not_expanded.map do |choices|
520
+ choices = choices.map do |sel|
521
+ next sel.first.to_s if sel.size == 1
522
+ "#{sel.join ' '}"
523
+ end
524
+ next choices.first if choices.size == 1 && !choices.include?(' ')
525
+ "(#{choices.join ', '})"
526
+ end.join ' '
230
527
  end
231
528
  end
232
529
  end
@@ -14,13 +14,12 @@ module Sass
14
14
  # @return [String, nil]
15
15
  attr_accessor :filename
16
16
 
17
- # Returns a representation of the node
18
- # as an array of strings and potentially {Sass::Script::Node}s
19
- # (if there's interpolation in the selector).
20
- # When the interpolation is resolved and the strings are joined together,
21
- # this will be the string representation of this node.
17
+ # Returns a representation of the node as an array of strings and
18
+ # potentially {Sass::Script::Tree::Node}s (if there's interpolation in the
19
+ # selector). When the interpolation is resolved and the strings are joined
20
+ # together, this will be the string representation of this node.
22
21
  #
23
- # @return [Array<String, Sass::Script::Node>]
22
+ # @return [Array<String, Sass::Script::Tree::Node>]
24
23
  def to_a
25
24
  Sass::Util.abstract(self)
26
25
  end
@@ -30,7 +29,13 @@ module Sass
30
29
  #
31
30
  # @return [String]
32
31
  def inspect
33
- to_a.map {|e| e.is_a?(Sass::Script::Node) ? "\#{#{e.to_sass}}" : e}.join
32
+ to_a.map {|e| e.is_a?(Sass::Script::Tree::Node) ? "\#{#{e.to_sass}}" : e}.join
33
+ end
34
+
35
+ # @see \{#inspect}
36
+ # @return [String]
37
+ def to_s
38
+ inspect
34
39
  end
35
40
 
36
41
  # Returns a hash code for this selector object.
@@ -41,7 +46,7 @@ module Sass
41
46
  #
42
47
  # @return [Fixnum]
43
48
  def hash
44
- @_hash ||= to_a.hash
49
+ @_hash ||= equality_key.hash
45
50
  end
46
51
 
47
52
  # Checks equality between this and another object.
@@ -53,7 +58,7 @@ module Sass
53
58
  # @param other [Object] The object to test equality against
54
59
  # @return [Boolean] Whether or not this is equal to `other`
55
60
  def eql?(other)
56
- other.class == self.class && other.hash == self.hash && other.to_a.eql?(to_a)
61
+ other.class == self.class && other.hash == hash && other.equality_key.eql?(equality_key)
57
62
  end
58
63
  alias_method :==, :eql?
59
64
 
@@ -78,17 +83,27 @@ module Sass
78
83
  return sels if sels.any? {|sel2| eql?(sel2)}
79
84
  sels_with_ix = Sass::Util.enum_with_index(sels)
80
85
  _, i =
81
- if self.is_a?(Pseudo) || self.is_a?(SelectorPseudoClass)
82
- sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) && sels.last.type == :element}
86
+ if is_a?(Pseudo) || is_a?(SelectorPseudoClass)
87
+ sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) && (sels.last.type == :element)}
83
88
  else
84
89
  sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) || sel.is_a?(SelectorPseudoClass)}
85
90
  end
86
91
  return sels + [self] unless i
87
- return sels[0...i] + [self] + sels[i..-1]
92
+ sels[0...i] + [self] + sels[i..-1]
88
93
  end
89
94
 
90
95
  protected
91
96
 
97
+ # Returns the key used for testing whether selectors are equal.
98
+ #
99
+ # This is based on \{#to\_a}, with adjacent strings merged so that
100
+ # selectors constructed in different ways are considered equivalent.
101
+ #
102
+ # @return [Array<String, Sass::Script::Tree::Node>]
103
+ def equality_key
104
+ @equality_key ||= Sass::Util.merge_adjacent_strings(to_a)
105
+ end
106
+
92
107
  # Unifies two namespaces,
93
108
  # returning a namespace that works for both of them if possible.
94
109
  #
@@ -106,7 +121,7 @@ module Sass
106
121
  return nil, false unless ns1 == ns2 || ns1.nil? || ns1 == ['*'] || ns2.nil? || ns2 == ['*']
107
122
  return ns2, true if ns1 == ['*']
108
123
  return ns1, true if ns2 == ['*']
109
- return ns1 || ns2, true
124
+ [ns1 || ns2, true]
110
125
  end
111
126
  end
112
127
  end