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
@@ -9,15 +9,18 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
9
9
 
10
10
  # Returns the immediate parent of the current node.
11
11
  # @return [Tree::Node]
12
- attr_reader :parent
12
+ def parent
13
+ @parents.last
14
+ end
13
15
 
14
16
  def initialize
17
+ @parents = []
15
18
  @extends = Sass::Util::SubsetMap.new
16
19
  end
17
20
 
18
21
  # If an exception is raised, this adds proper metadata to the backtrace.
19
22
  def visit(node)
20
- super(node.dup)
23
+ super(node)
21
24
  rescue Sass::SyntaxError => e
22
25
  e.modify_backtrace(:filename => node.filename, :line => node.line)
23
26
  raise e
@@ -26,11 +29,20 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
26
29
  # Keeps track of the current parent node.
27
30
  def visit_children(parent)
28
31
  with_parent parent do
29
- parent.children = super.flatten
32
+ parent.children = visit_children_without_parent(parent)
30
33
  parent
31
34
  end
32
35
  end
33
36
 
37
+ # Like {#visit\_children}, but doesn't set {#parent}.
38
+ #
39
+ # @param node [Sass::Tree::Node]
40
+ # @return [Array<Sass::Tree::Node>] the flattened results of
41
+ # visiting all the children of `node`
42
+ def visit_children_without_parent(node)
43
+ node.children.map {|c| visit(c)}.flatten
44
+ end
45
+
34
46
  # Runs a block of code with the current parent node
35
47
  # replaced with the given node.
36
48
  #
@@ -38,10 +50,10 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
38
50
  # @yield A block in which the parent is set to `parent`.
39
51
  # @return [Object] The return value of the block.
40
52
  def with_parent(parent)
41
- old_parent, @parent = @parent, parent
53
+ @parents.push parent
42
54
  yield
43
55
  ensure
44
- @parent = old_parent
56
+ @parents.pop
45
57
  end
46
58
 
47
59
  # In Ruby 1.8, ensures that there's only one `@charset` directive
@@ -52,12 +64,39 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
52
64
  def visit_root(node)
53
65
  yield
54
66
 
55
- # In Ruby 1.9 we can make all @charset nodes invisible
56
- # and infer the final @charset from the encoding of the final string.
57
- if Sass::Util.ruby1_8? && parent.nil?
58
- charset = node.children.find {|c| c.is_a?(Sass::Tree::CharsetNode)}
59
- node.children.reject! {|c| c.is_a?(Sass::Tree::CharsetNode)}
60
- node.children.unshift charset if charset
67
+ if parent.nil?
68
+ # In Ruby 1.9 we can make all @charset nodes invisible
69
+ # and infer the final @charset from the encoding of the final string.
70
+ if Sass::Util.ruby1_8?
71
+ charset = node.children.find {|c| c.is_a?(Sass::Tree::CharsetNode)}
72
+ node.children.reject! {|c| c.is_a?(Sass::Tree::CharsetNode)}
73
+ node.children.unshift charset if charset
74
+ end
75
+
76
+ imports_to_move = []
77
+ import_limit = nil
78
+ i = -1
79
+ node.children.reject! do |n|
80
+ i += 1
81
+ if import_limit
82
+ next false unless n.is_a?(Sass::Tree::CssImportNode)
83
+ imports_to_move << n
84
+ next true
85
+ end
86
+
87
+ if !n.is_a?(Sass::Tree::CommentNode) &&
88
+ !n.is_a?(Sass::Tree::CharsetNode) &&
89
+ !n.is_a?(Sass::Tree::CssImportNode)
90
+ import_limit = i
91
+ end
92
+
93
+ false
94
+ end
95
+
96
+ if import_limit
97
+ node.children = node.children[0...import_limit] + imports_to_move +
98
+ node.children[import_limit..-1]
99
+ end
61
100
  end
62
101
 
63
102
  return node, @extends
@@ -66,6 +105,22 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
66
105
  raise e
67
106
  end
68
107
 
