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
@@ -1,11 +1,144 @@
1
1
  # A visitor for converting a dynamic Sass tree into a static Sass tree.
2
2
  class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
3
- # @param root [Tree::Node] The root node of the tree to visit.
4
- # @param environment [Sass::Environment] The lexical environment.
5
- # @return [Tree::Node] The resulting tree of static nodes.
6
- def self.visit(root, environment = Sass::Environment.new)
7
- new(environment).send(:visit, root)
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
8
139
  end
140
+ # @comment
141
+ # rubocop:enable MethodLength
9
142
 
10
143
  protected
11
144
 
@@ -13,9 +146,10 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
13
146
  @environment = env
14
147
  end
15
148
 
16
- # If an exception is raised, this add proper metadata to the backtrace.
149
+ # If an exception is raised, this adds proper metadata to the backtrace.
17
150
  def visit(node)
18
- super(node.dup)
151
+ return super(node.dup) unless @environment
152
+ @environment.stack.with_base(node.filename, node.line) {super(node.dup)}
19
153
  rescue Sass::SyntaxError => e
20
154
  e.modify_backtrace(:filename => node.filename, :line => node.line)
21
155
  raise e
@@ -23,7 +157,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
23
157
 
24
158
  # Keeps track of the current environment.
25
159
  def visit_children(parent)
26
- with_environment Sass::Environment.new(@environment) do
160
+ with_environment Sass::Environment.new(@environment, parent.options) do
27
161
  parent.children = super.flatten
28
162
  parent
29
163
  end
@@ -43,7 +177,6 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
43
177
 
44
178
  # Sets the options on the environment if this is the top-level root.
45
179
  def visit_root(node)
46
- @environment.options = node.options if @environment.options.nil? || @environment.options.empty?
47
180
  yield
48
181
  rescue Sass::SyntaxError => e
49
182
  e.sass_template ||= node.template
@@ -53,23 +186,23 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
53
186
  # Removes this node from the tree if it's a silent comment.
54
187
  def visit_comment(node)
55
188
  return [] if node.invisible?
