oreorenasass 3.4.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 (268) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +11 -0
  3. data/CONTRIBUTING +3 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +221 -0
  6. data/Rakefile +370 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/sass +13 -0
  10. data/bin/sass-convert +12 -0
  11. data/bin/scss +13 -0
  12. data/extra/update_watch.rb +13 -0
  13. data/init.rb +18 -0
  14. data/lib/sass/cache_stores/base.rb +88 -0
  15. data/lib/sass/cache_stores/chain.rb +34 -0
  16. data/lib/sass/cache_stores/filesystem.rb +60 -0
  17. data/lib/sass/cache_stores/memory.rb +47 -0
  18. data/lib/sass/cache_stores/null.rb +25 -0
  19. data/lib/sass/cache_stores.rb +15 -0
  20. data/lib/sass/callbacks.rb +67 -0
  21. data/lib/sass/css.rb +407 -0
  22. data/lib/sass/engine.rb +1181 -0
  23. data/lib/sass/environment.rb +191 -0
  24. data/lib/sass/error.rb +198 -0
  25. data/lib/sass/exec/base.rb +187 -0
  26. data/lib/sass/exec/sass_convert.rb +264 -0
  27. data/lib/sass/exec/sass_scss.rb +424 -0
  28. data/lib/sass/exec.rb +9 -0
  29. data/lib/sass/features.rb +47 -0
  30. data/lib/sass/importers/base.rb +182 -0
  31. data/lib/sass/importers/filesystem.rb +211 -0
  32. data/lib/sass/importers.rb +22 -0
  33. data/lib/sass/logger/base.rb +30 -0
  34. data/lib/sass/logger/log_level.rb +45 -0
  35. data/lib/sass/logger.rb +12 -0
  36. data/lib/sass/media.rb +210 -0
  37. data/lib/sass/plugin/compiler.rb +565 -0
  38. data/lib/sass/plugin/configuration.rb +118 -0
  39. data/lib/sass/plugin/generic.rb +15 -0
  40. data/lib/sass/plugin/merb.rb +48 -0
  41. data/lib/sass/plugin/rack.rb +60 -0
  42. data/lib/sass/plugin/rails.rb +47 -0
  43. data/lib/sass/plugin/staleness_checker.rb +199 -0
  44. data/lib/sass/plugin.rb +133 -0
  45. data/lib/sass/railtie.rb +10 -0
  46. data/lib/sass/repl.rb +57 -0
  47. data/lib/sass/root.rb +7 -0
  48. data/lib/sass/script/css_lexer.rb +33 -0
  49. data/lib/sass/script/css_parser.rb +34 -0
  50. data/lib/sass/script/functions.rb +2626 -0
  51. data/lib/sass/script/lexer.rb +449 -0
  52. data/lib/sass/script/parser.rb +637 -0
  53. data/lib/sass/script/tree/funcall.rb +306 -0
  54. data/lib/sass/script/tree/interpolation.rb +118 -0
  55. data/lib/sass/script/tree/list_literal.rb +77 -0
  56. data/lib/sass/script/tree/literal.rb +45 -0
  57. data/lib/sass/script/tree/map_literal.rb +64 -0
  58. data/lib/sass/script/tree/node.rb +109 -0
  59. data/lib/sass/script/tree/operation.rb +103 -0
  60. data/lib/sass/script/tree/selector.rb +26 -0
  61. data/lib/sass/script/tree/string_interpolation.rb +104 -0
  62. data/lib/sass/script/tree/unary_operation.rb +69 -0
  63. data/lib/sass/script/tree/variable.rb +57 -0
  64. data/lib/sass/script/tree.rb +16 -0
  65. data/lib/sass/script/value/arg_list.rb +36 -0
  66. data/lib/sass/script/value/base.rb +240 -0
  67. data/lib/sass/script/value/bool.rb +35 -0
  68. data/lib/sass/script/value/color.rb +680 -0
  69. data/lib/sass/script/value/helpers.rb +262 -0
  70. data/lib/sass/script/value/list.rb +113 -0
  71. data/lib/sass/script/value/map.rb +70 -0
  72. data/lib/sass/script/value/null.rb +44 -0
  73. data/lib/sass/script/value/number.rb +530 -0
  74. data/lib/sass/script/value/string.rb +97 -0
  75. data/lib/sass/script/value.rb +11 -0
  76. data/lib/sass/script.rb +66 -0
  77. data/lib/sass/scss/css_parser.rb +42 -0
  78. data/lib/sass/scss/parser.rb +1209 -0
  79. data/lib/sass/scss/rx.rb +141 -0
  80. data/lib/sass/scss/script_lexer.rb +15 -0
  81. data/lib/sass/scss/script_parser.rb +25 -0
  82. data/lib/sass/scss/static_parser.rb +368 -0
  83. data/lib/sass/scss.rb +16 -0
  84. data/lib/sass/selector/abstract_sequence.rb +109 -0
  85. data/lib/sass/selector/comma_sequence.rb +175 -0
  86. data/lib/sass/selector/pseudo.rb +256 -0
  87. data/lib/sass/selector/sequence.rb +600 -0
  88. data/lib/sass/selector/simple.rb +117 -0
  89. data/lib/sass/selector/simple_sequence.rb +325 -0
  90. data/lib/sass/selector.rb +326 -0
  91. data/lib/sass/shared.rb +76 -0
  92. data/lib/sass/source/map.rb +210 -0
  93. data/lib/sass/source/position.rb +39 -0
  94. data/lib/sass/source/range.rb +41 -0
  95. data/lib/sass/stack.rb +120 -0
  96. data/lib/sass/supports.rb +227 -0
  97. data/lib/sass/tree/at_root_node.rb +83 -0
  98. data/lib/sass/tree/charset_node.rb +22 -0
  99. data/lib/sass/tree/comment_node.rb +82 -0
  100. data/lib/sass/tree/content_node.rb +9 -0
  101. data/lib/sass/tree/css_import_node.rb +60 -0
  102. data/lib/sass/tree/debug_node.rb +18 -0
  103. data/lib/sass/tree/directive_node.rb +59 -0
  104. data/lib/sass/tree/each_node.rb +24 -0
  105. data/lib/sass/tree/error_node.rb +18 -0
  106. data/lib/sass/tree/extend_node.rb +43 -0
  107. data/lib/sass/tree/for_node.rb +36 -0
  108. data/lib/sass/tree/function_node.rb +39 -0
  109. data/lib/sass/tree/if_node.rb +52 -0
  110. data/lib/sass/tree/import_node.rb +74 -0
  111. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  112. data/lib/sass/tree/media_node.rb +48 -0
  113. data/lib/sass/tree/mixin_def_node.rb +38 -0
  114. data/lib/sass/tree/mixin_node.rb +52 -0
  115. data/lib/sass/tree/node.rb +238 -0
  116. data/lib/sass/tree/prop_node.rb +171 -0
  117. data/lib/sass/tree/return_node.rb +19 -0
  118. data/lib/sass/tree/root_node.rb +44 -0
  119. data/lib/sass/tree/rule_node.rb +145 -0
  120. data/lib/sass/tree/supports_node.rb +38 -0
  121. data/lib/sass/tree/trace_node.rb +33 -0
  122. data/lib/sass/tree/variable_node.rb +36 -0
  123. data/lib/sass/tree/visitors/base.rb +72 -0
  124. data/lib/sass/tree/visitors/check_nesting.rb +177 -0
  125. data/lib/sass/tree/visitors/convert.rb +334 -0
  126. data/lib/sass/tree/visitors/cssize.rb +369 -0
  127. data/lib/sass/tree/visitors/deep_copy.rb +107 -0
  128. data/lib/sass/tree/visitors/extend.rb +68 -0
  129. data/lib/sass/tree/visitors/perform.rb +539 -0
  130. data/lib/sass/tree/visitors/set_options.rb +139 -0
  131. data/lib/sass/tree/visitors/to_css.rb +381 -0
  132. data/lib/sass/tree/warn_node.rb +18 -0
  133. data/lib/sass/tree/while_node.rb +18 -0
  134. data/lib/sass/util/cross_platform_random.rb +19 -0
  135. data/lib/sass/util/multibyte_string_scanner.rb +157 -0
  136. data/lib/sass/util/normalized_map.rb +130 -0
  137. data/lib/sass/util/ordered_hash.rb +192 -0
  138. data/lib/sass/util/subset_map.rb +110 -0
  139. data/lib/sass/util/test.rb +9 -0
  140. data/lib/sass/util.rb +1318 -0
  141. data/lib/sass/version.rb +124 -0
  142. data/lib/sass.rb +102 -0
  143. data/rails/init.rb +1 -0
  144. data/test/sass/cache_test.rb +131 -0
  145. data/test/sass/callbacks_test.rb +61 -0
  146. data/test/sass/compiler_test.rb +232 -0
  147. data/test/sass/conversion_test.rb +2054 -0
  148. data/test/sass/css2sass_test.rb +477 -0
  149. data/test/sass/data/hsl-rgb.txt +319 -0
  150. data/test/sass/encoding_test.rb +219 -0
  151. data/test/sass/engine_test.rb +3301 -0
  152. data/test/sass/exec_test.rb +86 -0
  153. data/test/sass/extend_test.rb +1661 -0
  154. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  155. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  156. data/test/sass/functions_test.rb +1926 -0
  157. data/test/sass/importer_test.rb +412 -0
  158. data/test/sass/logger_test.rb +58 -0
  159. data/test/sass/mock_importer.rb +49 -0
  160. data/test/sass/more_results/more1.css +9 -0
  161. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  162. data/test/sass/more_results/more_import.css +29 -0
  163. data/test/sass/more_templates/_more_partial.sass +2 -0
  164. data/test/sass/more_templates/more1.sass +23 -0
  165. data/test/sass/more_templates/more_import.sass +11 -0
  166. data/test/sass/plugin_test.rb +554 -0
  167. data/test/sass/results/alt.css +4 -0
  168. data/test/sass/results/basic.css +9 -0
  169. data/test/sass/results/cached_import_option.css +3 -0
  170. data/test/sass/results/compact.css +5 -0
  171. data/test/sass/results/complex.css +86 -0
  172. data/test/sass/results/compressed.css +1 -0
  173. data/test/sass/results/expanded.css +19 -0
  174. data/test/sass/results/filename_fn.css +3 -0
  175. data/test/sass/results/if.css +3 -0
  176. data/test/sass/results/import.css +31 -0
  177. data/test/sass/results/import_charset.css +5 -0
  178. data/test/sass/results/import_charset_1_8.css +5 -0
  179. data/test/sass/results/import_charset_ibm866.css +5 -0
  180. data/test/sass/results/import_content.css +1 -0
  181. data/test/sass/results/line_numbers.css +49 -0
  182. data/test/sass/results/mixins.css +95 -0
  183. data/test/sass/results/multiline.css +24 -0
  184. data/test/sass/results/nested.css +22 -0
  185. data/test/sass/results/options.css +1 -0
  186. data/test/sass/results/parent_ref.css +13 -0
  187. data/test/sass/results/script.css +16 -0
  188. data/test/sass/results/scss_import.css +31 -0
  189. data/test/sass/results/scss_importee.css +2 -0
  190. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  191. data/test/sass/results/subdir/subdir.css +3 -0
  192. data/test/sass/results/units.css +11 -0
  193. data/test/sass/results/warn.css +0 -0
  194. data/test/sass/results/warn_imported.css +0 -0
  195. data/test/sass/script_conversion_test.rb +328 -0
  196. data/test/sass/script_test.rb +1054 -0
  197. data/test/sass/scss/css_test.rb +1215 -0
  198. data/test/sass/scss/rx_test.rb +156 -0
  199. data/test/sass/scss/scss_test.rb +3900 -0
  200. data/test/sass/scss/test_helper.rb +37 -0
  201. data/test/sass/source_map_test.rb +977 -0
  202. data/test/sass/superselector_test.rb +191 -0
  203. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  204. data/test/sass/templates/_double_import_loop2.sass +1 -0
  205. data/test/sass/templates/_filename_fn_import.scss +11 -0
  206. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  207. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  208. data/test/sass/templates/_imported_content.sass +3 -0
  209. data/test/sass/templates/_partial.sass +2 -0
  210. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  211. data/test/sass/templates/alt.sass +16 -0
  212. data/test/sass/templates/basic.sass +23 -0
  213. data/test/sass/templates/bork1.sass +2 -0
  214. data/test/sass/templates/bork2.sass +2 -0
  215. data/test/sass/templates/bork3.sass +2 -0
  216. data/test/sass/templates/bork4.sass +2 -0
  217. data/test/sass/templates/bork5.sass +3 -0
  218. data/test/sass/templates/cached_import_option.scss +3 -0
  219. data/test/sass/templates/compact.sass +17 -0
  220. data/test/sass/templates/complex.sass +305 -0
  221. data/test/sass/templates/compressed.sass +15 -0
  222. data/test/sass/templates/double_import_loop1.sass +1 -0
  223. data/test/sass/templates/expanded.sass +17 -0
  224. data/test/sass/templates/filename_fn.scss +18 -0
  225. data/test/sass/templates/if.sass +11 -0
  226. data/test/sass/templates/import.sass +12 -0
  227. data/test/sass/templates/import_charset.sass +9 -0
  228. data/test/sass/templates/import_charset_1_8.sass +6 -0
  229. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  230. data/test/sass/templates/import_content.sass +4 -0
  231. data/test/sass/templates/importee.less +2 -0
  232. data/test/sass/templates/importee.sass +19 -0
  233. data/test/sass/templates/line_numbers.sass +13 -0
  234. data/test/sass/templates/mixin_bork.sass +5 -0
  235. data/test/sass/templates/mixins.sass +76 -0
  236. data/test/sass/templates/multiline.sass +20 -0
  237. data/test/sass/templates/nested.sass +25 -0
  238. data/test/sass/templates/nested_bork1.sass +2 -0
  239. data/test/sass/templates/nested_bork2.sass +2 -0
  240. data/test/sass/templates/nested_bork3.sass +2 -0
  241. data/test/sass/templates/nested_bork4.sass +2 -0
  242. data/test/sass/templates/nested_import.sass +2 -0
  243. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  244. data/test/sass/templates/options.sass +2 -0
  245. data/test/sass/templates/parent_ref.sass +25 -0
  246. data/test/sass/templates/same_name_different_ext.sass +2 -0
  247. data/test/sass/templates/same_name_different_ext.scss +1 -0
  248. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  249. data/test/sass/templates/script.sass +101 -0
  250. data/test/sass/templates/scss_import.scss +12 -0
  251. data/test/sass/templates/scss_importee.scss +1 -0
  252. data/test/sass/templates/single_import_loop.sass +1 -0
  253. data/test/sass/templates/subdir/import_up1.scss +1 -0
  254. data/test/sass/templates/subdir/import_up2.scss +1 -0
  255. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  256. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  257. data/test/sass/templates/subdir/subdir.sass +6 -0
  258. data/test/sass/templates/units.sass +11 -0
  259. data/test/sass/templates/warn.sass +3 -0
  260. data/test/sass/templates/warn_imported.sass +4 -0
  261. data/test/sass/test_helper.rb +8 -0
  262. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  263. data/test/sass/util/normalized_map_test.rb +51 -0
  264. data/test/sass/util/subset_map_test.rb +91 -0
  265. data/test/sass/util_test.rb +467 -0
  266. data/test/sass/value_helpers_test.rb +179 -0
  267. data/test/test_helper.rb +109 -0
  268. metadata +386 -0