108
+ # A simple struct wrapping up information about a single `@extend` instance. A
109
+ # single {ExtendNode} can have multiple Extends if either the parent node or
110
+ # the extended selector is a comma sequence.
111
+ #
112
+ # @attr extender [Sass::Selector::Sequence]
113
+ # The selector of the CSS rule containing the `@extend`.
114
+ # @attr target [Array<Sass::Selector::Simple>] The selector being `@extend`ed.
115
+ # @attr node [Sass::Tree::ExtendNode] The node that produced this extend.
116
+ # @attr directives [Array<Sass::Tree::DirectiveNode>]
117
+ # The directives containing the `@extend`.
118
+ # @attr result [Symbol]
119
+ # The result of this extend. One of `:not_found` (the target doesn't exist
120
+ # in the document), `:failed_to_unify` (the target exists but cannot be
121
+ # unified with the extender), or `:succeeded`.
122
+ Extend = Struct.new(:extender, :target, :node, :directives, :result)
123
+
69
124
  # Registers an extension in the `@extends` subset map.
70
125
  def visit_extend(node)
71
126
  node.resolved_selector.members.each do |seq|
@@ -76,15 +131,18 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
76
131
  sseq = seq.members.first
77
132
  if !sseq.is_a?(Sass::Selector::SimpleSequence)
78
133
  raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
134
+ elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)}
135
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend parent selectors")
79
136
  end
80
137
 
81
138
  sel = sseq.members
82
- parent.resolved_rules.members.each do |seq|
83
- if !seq.members.last.is_a?(Sass::Selector::SimpleSequence)
84
- raise Sass::SyntaxError.new("#{seq} can't extend: invalid selector")
139
+ parent.resolved_rules.members.each do |member|
140
+ unless member.members.last.is_a?(Sass::Selector::SimpleSequence)
141
+ raise Sass::SyntaxError.new("#{member} can't extend: invalid selector")
85
142
  end
86
143
 
87
- @extends[sel] = seq
144
+ parent_directives = @parents.select {|p| p.is_a?(Sass::Tree::DirectiveNode)}
145
+ @extends[sel] = Extend.new(member, sel, node, parent_directives, :not_found)
88
146
  end
89
147
  end
90
148
 
@@ -93,38 +151,16 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
93
151
 
94
152
  # Modifies exception backtraces to include the imported file.
95
153
  def visit_import(node)
96
- # Don't use #visit_children to avoid adding the import node to the list of parents.
97
- node.children.map {|c| visit(c)}.flatten
154
+ visit_children_without_parent(node)
98
155
  rescue Sass::SyntaxError => e
99
156
  e.modify_backtrace(:filename => node.children.first.filename)
100
157
  e.add_backtrace(:filename => node.filename, :line => node.line)
101
158
  raise e
102
159
  end
103
160
 
104
- # Bubbles the `@media` directive up through RuleNodes
105
- # and merges it with other `@media` directives.
106
- def visit_media(node)
107
- if parent.is_a?(Sass::Tree::RuleNode)
108
- new_rule = parent.dup
109
- new_rule.children = node.children
110
- node.children = with_parent(node) {Array(visit(new_rule))}
111
- # If the last child is actually the end of the group,
112
- # the parent's cssize will set it properly
113
- node.children.last.group_end = false unless node.children.empty?
114
- else
115
- yield
116
- end
117
-
118
- media = node.children.select {|c| c.is_a?(Sass::Tree::MediaNode)}
119
- node.children.reject! {|c| c.is_a?(Sass::Tree::MediaNode)}
120
- media.each {|n| n.query = "#{node.query} and #{n.query}"}
121
- (node.children.empty? ? [] : [node]) + media
122
- end
123
-
124
- # Asserts that all the mixin's children are valid in their new location.
125
- def visit_mixin(node)
126
- # Don't use #visit_children to avoid adding the mixin node to the list of parents.
127
- node.children.map {|c| visit(c)}.flatten
161
+ # Asserts that all the traced children are valid in their new location.
162
+ def visit_trace(node)
163
+ visit_children_without_parent(node)
128
164
  rescue Sass::SyntaxError => e
129
165
  e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line)
130
166
  e.add_backtrace(:filename => node.filename, :line => node.line)
@@ -150,17 +186,38 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
150
186
  result
151
187
  end
152
188
 