56
- if node.evaluated?
57
- node.value.gsub!(/(^|[^\\])\#\{([^}]*)\}/) do |md|
58
- $1+Sass::Script.parse($2, node.line, 0, node.options).perform(@environment).to_s
59
- end
60
- node.value = run_interp([Sass::Script::String.new(node.value)])
61
- end
189
+ node.resolved_value = run_interp_no_strip(node.value)
190
+ node.resolved_value.gsub!(/\\([\\#])/, '\1')
62
191
  node
63
192
  end
64
193
 
65
194
  # Prints the expression to STDERR.
66
195
  def visit_debug(node)
67
196
  res = node.expr.perform(@environment)
68
- res = res.value if res.is_a?(Sass::Script::String)
197
+ if res.is_a?(Sass::Script::Value::String)
198
+ res = res.value
199
+ else
200
+ res = res.to_sass
201
+ end
69
202
  if node.filename
70
- $stderr.puts "#{node.filename}:#{node.line} DEBUG: #{res}"
203
+ Sass::Util.sass_warn "#{node.filename}:#{node.line} DEBUG: #{res}"
71
204
  else
72
- $stderr.puts "Line #{node.line} DEBUG: #{res}"
205
+ Sass::Util.sass_warn "Line #{node.line} DEBUG: #{res}"
73
206
  end
74
207
  []
75
208
  end
@@ -79,8 +212,14 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
79
212
  list = node.list.perform(@environment)
80
213
 
81
214
  with_environment Sass::Environment.new(@environment) do
82
- list.to_a.map do |v|
83
- @environment.set_local_var(node.var, v)
215
+ list.to_a.map do |value|
216
+ if node.vars.length == 1
217
+ @environment.set_local_var(node.vars.first, value)
218
+ else
219
+ node.vars.zip(value.to_a) do |(var, sub_value)|
220
+ @environment.set_local_var(var, sub_value || Sass::Script::Value::Null.new)
221
+ end
222
+ end
84
223
  node.children.map {|c| visit(c)}
85
224
  end.flatten
86
225
  end
@@ -89,8 +228,9 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
89
228
  # Runs SassScript interpolation in the selector,
90
229
  # and then parses the result into a {Sass::Selector::CommaSequence}.
91
230
  def visit_extend(node)
92
- parser = Sass::SCSS::CssParser.new(run_interp(node.selector), node.line)
93
- node.resolved_selector = parser.parse_selector(node.filename)
231
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.selector),
232
+ node.filename, node.options[:importer], node.line)
233
+ node.resolved_selector = parser.parse_selector
94
234
  node
95
235
  end
96
236
 
@@ -102,12 +242,14 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
102
242
  to.assert_int!
103
243
 
104
244
  to = to.coerce(from.numerator_units, from.denominator_units)
105
- range = Range.new(from.to_i, to.to_i, node.exclusive)
245
+ direction = from.to_i > to.to_i ? -1 : 1
246
+ range = Range.new(direction * from.to_i, direction * to.to_i, node.exclusive)
106
247
 
107
248
  with_environment Sass::Environment.new(@environment) do
108
249
  range.map do |i|
109
250
  @environment.set_local_var(node.var,
110
- Sass::Script::Number.new(i, from.numerator_units, from.denominator_units))
251
+ Sass::Script::Value::Number.new(direction * i,
252
+ from.numerator_units, from.denominator_units))
111
253
  node.children.map {|c| visit(c)}
112
254
  end.flatten
113
255
  end
@@ -115,8 +257,10 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
115
257
 
116
258
  # Loads the function into the environment.
117
259
  def visit_function(node)
118
- @environment.set_function(node.name,
119
- Sass::Callable.new(node.name, node.args, @environment, node.children))
260
+ env = Sass::Environment.new(@environment, node.options)
261
+ @environment.set_local_function(node.name,
262
+ Sass::Callable.new(node.name, node.args, node.splat, env,
263
+ node.children, !:has_content, "function"))
120
264
  []
121
265
  end
122
266
 
@@ -136,77 +280,84 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
136
280
  # Returns a static DirectiveNode if this is importing a CSS file,
137
281
  # or parses and includes the imported Sass file.
138
282
  def visit_import(node)
139
- if path = node.css_import?
140
- return Sass::Tree::DirectiveNode.new("@import url(#{path})")
283
+ if (path = node.css_import?)
284
+ resolved_node = Sass::Tree::CssImportNode.resolved("url(#{path})")
285
+ resolved_node.source_range = node.source_range
286
+ return resolved_node
287
+ end
288
+ file = node.imported_file
289
+ if @environment.stack.frames.any? {|f| f.is_import? && f.filename == file.options[:filename]}
290
+ handle_import_loop!(node)
141
291
  end
142
292
 
143
- @environment.push_frame(:filename => node.filename, :line => node.line)
144
- root = node.imported_file.to_tree
145
- node.children = root.children.map {|c| visit(c)}.flatten
146
- node
147
- rescue Sass::SyntaxError => e
148
- e.modify_backtrace(:filename => node.imported_file.options[:filename])
149
- e.add_backtrace(:filename => node.filename, :line => node.line)
150
- raise e
151
- ensure
152
- @environment.pop_frame
293
+ begin
294
+ @environment.stack.with_import(node.filename, node.line) do
295
+ root = file.to_tree
296
+ Sass::Tree::Visitors::CheckNesting.visit(root)
297
+ node.children = root.children.map {|c| visit(c)}.flatten
298
+ node
299
+ end
300
+ rescue Sass::SyntaxError => e
301
+ e.modify_backtrace(:filename => node.imported_file.options[:filename])
302
+ e.add_backtrace(:filename => node.filename, :line => node.line)
303
+ raise e
304
+ end
153
305
  end
154
306
 
155
307
  # Loads a mixin into the environment.
156
308
  def visit_mixindef(node)
157
- @environment.set_mixin(node.name,
158
- Sass::Callable.new(node.name, node.args, @environment, node.children))
309
+ env = Sass::Environment.new(@environment, node.options)
310
+ @environment.set_local_mixin(node.name,
311
+ Sass::Callable.new(node.name, node.args, node.splat, env,
312
+ node.children, node.has_content, "mixin"))
159
313
  []
160
314
  end
161
315
 
162
316
  # Runs a mixin.
163
317
  def visit_mixin(node)
164
- handle_include_loop!(node) if @environment.mixins_in_use.include?(node.name)
318
+ @environment.stack.with_mixin(node.filename, node.line, node.name) do
319
+ mixin = @environment.mixin(node.name)
320
+ raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin
165
321
 
166
- original_env = @environment
167
- original_env.push_frame(:filename => node.filename, :line => node.line)
168
- original_env.prepare_frame(:mixin => node.name)
169
- raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin = @environment.mixin(node.name)
322
+ if node.children.any? && !mixin.has_content
323
+ raise Sass::SyntaxError.new(%Q{Mixin "#{node.name}" does not accept a content block.})
324
+ end
170
325
 
171
- passed_args = node.args.dup
172
- passed_keywords = node.keywords.dup
326
+ args = node.args.map {|a| a.perform(@environment)}
327
+ keywords = Sass::Util.map_vals(node.keywords) {|v| v.perform(@environment)}
328
+ splat = self.class.perform_splat(node.splat, keywords, node.kwarg_splat, @environment)
173
329
 
174
- raise Sass::SyntaxError.new(<<END.gsub("\n", "")) if mixin.args.size < passed_args.size
175
- Mixin #{node.name} takes #{mixin.args.size} argument#{'s' if mixin.args.size != 1}
176
- but #{node.args.size} #{node.args.size == 1 ? 'was' : 'were'} passed.
177
- END
330
+ self.class.perform_arguments(mixin, args, splat) do |env|
331
+ env.caller = Sass::Environment.new(@environment)
332
+ env.content = [node.children, @environment] if node.has_children
178
333
 
179
- passed_keywords.each do |name, value|
180
- # TODO: Make this fast
181
- unless mixin.args.find {|(var, default)| var.underscored_name == name}
182
- raise Sass::SyntaxError.new("Mixin #{node.name} doesn't have an argument named $#{name}")
334
+ trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
335
+ with_environment(env) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
336
+ trace_node
183
337
  end
184
338
  end
339
+ rescue Sass::SyntaxError => e
340
+ e.modify_backtrace(:mixin => node.name, :line => node.line)
341
+ e.add_backtrace(:line => node.line)
342
+ raise e
343
+ end
185
344
 
186
- environment = mixin.args.zip(passed_args).
187
- inject(Sass::Environment.new(mixin.environment)) do |env, ((var, default), value)|
188
- env.set_local_var(var.name,
189
- if value
190
- value.perform(@environment)
191
- elsif kv = passed_keywords[var.underscored_name]
192
- kv.perform(@environment)
193
- elsif default
194
- default.perform(env)
195
- end)
196
- raise Sass::SyntaxError.new("Mixin #{node.name} is missing parameter #{var.inspect}.") unless env.var(var.name)
197
- env
345
+ def visit_content(node)
346
+ content, content_env = @environment.content
347
+ return [] unless content
348
+ @environment.stack.with_mixin(node.filename, node.line, '@content') do
349
+ trace_node = Sass::Tree::TraceNode.from_node('@content', node)
350
+ content_env = Sass::Environment.new(content_env)
351
+ content_env.caller = Sass::Environment.new(@environment)
352
+ with_environment(content_env) do
353
+ trace_node.children = content.map {|c| visit(c.dup)}.flatten
354
+ end
355
+ trace_node
198
356
  end
199
-
200
- with_environment(environment) {node.children = mixin.tree.map {|c| visit(c)}.flatten}
201
- node
202
357
  rescue Sass::SyntaxError => e
203
- if original_env # Don't add backtrace info if this is an @include loop
204
- e.modify_backtrace(:mixin => node.name, :line => node.line)
205
- e.add_backtrace(:line => node.line)
206
- end
358
+ e.modify_backtrace(:mixin => '@content', :line => node.line)
359
+ e.add_backtrace(:line => node.line)
207
360
  raise e
208
- ensure
209
- original_env.pop_frame if original_env
210
361
  end
211
362
 
212
363
  # Runs any SassScript that may be embedded in a property.
@@ -214,6 +365,7 @@ END
214
365
  node.resolved_name = run_interp(node.name)
215
366
  val = node.value.perform(@environment)
216
367
  node.resolved_value = val.to_s
368
+ node.value_source_range = val.source_range if val.source_range
217
369
  yield
218
370
  end
219
371
 
@@ -225,35 +377,82 @@ END
225
377
  # Runs SassScript interpolation in the selector,
226
378
  # and then parses the result into a {Sass::Selector::CommaSequence}.
227
379
  def visit_rule(node)
228
- parser = Sass::SCSS::StaticParser.new(run_interp(node.rule), node.line)
229
- node.parsed_rules ||= parser.parse_selector(node.filename)
380
+ old_at_root_without_rule, @at_root_without_rule = @at_root_without_rule, false
381
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.rule),
382
+ node.filename, node.options[:importer], node.line)
383
+ node.parsed_rules ||= parser.parse_selector
384
+ node.resolved_rules = node.parsed_rules.resolve_parent_refs(
385
+ @environment.selector, !old_at_root_without_rule)
386
+ node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
387
+ with_environment Sass::Environment.new(@environment, node.options) do
388
+ @environment.selector = node.resolved_rules
389
+ node.children = node.children.map {|c| visit(c)}.flatten
390
+ end
391
+ node
392
+ ensure
393
+ @at_root_without_rule = old_at_root_without_rule
394
+ end
395
+
396
+ # Sets a variable that indicates that the first level of rule nodes
397
+ # shouldn't include the parent selector by default.
398
+ def visit_atroot(node)
399
+ if node.query
400
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
401
+ node.filename, node.options[:importer], node.line)
402
+ node.resolved_type, node.resolved_value = parser.parse_static_at_root_query
403
+ else
404
+ node.resolved_type, node.resolved_value = :without, ['rule']
405
+ end
406
+
407
+ old_at_root_without_rule = @at_root_without_rule
408
+ @at_root_without_rule = true if node.exclude?('rule')
230
409
  yield