@@ -0,0 +1,107 @@
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_error(node)
20
+ node.expr = node.expr.deep_copy
21
+ yield
22
+ end
23
+
24
+ def visit_each(node)
25
+ node.list = node.list.deep_copy
26
+ yield
27
+ end
28
+
29
+ def visit_extend(node)
30
+ node.selector = node.selector.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
31
+ yield
32
+ end
33
+
34
+ def visit_for(node)
35
+ node.from = node.from.deep_copy
36
+ node.to = node.to.deep_copy
37
+ yield
38
+ end
39
+
40
+ def visit_function(node)
41
+ node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
42
+ yield
43
+ end
44
+
45
+ def visit_if(node)
46
+ node.expr = node.expr.deep_copy if node.expr
47
+ node.else = visit(node.else) if node.else
48
+ yield
49
+ end
50
+
51
+ def visit_mixindef(node)
52
+ node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
53
+ yield
54
+ end
55
+
56
+ def visit_mixin(node)
57
+ node.args = node.args.map {|a| a.deep_copy}
58
+ node.keywords = Hash[node.keywords.map {|k, v| [k, v.deep_copy]}]
59
+ yield
60
+ end
61
+
62
+ def visit_prop(node)
63
+ node.name = node.name.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
64
+ node.value = node.value.deep_copy
65
+ yield
66
+ end
67
+
68
+ def visit_return(node)
69
+ node.expr = node.expr.deep_copy
70
+ yield
71
+ end
72
+
73
+ def visit_rule(node)
74
+ node.rule = node.rule.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
75
+ yield
76
+ end
77
+
78
+ def visit_variable(node)
79
+ node.expr = node.expr.deep_copy
80
+ yield
81
+ end
82
+
83
+ def visit_warn(node)
84
+ node.expr = node.expr.deep_copy
85
+ yield
86
+ end
87
+
88
+ def visit_while(node)
89
+ node.expr = node.expr.deep_copy
90
+ yield
91
+ end
92
+
93
+ def visit_directive(node)
94
+ node.value = node.value.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
95
+ yield
96
+ end
97
+
98
+ def visit_media(node)
99
+ node.query = node.query.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
100
+ yield
101
+ end
102
+
103
+ def visit_supports(node)
104
+ node.condition = node.condition.deep_copy
105
+ yield
106
+ end
107
+ 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
@@ -0,0 +1,539 @@
1
+ # A visitor for converting a dynamic Sass tree into a static Sass tree.
2
+ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
3
+ class << self
4
+ # @param root [Tree::Node] The root node of the tree to visit.
5
+ # @param environment [Sass::Environment] The lexical environment.
6
+ # @return [Tree::Node] The resulting tree of static nodes.
7
+ def visit(root, environment = nil)
8
+ new(environment).send(:visit, root)
9
+ end
10
+
11
+ # @api private
12
+ # @comment
13
+ # rubocop:disable MethodLength
14
+ def perform_arguments(callable, args, splat)
15
+ desc = "#{callable.type.capitalize} #{callable.name}"
16
+ downcase_desc = "#{callable.type} #{callable.name}"
17
+
18
+ # All keywords are contained in splat.keywords for consistency,
19
+ # even if there were no splats passed in.
20
+ old_keywords_accessed = splat.keywords_accessed
21
+ keywords = splat.keywords
22
+ splat.keywords_accessed = old_keywords_accessed
23
+
24
+ begin
25
+ unless keywords.empty?
26
+ unknown_args = Sass::Util.array_minus(keywords.keys,
27
+ callable.args.map {|var| var.first.underscored_name})
28
+ if callable.splat && unknown_args.include?(callable.splat.underscored_name)
29
+ raise Sass::SyntaxError.new("Argument $#{callable.splat.name} of #{downcase_desc} " +
30
+ "cannot be used as a named argument.")
31
+ elsif unknown_args.any?
32
+ description = unknown_args.length > 1 ? 'the following arguments:' : 'an argument named'
33
+ raise Sass::SyntaxError.new("#{desc} doesn't have #{description} " +
34
+ "#{unknown_args.map {|name| "$#{name}"}.join ', '}.")
35
+ end
36
+ end
37
+ rescue Sass::SyntaxError => keyword_exception
38
+ end
39
+
40
+ # If there's no splat, raise the keyword exception immediately. The actual
41
+ # raising happens in the ensure clause at the end of this function.
42
+ return if keyword_exception && !callable.splat
43
+
44
+ if args.size > callable.args.size && !callable.splat
45
+ takes = callable.args.size
46
+ passed = args.size
47
+ raise Sass::SyntaxError.new(
48
+ "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
49
+ "but #{passed} #{passed == 1 ? 'was' : 'were'} passed.")
50
+ end
51
+
52
+ splat_sep = :comma
53
+ if splat
54
+ args += splat.to_a
55
+ splat_sep = splat.separator
56
+ end
57
+
58
+ env = Sass::Environment.new(callable.environment)
59
+ callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
60
+ if value && keywords.has_key?(var.name)
61
+ raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} " +
62
+ "both by position and by name.")
63
+ end
64
+
65
+ value ||= keywords.delete(var.name)
66
+ value ||= default && default.perform(env)
67
+ raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
68
+ env.set_local_var(var.name, value)
69
+ end
70
+
71
+ if callable.splat
72
+ rest = args[callable.args.length..-1] || []
73
+ arg_list = Sass::Script::Value::ArgList.new(rest, keywords, splat_sep)
74
+ arg_list.options = env.options
75
+ env.set_local_var(callable.splat.name, arg_list)
76
+ end
77
+
78
+ yield env
79
+ rescue StandardError => e
80
+ ensure
81
+ # If there's a keyword exception, we don't want to throw it immediately,
82
+ # because the invalid keywords may be part of a glob argument that should be
83
+ # passed on to another function. So we only raise it if we reach the end of
84
+ # this function *and* the keywords attached to the argument list glob object
85
+ # haven't been accessed.
86
+ #
87
+ # The keyword exception takes precedence over any Sass errors, but not over
88
+ # non-Sass exceptions.
89
+ if keyword_exception &&
90
+ !(arg_list && arg_list.keywords_accessed) &&
91
+ (e.nil? || e.is_a?(Sass::SyntaxError))
92
+ raise keyword_exception
93
+ elsif e
94
+ raise e
95
+ end
96
+ end
97
+
98
+ # @api private
99
+ # @return [Sass::Script::Value::ArgList]
100
+ def perform_splat(splat, performed_keywords, kwarg_splat, environment)
101
+ args, kwargs, separator = [], nil, :comma
102
+
103
+ if splat
104
+ splat = splat.perform(environment)
105
+ separator = splat.separator || separator
106
+ if splat.is_a?(Sass::Script::Value::ArgList)
107
+ args = splat.to_a
108
+ kwargs = splat.keywords
109
+ elsif splat.is_a?(Sass::Script::Value::Map)
110
+ kwargs = arg_hash(splat)
111
+ else
112
+ args = splat.to_a
113
+ end
114
+ end
115
+ kwargs ||= Sass::Util::NormalizedMap.new
116
+ kwargs.update(performed_keywords)
117
+
118
+ if kwarg_splat
119
+ kwarg_splat = kwarg_splat.perform(environment)
120
+ unless kwarg_splat.is_a?(Sass::Script::Value::Map)
121
+ raise Sass::SyntaxError.new("Variable keyword arguments must be a map " +
122
+ "(was #{kwarg_splat.inspect}).")
123
+ end
124
+ kwargs.update(arg_hash(kwarg_splat))
125
+ end
126
+
127
+ Sass::Script::Value::ArgList.new(args, kwargs, separator)
128
+ end
129
+
130
+ private
131
+
132
+ def arg_hash(map)
133
+ Sass::Util.map_keys(map.to_h) do |key|
134
+ next key.value if key.is_a?(Sass::Script::Value::String)
135
+ raise Sass::SyntaxError.new("Variable keyword argument map must have string keys.\n" +
136
+ "#{key.inspect} is not a string in #{map.inspect}.")
137
+ end
138
+ end
139
+ end
140
+ # @comment
141
+ # rubocop:enable MethodLength
142
+
143
+ protected
144
+
145
+ def initialize(env)
146
+ @environment = env
147
+ end
148
+
149
+ # If an exception is raised, this adds proper metadata to the backtrace.
150
+ def visit(node)
151
+ return super(node.dup) unless @environment
152
+ @environment.stack.with_base(node.filename, node.line) {super(node.dup)}
153
+ rescue Sass::SyntaxError => e
154
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
155
+ raise e
156
+ end
157
+
158
+ # Keeps track of the current environment.
159
+ def visit_children(parent)
160
+ with_environment Sass::Environment.new(@environment, parent.options) do
161
+ parent.children = super.flatten
162
+ parent
163
+ end
164
+ end
165
+
166
+ # Runs a block of code with the current environment replaced with the given one.
167
+ #
168
+ # @param env [Sass::Environment] The new environment for the duration of the block.
169
+ # @yield A block in which the environment is set to `env`.
170
+ # @return [Object] The return value of the block.
171
+ def with_environment(env)
172
+ old_env, @environment = @environment, env
173
+ yield
174
+ ensure
175
+ @environment = old_env
176
+ end
177
+
178
+ # Sets the options on the environment if this is the top-level root.
179
+ def visit_root(node)
180
+ yield
181
+ rescue Sass::SyntaxError => e
182
+ e.sass_template ||= node.template
183
+ raise e
184
+ end
185
+
186
+ # Removes this node from the tree if it's a silent comment.
187
+ def visit_comment(node)
188
+ return [] if node.invisible?
189
+ node.resolved_value = run_interp_no_strip(node.value)
190
+ node.resolved_value.gsub!(/\\([\\#])/, '\1')
191
+ node
192
+ end
193
+
194
+ # Prints the expression to STDERR.
195
+ def visit_debug(node)
196
+ res = node.expr.perform(@environment)
197
+ if res.is_a?(Sass::Script::Value::String)
198
+ res = res.value
199
+ else
200
+ res = res.to_sass
201
+ end
202
+ if node.filename
203
+ Sass::Util.sass_warn "#{node.filename}:#{node.line} DEBUG: #{res}"
204
+ else
205
+ Sass::Util.sass_warn "Line #{node.line} DEBUG: #{res}"
206
+ end
207
+ []
208
+ end
209
+
210
+ # Throws the expression as an error.
211
+ def visit_error(node)
212
+ res = node.expr.perform(@environment)
213
+ if res.is_a?(Sass::Script::Value::String)
214
+ res = res.value
215
+ else
216
+ res = res.to_sass
217
+ end
218
+ raise Sass::SyntaxError.new(res)
219
+ end
220
+
221
+ # Runs the child nodes once for each value in the list.
222
+ def visit_each(node)
223
+ list = node.list.perform(@environment)
224
+
225
+ with_environment Sass::Environment.new(@environment) do
226
+ list.to_a.map do |value|
227
+ if node.vars.length == 1
228
+ @environment.set_local_var(node.vars.first, value)
229
+ else
230
+ node.vars.zip(value.to_a) do |(var, sub_value)|
231
+ @environment.set_local_var(var, sub_value || Sass::Script::Value::Null.new)
232
+ end
233
+ end
234
+ node.children.map {|c| visit(c)}
235
+ end.flatten
236
+ end
237
+ end
238
+
239
+ # Runs SassScript interpolation in the selector,
240
+ # and then parses the result into a {Sass::Selector::CommaSequence}.
241
+ def visit_extend(node)
242
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.selector),
243
+ node.filename, node.options[:importer], node.line)
244
+ node.resolved_selector = parser.parse_selector
245
+ node
246
+ end
247
+
248
+ # Runs the child nodes once for each time through the loop, varying the variable each time.
249
+ def visit_for(node)
250
+ from = node.from.perform(@environment)
251
+ to = node.to.perform(@environment)
252
+ from.assert_int!
253
+ to.assert_int!
254
+
255
+ to = to.coerce(from.numerator_units, from.denominator_units)
256
+ direction = from.to_i > to.to_i ? -1 : 1
257
+ range = Range.new(direction * from.to_i, direction * to.to_i, node.exclusive)
258
+
259
+ with_environment Sass::Environment.new(@environment) do
260
+ range.map do |i|
261
+ @environment.set_local_var(node.var,
262
+ Sass::Script::Value::Number.new(direction * i,
263
+ from.numerator_units, from.denominator_units))
264
+ node.children.map {|c| visit(c)}
265
+ end.flatten
266
+ end
267
+ end
268
+
269
+ # Loads the function into the environment.
270
+ def visit_function(node)
271
+ env = Sass::Environment.new(@environment, node.options)
272
+ @environment.set_local_function(node.name,
273
+ Sass::Callable.new(node.name, node.args, node.splat, env,
274
+ node.children, !:has_content, "function"))
275
+ []
276
+ end
277
+
278
+ # Runs the child nodes if the conditional expression is true;
279
+ # otherwise, tries the else nodes.
280
+ def visit_if(node)
281
+ if node.expr.nil? || node.expr.perform(@environment).to_bool
282
+ yield
283
+ node.children
284
+ elsif node.else
285
+ visit(node.else)
286
+ else
287
+ []
288
+ end
289
+ end
290
+
291
+ # Returns a static DirectiveNode if this is importing a CSS file,
292
+ # or parses and includes the imported Sass file.
293
+ def visit_import(node)
294
+ if (path = node.css_import?)
295
+ resolved_node = Sass::Tree::CssImportNode.resolved("url(#{path})")
296
+ resolved_node.source_range = node.source_range
297
+ return resolved_node
298
+ end
299
+ file = node.imported_file
300
+ if @environment.stack.frames.any? {|f| f.is_import? && f.filename == file.options[:filename]}
301
+ handle_import_loop!(node)
302
+ end
303
+
304
+ begin
305
+ @environment.stack.with_import(node.filename, node.line) do
306
+ root = file.to_tree
307
+ Sass::Tree::Visitors::CheckNesting.visit(root)
308
+ node.children = root.children.map {|c| visit(c)}.flatten
309
+ node
310
+ end
311
+ rescue Sass::SyntaxError => e
312
+ e.modify_backtrace(:filename => node.imported_file.options[:filename])
313
+ e.add_backtrace(:filename => node.filename, :line => node.line)
314
+ raise e
315
+ end
316
+ end
317
+
318
+ # Loads a mixin into the environment.
319
+ def visit_mixindef(node)
320
+ env = Sass::Environment.new(@environment, node.options)
321
+ @environment.set_local_mixin(node.name,
322
+ Sass::Callable.new(node.name, node.args, node.splat, env,
323
+ node.children, node.has_content, "mixin"))
324
+ []
325
+ end
326
+
327
+ # Runs a mixin.
328
+ def visit_mixin(node)
329
+ @environment.stack.with_mixin(node.filename, node.line, node.name) do
330
+ mixin = @environment.mixin(node.name)
331
+ raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin
332
+
333
+ if node.children.any? && !mixin.has_content
334
+ raise Sass::SyntaxError.new(%Q{Mixin "#{node.name}" does not accept a content block.})
335
+ end
336
+
337
+ args = node.args.map {|a| a.perform(@environment)}
338
+ keywords = Sass::Util.map_vals(node.keywords) {|v| v.perform(@environment)}
339
+ splat = self.class.perform_splat(node.splat, keywords, node.kwarg_splat, @environment)
340
+
341
+ self.class.perform_arguments(mixin, args, splat) do |env|
342
+ env.caller = Sass::Environment.new(@environment)
343
+ env.content = [node.children, @environment] if node.has_children
344
+
345
+ trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
346
+ with_environment(env) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
347
+ trace_node
348
+ end
349
+ end
350
+ rescue Sass::SyntaxError => e
351
+ e.modify_backtrace(:mixin => node.name, :line => node.line)
352
+ e.add_backtrace(:line => node.line)
353
+ raise e
354
+ end
355
+
356
+ def visit_content(node)
357
+ content, content_env = @environment.content
358
+ return [] unless content
359
+ @environment.stack.with_mixin(node.filename, node.line, '@content') do
360
+ trace_node = Sass::Tree::TraceNode.from_node('@content', node)
361
+ content_env = Sass::Environment.new(content_env)
362
+ content_env.caller = Sass::Environment.new(@environment)
363
+ with_environment(content_env) do
364
+ trace_node.children = content.map {|c| visit(c.dup)}.flatten
365
+ end
366
+ trace_node
367
+ end
368
+ rescue Sass::SyntaxError => e
369
+ e.modify_backtrace(:mixin => '@content', :line => node.line)
370
+ e.add_backtrace(:line => node.line)
371
+ raise e
372
+ end
373
+
374
+ # Runs any SassScript that may be embedded in a property.
375
+ def visit_prop(node)
376
+ node.resolved_name = run_interp(node.name)
377
+ val = node.value.perform(@environment)
378
+ node.resolved_value = val.to_s
379
+ node.value_source_range = val.source_range if val.source_range
380
+ yield
381
+ end
382
+
383
+ # Returns the value of the expression.
384
+ def visit_return(node)
385
+ throw :_sass_return, node.expr.perform(@environment)
386
+ end
387
+
388
+ # Runs SassScript interpolation in the selector,
389
+ # and then parses the result into a {Sass::Selector::CommaSequence}.
390
+ def visit_rule(node)
391
+ old_at_root_without_rule = @at_root_without_rule
392
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.rule),
393
+ node.filename, node.options[:importer], node.line)
394
+ if @in_keyframes
395
+ keyframe_rule_node = Sass::Tree::KeyframeRuleNode.new(parser.parse_keyframes_selector)
396
+ keyframe_rule_node.options = node.options
397
+ keyframe_rule_node.line = node.line
398
+ keyframe_rule_node.filename = node.filename
399
+ keyframe_rule_node.source_range = node.source_range
400
+ with_environment Sass::Environment.new(@environment, node.options) do
401
+ keyframe_rule_node.children = node.children.map {|c| visit(c)}.flatten
402
+ end
403
+ keyframe_rule_node
404
+ else
405
+ @at_root_without_rule = false
406
+ node.parsed_rules ||= parser.parse_selector
407
+ node.resolved_rules = node.parsed_rules.resolve_parent_refs(
408
+ @environment.selector, !old_at_root_without_rule)
409
+ node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
410
+ with_environment Sass::Environment.new(@environment, node.options) do
411
+ @environment.selector = node.resolved_rules
412
+ node.children = node.children.map {|c| visit(c)}.flatten
413
+ end
414
+ node
415
+ end
416
+ ensure
417
+ @at_root_without_rule = old_at_root_without_rule
418
+ end
419
+
420
+ # Sets a variable that indicates that the first level of rule nodes
421
+ # shouldn't include the parent selector by default.
422
+ def visit_atroot(node)
423
+ if node.query
424
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
425
+ node.filename, node.options[:importer], node.line)
426
+ node.resolved_type, node.resolved_value = parser.parse_static_at_root_query
427
+ else
428
+ node.resolved_type, node.resolved_value = :without, ['rule']
429
+ end
430
+
431
+ old_at_root_without_rule = @at_root_without_rule
432
+ old_in_keyframes = @in_keyframes
433
+ @at_root_without_rule = true if node.exclude?('rule')
434
+ @in_keyframes = false if node.exclude?('keyframes')
435
+ yield
436
+ ensure
437
+ @in_keyframes = old_in_keyframes
438
+ @at_root_without_rule = old_at_root_without_rule
439
+ end
440
+
441
+ # Loads the new variable value into the environment.
442
+ def visit_variable(node)
443
+ env = @environment
444
+ env = env.global_env if node.global
445
+ if node.guarded
446
+ var = env.var(node.name)
447
+ return [] if var && !var.null?
448
+ end
449
+
450
+ val = node.expr.perform(@environment)
451
+ if node.expr.source_range
452
+ val.source_range = node.expr.source_range
453
+ else
454
+ val.source_range = node.source_range
455
+ end
456
+ env.set_var(node.name, val)
457
+ []
458
+ end
459
+
460
+ # Prints the expression to STDERR with a stylesheet trace.
461
+ def visit_warn(node)
462
+ res = node.expr.perform(@environment)
463
+ res = res.value if res.is_a?(Sass::Script::Value::String)
464
+ msg = "WARNING: #{res}\n "
465
+ msg << @environment.stack.to_s.gsub("\n", "\n ") << "\n"
466
+ Sass::Util.sass_warn msg
467
+ []
468
+ end
469
+
470
+ # Runs the child nodes until the continuation expression becomes false.
471
+ def visit_while(node)
472
+ children = []
473
+ with_environment Sass::Environment.new(@environment) do
474
+ children += node.children.map {|c| visit(c)} while node.expr.perform(@environment).to_bool
475
+ end
476
+ children.flatten
477
+ end
478
+
479
+ def visit_directive(node)
480
+ node.resolved_value = run_interp(node.value)
481
+ old_in_keyframes, @in_keyframes = @in_keyframes, node.normalized_name == "@keyframes"
482
+ with_environment Sass::Environment.new(@environment) do
483
+ node.children = node.children.map {|c| visit(c)}.flatten
484
+ node
485
+ end
486
+ ensure
487
+ @in_keyframes = old_in_keyframes
488
+ end
489
+
490
+ def visit_media(node)
491
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
492
+ node.filename, node.options[:importer], node.line)
493
+ node.resolved_query ||= parser.parse_media_query_list
494
+ yield
495
+ end
496
+
497
+ def visit_supports(node)
498
+ node.condition = node.condition.deep_copy
499
+ node.condition.perform(@environment)
500
+ yield
501
+ end
502
+
503
+ def visit_cssimport(node)
504
+ node.resolved_uri = run_interp([node.uri])
505
+ if node.query && !node.query.empty?
506
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
507
+ node.filename, node.options[:importer], node.line)
508
+ node.resolved_query ||= parser.parse_media_query_list
509
+ end
510
+ yield
511
+ end
512
+
513
+ private
514
+
515
+ def run_interp_no_strip(text)
516
+ text.map do |r|
517
+ next r if r.is_a?(String)
518
+ r.perform(@environment).to_s(:quote => :none)
519
+ end.join
520
+ end
521
+
522
+ def run_interp(text)
523
+ run_interp_no_strip(text).strip
524
+ end
525
+
526
+ def handle_import_loop!(node)
527
+ msg = "An @import loop has been found:"
528
+ files = @environment.stack.frames.select {|f| f.is_import?}.map {|f| f.filename}.compact
529
+ if node.filename == node.imported_file.options[:filename]
530
+ raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself")
531
+ end
532
+
533
+ files << node.filename << node.imported_file.options[:filename]
534
+ msg << "\n" << Sass::Util.enum_cons(files, 2).map do |m1, m2|
535
+ " #{m1} imports #{m2}"
536
+ end.join("\n")
537
+ raise Sass::SyntaxError.new(msg)
538
+ end
539
+ end