153
- # Resolves parent references and nested selectors,
154
- # and updates the indentation of the rule node based on the nesting level.
155
- def visit_rule(node)
156
- parent_resolved_rules = parent.is_a?(Sass::Tree::RuleNode) ? parent.resolved_rules : nil
157
- # It's possible for resolved_rules to be set if we've duplicated this node during @media bubbling
158
- node.resolved_rules ||= node.parsed_rules.resolve_parent_refs(parent_resolved_rules)
189
+ def visit_atroot(node)
190
+ # If there aren't any more directives or rules that this @at-root needs to
191
+ # exclude, we can get rid of it and just evaluate the children.
192
+ if @parents.none? {|n| node.exclude_node?(n)}
193
+ results = visit_children_without_parent(node)
194
+ results.each {|c| c.tabs += node.tabs if bubblable?(c)}
195
+ if !results.empty? && bubblable?(results.last)
196
+ results.last.group_end = node.group_end
197
+ end
198
+ return results
199
+ end
200
+
201
+ # If this @at-root excludes the immediate parent, return it as-is so that it
202
+ # can be bubbled up by the parent node.
203
+ return Bubble.new(node) if node.exclude_node?(parent)
204
+
205
+ # Otherwise, duplicate the current parent and move it into the @at-root
206
+ # node. As above, returning an @at-root node signals to the parent directive
207
+ # that it should be bubbled upwards.
208
+ bubble(node)
209
+ end
210
+
211
+ # The following directives are visible and have children. This means they need
212
+ # to be able to handle bubbling up nodes such as @at-root and @media.
159
213
 
214
+ # Updates the indentation of the rule node based on the nesting
215
+ # level. The selectors were resolved in {Perform}.
216
+ def visit_rule(node)
160
217
  yield
161
218
 
162
- rules = node.children.select {|c| c.is_a?(Sass::Tree::RuleNode) || c.is_a?(Sass::Tree::MediaNode)}
163
- props = node.children.reject {|c| c.is_a?(Sass::Tree::RuleNode) || c.is_a?(Sass::Tree::MediaNode) || c.invisible?}
219
+ rules = node.children.select {|c| bubblable?(c)}
220
+ props = node.children.reject {|c| bubblable?(c) || c.invisible?}
164
221
 
165
222
  unless props.empty?
166
223
  node.children = props
@@ -168,8 +225,145 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
168
225
  rules.unshift(node)
169
226
  end
170
227
 
171
- rules.last.group_end = true unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty?
172
-
228
+ rules = debubble(rules)
229
+ unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty? || !bubblable?(rules.last)
230
+ rules.last.group_end = true
231
+ end
173
232
  rules
174
233
  end