410
+ ensure
411
+ @at_root_without_rule = old_at_root_without_rule
231
412
  end
232
413
 
233
414
  # Loads the new variable value into the environment.
234
415
  def visit_variable(node)
235
- return [] if node.guarded && !@environment.var(node.name).nil?
416
+ env = @environment
417
+ identifier = [node.name, node.filename, node.line]
418
+ if node.global
419
+ env = env.global_env
420
+ elsif env.parent && env.is_var_global?(node.name) &&
421
+ !env.global_env.global_warning_given.include?(identifier)
422
+ env.global_env.global_warning_given.add(identifier)
423
+ var_expr = "$#{node.name}: #{node.expr.to_sass(env.options)} !global"
424
+ var_expr << " !default" if node.guarded
425
+ location = "on line #{node.line}"
426
+ location << " of #{node.filename}" if node.filename
427
+ Sass::Util.sass_warn <<WARNING
428
+ DEPRECATION WARNING #{location}:
429
+ Assigning to global variable "$#{node.name}" by default is deprecated.
430
+ In future versions of Sass, this will create a new local variable.
431
+ If you want to assign to the global variable, use "#{var_expr}" instead.
432
+ Note that this will be incompatible with Sass 3.2.
433
+ WARNING
434
+ end
435
+
436
+ var = env.var(node.name)
437
+ return [] if node.guarded && var && !var.null?
236
438
  val = node.expr.perform(@environment)