234
+
235
+ # Bubbles a directive up through RuleNodes.
236
+ def visit_directive(node)
237
+ return node unless node.has_children
238
+ return bubble(node) if parent.is_a?(Sass::Tree::RuleNode)
239
+
240
+ yield
241
+
242
+ # Since we don't know if the mere presence of an unknown directive may be
243
+ # important, we should keep an empty version around even if all the contents
244
+ # are removed via @at-root. However, if the contents are just bubbled out,
245
+ # we don't need to do so.
246
+ directive_exists = node.children.any? do |child|
247
+ next true unless child.is_a?(Bubble)
248
+ next false unless child.node.is_a?(Sass::Tree::DirectiveNode)
249
+ child.node.resolved_value == node.resolved_value
250
+ end
251
+
252
+ if directive_exists
253
+ []
254
+ else
255
+ empty_node = node.dup
256
+ empty_node.children = []
257
+ [empty_node]
258
+ end + debubble(node.children, node)
259
+ end
260
+
261
+ # Bubbles the `@media` directive up through RuleNodes
262
+ # and merges it with other `@media` directives.
263
+ def visit_media(node)
264
+ return bubble(node) if parent.is_a?(Sass::Tree::RuleNode)
265
+ return Bubble.new(node) if parent.is_a?(Sass::Tree::MediaNode)
266
+
267
+ yield
268
+
269
+ debubble(node.children, node) do |child|
270
+ next child unless child.is_a?(Sass::Tree::MediaNode)
271
+ # Copies of `node` can be bubbled, and we don't want to merge it with its
272
+ # own query.
273
+ next child if child.resolved_query == node.resolved_query
274
+ next child if child.resolved_query = child.resolved_query.merge(node.resolved_query)
275
+ end
276
+ end
277
+
278
+ # Bubbles the `@supports` directive up through RuleNodes.
279
+ def visit_supports(node)
280
+ return node unless node.has_children
281
+ return bubble(node) if parent.is_a?(Sass::Tree::RuleNode)
282
+
283
+ yield
284
+
285
+ debubble(node.children, node)
286
+ end
287
+
288
+ private
289
+
290
+ # "Bubbles" `node` one level by copying the parent and wrapping `node`'s
291
+ # children with it.
292
+ #
293
+ # @param node [Sass::Tree::Node].
294
+ # @return [Bubble]
295
+ def bubble(node)
296
+ new_rule = parent.dup
297
+ new_rule.children = node.children
298
+ node.children = [new_rule]
299
+ Bubble.new(node)
300
+ end
301
+
302
+ # Pops all bubbles in `children` and intersperses the results with the other
303
+ # values.
304
+ #
305
+ # If `parent` is passed, it's copied and used as the parent node for the
306
+ # nested portions of `children`.
307
+ #
308
+ # @param children [List<Sass::Tree::Node, Bubble>]
309
+ # @param parent [Sass::Tree::Node]
310
+ # @yield [node] An optional block for processing bubbled nodes. Each bubbled
311
+ # node will be passed to this block.
312
+ # @yieldparam node [Sass::Tree::Node] A bubbled node.
313
+ # @yieldreturn [Sass::Tree::Node?] A node to use in place of the bubbled node.
314
+ # This can be the node itself, or `nil` to indicate that the node should be
315
+ # omitted.
316
+ # @return [List<Sass::Tree::Node, Bubble>]
317
+ def debubble(children, parent = nil)
318
+ Sass::Util.slice_by(children) {|c| c.is_a?(Bubble)}.map do |(is_bubble, slice)|
319
+ unless is_bubble
320
+ next slice unless parent
321
+ new_parent = parent.dup
322
+ new_parent.children = slice
323
+ next new_parent
324
+ end
325
+
326
+ next slice.map do |bubble|
327
+ next unless (node = block_given? ? yield(bubble.node) : bubble.node)
328
+ node.tabs += bubble.tabs
329
+ node.group_end = bubble.group_end
330
+ [visit(node)].flatten
331
+ end.compact
332
+ end.flatten
333
+ end
334
+
335
+ # Returns whether or not a node can be bubbled up through the syntax tree.
336
+ #
337
+ # @param node [Sass::Tree::Node]
338
+ # @return [Boolean]
339
+ def bubblable?(node)
340
+ node.is_a?(Sass::Tree::RuleNode) || node.bubbles?
341
+ end
342
+
343
+ # A wrapper class for a node that indicates to the parent that it should
344
+ # treat the wrapped node as a sibling rather than a child.
345
+ #
346
+ # Nodes should be wrapped before they're passed to \{Cssize.visit}. They will
347
+ # be automatically visited upon calling \{#pop}.
348
+ #
349
+ # This duck types as a [Sass::Tree::Node] for the purposes of
350
+ # tree-manipulation operations.
351
+ class Bubble
352
+ attr_accessor :node
353
+ attr_accessor :tabs
354
+ attr_accessor :group_end
355
+
356
+ def initialize(node)
357
+ @node = node
358
+ @tabs = 0
359
+ end
360
+
361
+ def bubbles?
362
+ true
363
+ end
364
+
365
+ def inspect
366
+ "(Bubble #{node.inspect})"
367
+ end
368
+ end
175
369
  end
@@ -0,0 +1,102 @@
1
+ # A visitor for copying the full structure of a Sass tree.
2
+ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
3
+ protected
4
+
5
+ def visit(node)
6
+ super(node.dup)
7
+ end
8
+
9
+ def visit_children(parent)
10
+ parent.children = parent.children.map {|c| visit(c)}
11
+ parent
12
+ end
13
+
14
+ def visit_debug(node)
15
+ node.expr = node.expr.deep_copy
16
+ yield
17
+ end
18
+
19
+ def visit_each(node)
20
+ node.list = node.list.deep_copy
21
+ yield
22
+ end
23
+
24
+ def visit_extend(node)
25
+ node.selector = node.selector.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
26
+ yield
27
+ end
28
+
29
+ def visit_for(node)
30
+ node.from = node.from.deep_copy
31
+ node.to = node.to.deep_copy
32
+ yield
33
+ end
34
+
35
+ def visit_function(node)
36
+ node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
37
+ yield
38
+ end
39
+
40
+ def visit_if(node)
41
+ node.expr = node.expr.deep_copy if node.expr
42
+ node.else = visit(node.else) if node.else
43
+ yield
44
+ end
45
+
46
+ def visit_mixindef(node)
47
+ node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
48
+ yield
49
+ end
50
+
51
+ def visit_mixin(node)
52
+ node.args = node.args.map {|a| a.deep_copy}
53
+ node.keywords = Hash[node.keywords.map {|k, v| [k, v.deep_copy]}]
54
+ yield
55
+ end
56
+
57
+ def visit_prop(node)
58
+ node.name = node.name.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
59
+ node.value = node.value.deep_copy
60
+ yield
61
+ end
62
+
63
+ def visit_return(node)
64
+ node.expr = node.expr.deep_copy
65
+ yield
66
+ end
67
+
68
+ def visit_rule(node)
69
+ node.rule = node.rule.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
70
+ yield
71
+ end
72
+
73
+ def visit_variable(node)
74
+ node.expr = node.expr.deep_copy
75
+ yield
76
+ end
77
+
78
+ def visit_warn(node)
79
+ node.expr = node.expr.deep_copy
80
+ yield
81
+ end
82
+
83
+ def visit_while(node)
84
+ node.expr = node.expr.deep_copy
85
+ yield
86
+ end
87
+
88
+ def visit_directive(node)
89
+ node.value = node.value.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
90
+ yield
91
+ end
92
+
93
+ def visit_media(node)
94
+ node.query = node.query.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
95
+ yield
96
+ end
97
+
98
+ def visit_supports(node)
99
+ node.condition = node.condition.deep_copy
100
+ yield
101
+ end
102
+ end
@@ -0,0 +1,68 @@
1
+ # A visitor for performing selector inheritance on a static CSS tree.
2
+ #
3
+ # Destructively modifies the tree.
4
+ class Sass::Tree::Visitors::Extend < Sass::Tree::Visitors::Base
5
+ # Performs the given extensions on the static CSS tree based in `root`, then
6
+ # validates that all extends matched some selector.
7
+ #
8
+ # @param root [Tree::Node] The root node of the tree to visit.
9
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
10
+ # Sass::Tree::Visitors::Cssize::Extend}]
11
+ # The extensions to perform on this tree.
12
+ # @return [Object] The return value of \{#visit} for the root node.
13
+ def self.visit(root, extends)
14
+ return if extends.empty?
15
+ new(extends).send(:visit, root)
16
+ check_extends_fired! extends
17
+ end
18
+
19
+ protected
20
+
21
+ def initialize(extends)
22
+ @parent_directives = []
23
+ @extends = extends
24
+ end
25
+
26
+ # If an exception is raised, this adds proper metadata to the backtrace.
27
+ def visit(node)
28
+ super(node)
29
+ rescue Sass::SyntaxError => e
30
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
31
+ raise e
32
+ end
33
+
34
+ # Keeps track of the current parent directives.
35
+ def visit_children(parent)
36
+ @parent_directives.push parent if parent.is_a?(Sass::Tree::DirectiveNode)
37
+ super
38
+ ensure
39
+ @parent_directives.pop if parent.is_a?(Sass::Tree::DirectiveNode)
40
+ end
41
+
42
+ # Applies the extend to a single rule's selector.
43
+ def visit_rule(node)
44
+ node.resolved_rules = node.resolved_rules.do_extend(@extends, @parent_directives)
45
+ end
46
+
47
+ private
48
+
49
+ def self.check_extends_fired!(extends)
50
+ extends.each_value do |ex|
51
+ next if ex.result == :succeeded || ex.node.optional?
52
+ message = "\"#{ex.extender}\" failed to @extend \"#{ex.target.join}\"."
53
+ reason =
54
+ if ex.result == :not_found
55
+ "The selector \"#{ex.target.join}\" was not found."
56
+ else
57
+ "No selectors matching \"#{ex.target.join}\" could be unified with \"#{ex.extender}\"."
58
+ end
59
+
60
+ # TODO(nweiz): this should use the Sass stack trace of the extend node.
61
+ raise Sass::SyntaxError.new(<<MESSAGE, :filename => ex.node.filename, :line => ex.node.line)
62
+ #{message}
63
+ #{reason}
64
+ Use "@extend #{ex.target.join} !optional" if the extend should be able to fail.
65
+ MESSAGE
66
+ end
67
+ end
68
+ end