237
- @environment.set_var(node.name, val)
439
+ if node.expr.source_range
440
+ val.source_range = node.expr.source_range
441
+ else
442
+ val.source_range = node.source_range
443
+ end
444
+ env.set_var(node.name, val)
238
445
  []
239
446
  end
240
447
 
241
448
  # Prints the expression to STDERR with a stylesheet trace.
242
449
  def visit_warn(node)
243
- @environment.push_frame(:filename => node.filename, :line => node.line)
244
450
  res = node.expr.perform(@environment)
245
- res = res.value if res.is_a?(Sass::Script::String)
246
- msg = "WARNING: #{res}\n"
247
- @environment.stack.reverse.each_with_index do |entry, i|
248
- msg << " #{i == 0 ? "on" : "from"} line #{entry[:line]}" <<
249
- " of #{entry[:filename] || "an unknown file"}"
250
- msg << ", in `#{entry[:mixin]}'" if entry[:mixin]
251
- msg << "\n"
252
- end
451
+ res = res.value if res.is_a?(Sass::Script::Value::String)
452
+ msg = "WARNING: #{res}\n "
453
+ msg << @environment.stack.to_s.gsub("\n", "\n ") << "\n"
253
454
  Sass::Util.sass_warn msg
254
455
  []
255
- ensure
256
- @environment.pop_frame
257
456
  end
258
457
 
259
458
  # Runs the child nodes until the continuation expression becomes false.
@@ -266,35 +465,62 @@ END
266
465
  end
267
466
 
268
467
  def visit_directive(node)
269
- if node.value['#{']
270
- node.value = run_interp(Sass::Engine.parse_interp(node.value, node.line, 0, node.options))
468
+ node.resolved_value = run_interp(node.value)
469
+ with_environment Sass::Environment.new(@environment) do
470
+ node.children = node.children.map {|c| visit(c)}.flatten
471
+ node
472
+ end
473
+ end
474
+
475
+ def visit_media(node)
476
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
477
+ node.filename, node.options[:importer], node.line)
478
+ node.resolved_query ||= parser.parse_media_query_list
479
+ yield
480
+ end
481
+
482
+ def visit_supports(node)
483
+ node.condition = node.condition.deep_copy
484
+ node.condition.perform(@environment)
485
+ yield
486
+ end
487
+
488
+ def visit_cssimport(node)
489
+ node.resolved_uri = run_interp([node.uri])
490
+ if node.query
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
271
494
  end
272
495
  yield
273
- node
274
496
  end
275
497
 
276
498
  private
277
499
 
278
- def run_interp(text)
500
+ def run_interp_no_strip(text)
279
501
  text.map do |r|
280
502
  next r if r.is_a?(String)
281
503
  val = r.perform(@environment)
282
504
  # Interpolated strings should never render with quotes
283
- next val.value if val.is_a?(Sass::Script::String)
505
+ next val.value if val.is_a?(Sass::Script::Value::String)
284
506
  val.to_s
285
- end.join.strip
507
+ end.join
508
+ end
509
+
510
+ def run_interp(text)
511
+ run_interp_no_strip(text).strip
286
512
  end
287
513
 
288
- def handle_include_loop!(node)
289
- msg = "An @include loop has been found:"
290
- mixins = @environment.stack.map {|s| s[:mixin]}.compact
291
- if mixins.size == 2 && mixins[0] == mixins[1]
292
- raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself")
514
+ def handle_import_loop!(node)
515
+ msg = "An @import loop has been found:"
516
+ files = @environment.stack.frames.select {|f| f.is_import?}.map {|f| f.filename}.compact
517
+ if node.filename == node.imported_file.options[:filename]
518
+ raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself")
293
519
  end
294
520
 
295
- mixins << node.name
296
- msg << "\n" << Sass::Util.enum_cons(mixins, 2).map do |m1, m2|
297
- " #{m1} includes #{m2}"
521
+ files << node.filename << node.imported_file.options[:filename]
522
+ msg << "\n" << Sass::Util.enum_cons(files, 2).map do |m1, m2|
523
+ " #{m1} imports #{m2}"
298
524
  end.join("\n")
299
525
  raise Sass::SyntaxError.new(msg)
300
526
  end