sass 3.2.7 → 3.3.0.rc.1

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 (184) hide show
  1. data/MIT-LICENSE +2 -2
  2. data/README.md +14 -2
  3. data/Rakefile +25 -1
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/lib/sass/cache_stores/base.rb +4 -2
  8. data/lib/sass/cache_stores/chain.rb +2 -1
  9. data/lib/sass/cache_stores/filesystem.rb +2 -6
  10. data/lib/sass/cache_stores/memory.rb +1 -1
  11. data/lib/sass/cache_stores/null.rb +2 -2
  12. data/lib/sass/callbacks.rb +1 -0
  13. data/lib/sass/css.rb +10 -10
  14. data/lib/sass/engine.rb +403 -150
  15. data/lib/sass/environment.rb +136 -57
  16. data/lib/sass/error.rb +7 -7
  17. data/lib/sass/exec.rb +123 -39
  18. data/lib/sass/features.rb +41 -0
  19. data/lib/sass/importers/base.rb +33 -2
  20. data/lib/sass/importers/deprecated_path.rb +45 -0
  21. data/lib/sass/importers/filesystem.rb +25 -14
  22. data/lib/sass/importers.rb +1 -0
  23. data/lib/sass/logger/base.rb +3 -3
  24. data/lib/sass/logger/log_level.rb +4 -6
  25. data/lib/sass/media.rb +19 -19
  26. data/lib/sass/plugin/compiler.rb +141 -101
  27. data/lib/sass/plugin/configuration.rb +18 -22
  28. data/lib/sass/plugin/merb.rb +1 -1
  29. data/lib/sass/plugin/staleness_checker.rb +24 -8
  30. data/lib/sass/plugin.rb +4 -2
  31. data/lib/sass/repl.rb +3 -3
  32. data/lib/sass/script/css_lexer.rb +9 -4
  33. data/lib/sass/script/css_parser.rb +6 -2
  34. data/lib/sass/script/functions.rb +1343 -590
  35. data/lib/sass/script/lexer.rb +84 -52
  36. data/lib/sass/script/parser.rb +217 -97
  37. data/lib/sass/script/tree/funcall.rb +290 -0
  38. data/lib/sass/script/{interpolation.rb → tree/interpolation.rb} +34 -13
  39. data/lib/sass/script/tree/list_literal.rb +80 -0
  40. data/lib/sass/script/tree/literal.rb +47 -0
  41. data/lib/sass/script/tree/map_literal.rb +64 -0
  42. data/lib/sass/script/{node.rb → tree/node.rb} +22 -12
  43. data/lib/sass/script/{operation.rb → tree/operation.rb} +17 -25
  44. data/lib/sass/script/tree/selector.rb +30 -0
  45. data/lib/sass/script/{string_interpolation.rb → tree/string_interpolation.rb} +5 -4
  46. data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +14 -9
  47. data/lib/sass/script/tree/variable.rb +57 -0
  48. data/lib/sass/script/tree.rb +16 -0
  49. data/lib/sass/script/{arg_list.rb → value/arg_list.rb} +4 -24
  50. data/lib/sass/script/value/base.rb +248 -0
  51. data/lib/sass/script/value/bool.rb +36 -0
  52. data/lib/sass/script/{color.rb → value/color.rb} +239 -195
  53. data/lib/sass/script/value/helpers.rb +155 -0
  54. data/lib/sass/script/value/list.rb +119 -0
  55. data/lib/sass/script/value/map.rb +70 -0
  56. data/lib/sass/script/value/null.rb +45 -0
  57. data/lib/sass/script/{number.rb → value/number.rb} +91 -65
  58. data/lib/sass/script/{string.rb → value/string.rb} +9 -11
  59. data/lib/sass/script/value.rb +11 -0
  60. data/lib/sass/script.rb +35 -8
  61. data/lib/sass/scss/css_parser.rb +2 -1
  62. data/lib/sass/scss/parser.rb +338 -170
  63. data/lib/sass/scss/rx.rb +5 -6
  64. data/lib/sass/scss/script_lexer.rb +1 -0
  65. data/lib/sass/scss/script_parser.rb +1 -0
  66. data/lib/sass/scss/static_parser.rb +23 -6
  67. data/lib/sass/selector/abstract_sequence.rb +2 -2
  68. data/lib/sass/selector/comma_sequence.rb +21 -16
  69. data/lib/sass/selector/sequence.rb +60 -34
  70. data/lib/sass/selector/simple.rb +11 -12
  71. data/lib/sass/selector/simple_sequence.rb +55 -33
  72. data/lib/sass/selector.rb +52 -48
  73. data/lib/sass/source/map.rb +211 -0
  74. data/lib/sass/source/position.rb +39 -0
  75. data/lib/sass/source/range.rb +41 -0
  76. data/lib/sass/stack.rb +120 -0
  77. data/lib/sass/supports.rb +12 -13
  78. data/lib/sass/tree/at_root_node.rb +82 -0
  79. data/lib/sass/tree/comment_node.rb +3 -3
  80. data/lib/sass/tree/css_import_node.rb +11 -11
  81. data/lib/sass/tree/debug_node.rb +2 -2
  82. data/lib/sass/tree/directive_node.rb +13 -2
  83. data/lib/sass/tree/each_node.rb +8 -8
  84. data/lib/sass/tree/extend_node.rb +13 -6
  85. data/lib/sass/tree/for_node.rb +4 -4
  86. data/lib/sass/tree/function_node.rb +5 -4
  87. data/lib/sass/tree/if_node.rb +1 -1
  88. data/lib/sass/tree/import_node.rb +4 -5
  89. data/lib/sass/tree/media_node.rb +4 -14
  90. data/lib/sass/tree/mixin_def_node.rb +4 -4
  91. data/lib/sass/tree/mixin_node.rb +21 -8
  92. data/lib/sass/tree/node.rb +29 -12
  93. data/lib/sass/tree/prop_node.rb +38 -18
  94. data/lib/sass/tree/return_node.rb +3 -2
  95. data/lib/sass/tree/root_node.rb +19 -3
  96. data/lib/sass/tree/rule_node.rb +25 -17
  97. data/lib/sass/tree/supports_node.rb +0 -13
  98. data/lib/sass/tree/trace_node.rb +2 -1
  99. data/lib/sass/tree/variable_node.rb +9 -3
  100. data/lib/sass/tree/visitors/base.rb +6 -6
  101. data/lib/sass/tree/visitors/check_nesting.rb +12 -9
  102. data/lib/sass/tree/visitors/convert.rb +63 -38
  103. data/lib/sass/tree/visitors/cssize.rb +63 -23
  104. data/lib/sass/tree/visitors/deep_copy.rb +6 -5
  105. data/lib/sass/tree/visitors/extend.rb +7 -7
  106. data/lib/sass/tree/visitors/perform.rb +256 -151
  107. data/lib/sass/tree/visitors/set_options.rb +6 -6
  108. data/lib/sass/tree/visitors/to_css.rb +231 -81
  109. data/lib/sass/tree/warn_node.rb +2 -2
  110. data/lib/sass/tree/while_node.rb +2 -2
  111. data/lib/sass/util/multibyte_string_scanner.rb +2 -0
  112. data/lib/sass/util/normalized_map.rb +65 -0
  113. data/lib/sass/util/ordered_hash.rb +188 -0
  114. data/lib/sass/util/subset_map.rb +3 -2
  115. data/lib/sass/util/test.rb +9 -0
  116. data/lib/sass/util.rb +220 -34
  117. data/lib/sass/version.rb +9 -9
  118. data/lib/sass.rb +14 -7
  119. data/test/sass/compiler_test.rb +213 -0
  120. data/test/sass/conversion_test.rb +235 -9
  121. data/test/sass/engine_test.rb +230 -60
  122. data/test/sass/exec_test.rb +86 -0
  123. data/test/sass/extend_test.rb +215 -147
  124. data/test/sass/functions_test.rb +584 -99
  125. data/test/sass/importer_test.rb +165 -17
  126. data/test/sass/plugin_test.rb +19 -13
  127. data/test/sass/script_conversion_test.rb +40 -0
  128. data/test/sass/script_test.rb +231 -21
  129. data/test/sass/scss/css_test.rb +14 -5
  130. data/test/sass/scss/scss_test.rb +1266 -66
  131. data/test/sass/source_map_test.rb +879 -0
  132. data/test/sass/templates/bork5.sass +3 -0
  133. data/test/sass/util/normalized_map_test.rb +30 -0
  134. data/test/sass/util_test.rb +90 -0
  135. data/test/sass/value_helpers_test.rb +181 -0
  136. data/test/test_helper.rb +7 -2
  137. metadata +316 -291
  138. data/lib/sass/script/bool.rb +0 -18
  139. data/lib/sass/script/funcall.rb +0 -231
  140. data/lib/sass/script/list.rb +0 -84
  141. data/lib/sass/script/literal.rb +0 -239
  142. data/lib/sass/script/null.rb +0 -34
  143. data/lib/sass/script/variable.rb +0 -58
  144. data/test/Gemfile +0 -3
  145. data/vendor/listen/CHANGELOG.md +0 -221
  146. data/vendor/listen/CONTRIBUTING.md +0 -38
  147. data/vendor/listen/Gemfile +0 -30
  148. data/vendor/listen/Guardfile +0 -8
  149. data/vendor/listen/LICENSE +0 -20
  150. data/vendor/listen/README.md +0 -315
  151. data/vendor/listen/Rakefile +0 -47
  152. data/vendor/listen/Vagrantfile +0 -96
  153. data/vendor/listen/lib/listen/adapter.rb +0 -214
  154. data/vendor/listen/lib/listen/adapters/bsd.rb +0 -112
  155. data/vendor/listen/lib/listen/adapters/darwin.rb +0 -85
  156. data/vendor/listen/lib/listen/adapters/linux.rb +0 -113
  157. data/vendor/listen/lib/listen/adapters/polling.rb +0 -67
  158. data/vendor/listen/lib/listen/adapters/windows.rb +0 -87
  159. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  160. data/vendor/listen/lib/listen/directory_record.rb +0 -371
  161. data/vendor/listen/lib/listen/listener.rb +0 -225
  162. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  163. data/vendor/listen/lib/listen/turnstile.rb +0 -28
  164. data/vendor/listen/lib/listen/version.rb +0 -3
  165. data/vendor/listen/lib/listen.rb +0 -40
  166. data/vendor/listen/listen.gemspec +0 -22
  167. data/vendor/listen/spec/listen/adapter_spec.rb +0 -183
  168. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +0 -36
  169. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +0 -37
  170. data/vendor/listen/spec/listen/adapters/linux_spec.rb +0 -47
  171. data/vendor/listen/spec/listen/adapters/polling_spec.rb +0 -68
  172. data/vendor/listen/spec/listen/adapters/windows_spec.rb +0 -30
  173. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  174. data/vendor/listen/spec/listen/directory_record_spec.rb +0 -1225
  175. data/vendor/listen/spec/listen/listener_spec.rb +0 -169
  176. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
  177. data/vendor/listen/spec/listen/turnstile_spec.rb +0 -56
  178. data/vendor/listen/spec/listen_spec.rb +0 -73
  179. data/vendor/listen/spec/spec_helper.rb +0 -21
  180. data/vendor/listen/spec/support/adapter_helper.rb +0 -629
  181. data/vendor/listen/spec/support/directory_record_helper.rb +0 -55
  182. data/vendor/listen/spec/support/fixtures_helper.rb +0 -29
  183. data/vendor/listen/spec/support/listeners_helper.rb +0 -156
  184. data/vendor/listen/spec/support/platform_helper.rb +0 -15
@@ -1,103 +1,156 @@
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)
8
- end
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
9
39
 
10
- # @api private
11
- def self.perform_arguments(callable, args, keywords, splat)
12
- desc = "#{callable.type.capitalize} #{callable.name}"
13
- downcase_desc = "#{callable.type} #{callable.name}"
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
14
43
 
15
- begin
16
- unless keywords.empty?
17
- unknown_args = Sass::Util.array_minus(keywords.keys,
18
- callable.args.map {|var| var.first.underscored_name})
19
- if callable.splat && unknown_args.include?(callable.splat.underscored_name)
20
- raise Sass::SyntaxError.new("Argument $#{callable.splat.name} of #{downcase_desc} cannot be used as a named argument.")
21
- elsif unknown_args.any?
22
- description = unknown_args.length > 1 ? 'the following arguments:' : 'an argument named'
23
- raise Sass::SyntaxError.new("#{desc} doesn't have #{description} #{unknown_args.map {|name| "$#{name}"}.join ', '}.")
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
+ keywords = keywords.dup
59
+ env = Sass::Environment.new(callable.environment)
60
+ callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
61
+ if value && keywords.include?(var.underscored_name)
62
+ raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} " +
63
+ "both by position and by name.")
24
64
  end
65
+
66
+ value ||= keywords.delete(var.underscored_name)
67
+ value ||= default && default.perform(env)
68
+ raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
69
+ env.set_local_var(var.name, value)
25
70
  end
26
- rescue Sass::SyntaxError => keyword_exception
27
- end
28
71
 
29
- # If there's no splat, raise the keyword exception immediately. The actual
30
- # raising happens in the ensure clause at the end of this function.
31
- return if keyword_exception && !callable.splat
72
+ if callable.splat
73
+ rest = args[callable.args.length..-1] || []
74
+ arg_list = Sass::Script::Value::ArgList.new(rest, keywords.dup, splat_sep)
75
+ arg_list.options = env.options
76
+ env.set_local_var(callable.splat.name, arg_list)
77
+ end
32
78
 
33
- if args.size > callable.args.size && !callable.splat
34
- takes = callable.args.size
35
- passed = args.size
36
- raise Sass::SyntaxError.new(
37
- "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
38
- "but #{passed} #{passed == 1 ? 'was' : 'were'} passed.")
79
+ yield env
80
+ rescue StandardError => e
81
+ ensure
82
+ # If there's a keyword exception, we don't want to throw it immediately,
83
+ # because the invalid keywords may be part of a glob argument that should be
84
+ # passed on to another function. So we only raise it if we reach the end of
85
+ # this function *and* the keywords attached to the argument list glob object
86
+ # haven't been accessed.
87
+ #
88
+ # The keyword exception takes precedence over any Sass errors, but not over
89
+ # non-Sass exceptions.
90
+ if keyword_exception &&
91
+ !(arg_list && arg_list.keywords_accessed) &&
92
+ (e.nil? || e.is_a?(Sass::SyntaxError))
93
+ raise keyword_exception
94
+ elsif e
95
+ raise e
96
+ end
39
97
  end
40
98
 
41
- splat_sep = :comma
42
- if splat
43
- args += splat.to_a
44
- splat_sep = splat.separator if splat.is_a?(Sass::Script::List)
45
- # If the splat argument exists, there won't be any keywords passed in
46
- # manually, so we can safely overwrite rather than merge here.
47
- keywords = splat.keywords if splat.is_a?(Sass::Script::ArgList)
48
- end
99
+ # @api private
100
+ # @return [Sass::Script::Value::ArgList]
101
+ def perform_splat(splat, performed_keywords, kwarg_splat, environment)
102
+ args, kwargs, separator = [], Sass::Util.ordered_hash, :comma
103
+
104
+ if splat
105
+ splat = splat.perform(environment)
106
+ separator = splat.separator || separator
107
+ if splat.is_a?(Sass::Script::Value::ArgList)
108
+ args = splat.to_a
109
+ kwargs = splat.keywords
110
+ elsif splat.is_a?(Sass::Script::Value::Map)
111
+ kwargs = arg_hash(splat)
112
+ else
113
+ args = splat.to_a
114
+ end
115
+ end
49
116
 
50
- keywords = keywords.dup
51
- env = Sass::Environment.new(callable.environment)
52
- callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
53
- if value && keywords.include?(var.underscored_name)
54
- raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} both by position and by name.")
117
+ kwargs = kwargs.merge(performed_keywords)
118
+
119
+ if kwarg_splat
120
+ kwarg_splat = kwarg_splat.perform(environment)
121
+ unless kwarg_splat.is_a?(Sass::Script::Value::Map)
122
+ raise Sass::SyntaxError.new("Variable keyword arguments must be a map " +
123
+ "(was #{kwarg_splat.inspect}).")
124
+ end
125
+ kwargs = kwargs.merge(arg_hash(kwarg_splat))
55
126
  end
56
127
 
57
- value ||= keywords.delete(var.underscored_name)
58
- value ||= default && default.perform(env)
59
- raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
60
- env.set_local_var(var.name, value)
128
+ Sass::Script::Value::ArgList.new(args, kwargs, separator)
61
129
  end
62
130
 
63
- if callable.splat
64
- rest = args[callable.args.length..-1]
65
- arg_list = Sass::Script::ArgList.new(rest, keywords.dup, splat_sep)
66
- arg_list.options = env.options
67
- env.set_local_var(callable.splat.name, arg_list)
68
- end
131
+ private
69
132
 
70
- yield env
71
- rescue Exception => e
72
- ensure
73
- # If there's a keyword exception, we don't want to throw it immediately,
74
- # because the invalid keywords may be part of a glob argument that should be
75
- # passed on to another function. So we only raise it if we reach the end of
76
- # this function *and* the keywords attached to the argument list glob object
77
- # haven't been accessed.
78
- #
79
- # The keyword exception takes precedence over any Sass errors, but not over
80
- # non-Sass exceptions.
81
- if keyword_exception &&
82
- !(arg_list && arg_list.keywords_accessed) &&
83
- (e.nil? || e.is_a?(Sass::SyntaxError))
84
- raise keyword_exception
85
- elsif e
86
- raise e
133
+ def arg_hash(map)
134
+ Sass::Util.map_keys(map.to_h) do |key|
135
+ next key.value if key.is_a?(Sass::Script::Value::String)
136
+ raise Sass::SyntaxError.new("Variable keyword argument map must have string keys.\n" +
137
+ "#{key.inspect} is not a string in #{map.inspect}.")
138
+ end
87
139
  end
88
140
  end
141
+ # @comment
142
+ # rubocop:enable MethodLength
89
143
 
90
144
  protected
91
145
 
92
146
  def initialize(env)
93
147
  @environment = env
94
- # Stack trace information, including mixin includes and imports.
95
- @stack = []
96
148
  end
97
149
 
98
150
  # If an exception is raised, this adds proper metadata to the backtrace.
99
151
  def visit(node)
100
- super(node.dup)
152
+ return super(node.dup) unless @environment
153
+ @environment.stack.with_base(node.filename, node.line) {super(node.dup)}
101
154
  rescue Sass::SyntaxError => e
102
155
  e.modify_backtrace(:filename => node.filename, :line => node.line)
103
156
  raise e
@@ -142,11 +195,11 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
142
195
  # Prints the expression to STDERR.
143
196
  def visit_debug(node)
144
197
  res = node.expr.perform(@environment)
145
- res = res.value if res.is_a?(Sass::Script::String)
198
+ res = res.value if res.is_a?(Sass::Script::Value::String)
146
199
  if node.filename
147
- $stderr.puts "#{node.filename}:#{node.line} DEBUG: #{res}"
200
+ Sass::Util.sass_warn "#{node.filename}:#{node.line} DEBUG: #{res}"
148
201
  else
149
- $stderr.puts "Line #{node.line} DEBUG: #{res}"
202
+ Sass::Util.sass_warn "Line #{node.line} DEBUG: #{res}"
150
203
  end
151
204
  []
152
205
  end
@@ -156,8 +209,14 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
156
209
  list = node.list.perform(@environment)
157
210
 
158
211
  with_environment Sass::Environment.new(@environment) do
159
- list.to_a.map do |v|
160
- @environment.set_local_var(node.var, v)
212
+ list.to_a.map do |value|
213
+ if node.vars.length == 1
214
+ @environment.set_local_var(node.vars.first, value)
215
+ else
216
+ node.vars.zip(value.to_a) do |(var, sub_value)|
217
+ @environment.set_local_var(var, sub_value || Sass::Script::Value::Null.new)
218
+ end
219
+ end
161
220
  node.children.map {|c| visit(c)}
162
221
  end.flatten
163
222
  end
@@ -166,7 +225,8 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
166
225
  # Runs SassScript interpolation in the selector,
167
226
  # and then parses the result into a {Sass::Selector::CommaSequence}.
168
227
  def visit_extend(node)
169
- parser = Sass::SCSS::StaticParser.new(run_interp(node.selector), node.filename, node.line)
228
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.selector),
229
+ node.filename, node.options[:importer], node.line)
170
230
  node.resolved_selector = parser.parse_selector
171
231
  node
172
232
  end
@@ -184,7 +244,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
184
244
  with_environment Sass::Environment.new(@environment) do
185
245
  range.map do |i|
186
246
  @environment.set_local_var(node.var,
187
- Sass::Script::Number.new(i, from.numerator_units, from.denominator_units))
247
+ Sass::Script::Value::Number.new(i, from.numerator_units, from.denominator_units))
188
248
  node.children.map {|c| visit(c)}
189
249
  end.flatten
190
250
  end
@@ -194,7 +254,8 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
194
254
  def visit_function(node)
195
255
  env = Sass::Environment.new(@environment, node.options)
196
256
  @environment.set_local_function(node.name,
197
- Sass::Callable.new(node.name, node.args, node.splat, env, node.children, !:has_content, "function"))
257
+ Sass::Callable.new(node.name, node.args, node.splat, env,
258
+ node.children, !:has_content, "function"))
198
259
  []
199
260
  end
200
261
 
@@ -214,59 +275,67 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
214
275
  # Returns a static DirectiveNode if this is importing a CSS file,
215
276
  # or parses and includes the imported Sass file.
216
277
  def visit_import(node)
217
- if path = node.css_import?
218
- return Sass::Tree::CssImportNode.resolved("url(#{path})")
278
+ if (path = node.css_import?)
279
+ resolved_node = Sass::Tree::CssImportNode.resolved("url(#{path})")
280
+ resolved_node.source_range = node.source_range
281
+ return resolved_node
219
282
  end
220
283
  file = node.imported_file
221
- handle_import_loop!(node) if @stack.any? {|e| e[:filename] == file.options[:filename]}
284
+ if @environment.stack.frames.any? {|f| f.is_import? && f.filename == file.options[:filename]}
285
+ handle_import_loop!(node)
286
+ end
222
287
 
223
288
  begin
224
- @stack.push(:filename => node.filename, :line => node.line)
225
- root = file.to_tree
226
- Sass::Tree::Visitors::CheckNesting.visit(root)
227
- node.children = root.children.map {|c| visit(c)}.flatten
228
- node
289
+ @environment.stack.with_import(node.filename, node.line) do
290
+ root = file.to_tree
291
+ Sass::Tree::Visitors::CheckNesting.visit(root)
292
+ node.children = root.children.map {|c| visit(c)}.flatten
293
+ node
294
+ end
229
295
  rescue Sass::SyntaxError => e
230
296
  e.modify_backtrace(:filename => node.imported_file.options[:filename])
231
297
  e.add_backtrace(:filename => node.filename, :line => node.line)
232
298
  raise e
233
299
  end
234
- ensure
235
- @stack.pop unless path
236
300
  end
237
301
 
238
302
  # Loads a mixin into the environment.
239
303
  def visit_mixindef(node)
240
304
  env = Sass::Environment.new(@environment, node.options)
241
305
  @environment.set_local_mixin(node.name,
242
- Sass::Callable.new(node.name, node.args, node.splat, env, node.children, node.has_content, "mixin"))
306
+ Sass::Callable.new(node.name, node.args, node.splat, env,
307
+ node.children, node.has_content, "mixin"))
243
308
  []
244
309
  end
245
310
 
246
311
  # Runs a mixin.
247
312
  def visit_mixin(node)
248
313
  include_loop = true
249
- handle_include_loop!(node) if @stack.any? {|e| e[:name] == node.name}
314
+ if @environment.stack.frames.any? {|f| f.is_mixin? && f.name == node.name}
315
+ handle_include_loop!(node)
316
+ end
250
317
  include_loop = false
251
318
 
252
- @stack.push(:filename => node.filename, :line => node.line, :name => node.name)
253
- raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin = @environment.mixin(node.name)
319
+ @environment.stack.with_mixin(node.filename, node.line, node.name) do
320
+ mixin = @environment.mixin(node.name)
321
+ raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin
254
322
 
255
- if node.children.any? && !mixin.has_content
256
- raise Sass::SyntaxError.new(%Q{Mixin "#{node.name}" does not accept a content block.})
257
- end
323
+ if node.children.any? && !mixin.has_content
324
+ raise Sass::SyntaxError.new(%Q{Mixin "#{node.name}" does not accept a content block.})
325
+ end
258
326
 
259
- args = node.args.map {|a| a.perform(@environment)}
260
- keywords = Sass::Util.map_hash(node.keywords) {|k, v| [k, v.perform(@environment)]}
261
- splat = node.splat.perform(@environment) if node.splat
327
+ args = node.args.map {|a| a.perform(@environment)}
328
+ keywords = Sass::Util.map_hash(node.keywords) {|k, v| [k, v.perform(@environment)]}
329
+ splat = self.class.perform_splat(node.splat, keywords, node.kwarg_splat, @environment)
262
330
 
263
- self.class.perform_arguments(mixin, args, keywords, splat) do |env|
264
- env.caller = Sass::Environment.new(@environment)
265
- env.content = node.children if node.has_children
331
+ self.class.perform_arguments(mixin, args, splat) do |env|
332
+ env.caller = Sass::Environment.new(@environment)
333
+ env.content = [node.children, @environment] if node.has_children
266
334
 
267
- trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
268
- with_environment(env) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
269
- trace_node
335
+ trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
336
+ with_environment(env) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
337
+ trace_node
338
+ end
270
339
  end
271
340
  rescue Sass::SyntaxError => e
272
341
  unless include_loop
@@ -274,22 +343,24 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
274
343
  e.add_backtrace(:line => node.line)
275
344
  end
276
345
  raise e
277
- ensure
278
- @stack.pop unless include_loop
279
346
  end
280
347
 
281
348
  def visit_content(node)
282
- return [] unless content = @environment.content
283
- @stack.push(:filename => node.filename, :line => node.line, :name => '@content')
284
- trace_node = Sass::Tree::TraceNode.from_node('@content', node)
285
- with_environment(@environment.caller) {trace_node.children = content.map {|c| visit(c.dup)}.flatten}
286
- trace_node
349
+ content, content_env = @environment.content
350
+ return [] unless content
351
+ @environment.stack.with_mixin(node.filename, node.line, '@content') do
352
+ trace_node = Sass::Tree::TraceNode.from_node('@content', node)
353
+ content_env = Sass::Environment.new(content_env)
354
+ content_env.caller = Sass::Environment.new(@environment)
355
+ with_environment(content_env) do
356
+ trace_node.children = content.map {|c| visit(c.dup)}.flatten
357
+ end
358
+ trace_node
359
+ end
287
360
  rescue Sass::SyntaxError => e
288
361
  e.modify_backtrace(:mixin => '@content', :line => node.line)
289
362
  e.add_backtrace(:line => node.line)
290
363
  raise e
291
- ensure
292
- @stack.pop if content
293
364
  end
294
365
 
295
366
  # Runs any SassScript that may be embedded in a property.
@@ -297,6 +368,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
297
368
  node.resolved_name = run_interp(node.name)
298
369
  val = node.value.perform(@environment)
299
370
  node.resolved_value = val.to_s
371
+ node.value_source_range = val.source_range if val.source_range
300
372
  yield
301
373
  end
302
374
 
@@ -308,38 +380,78 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
308
380
  # Runs SassScript interpolation in the selector,
309
381
  # and then parses the result into a {Sass::Selector::CommaSequence}.
310
382
  def visit_rule(node)
311
- rule = node.rule
312
- rule = rule.map {|e| e.is_a?(String) && e != ' ' ? e.strip : e} if node.style == :compressed
313
- parser = Sass::SCSS::StaticParser.new(run_interp(node.rule), node.filename, node.line)
383
+ old_at_root_without_rule, @at_root_without_rule = @at_root_without_rule, false
384
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.rule),
385
+ node.filename, node.options[:importer], node.line)
314
386
  node.parsed_rules ||= parser.parse_selector
315
- if node.options[:trace_selectors]
316
- @stack.push(:filename => node.filename, :line => node.line)
317
- node.stack_trace = stack_trace
318
- @stack.pop
387
+ node.resolved_rules = node.parsed_rules.resolve_parent_refs(
388
+ @environment.selector, !old_at_root_without_rule)
389
+ node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
390
+ with_environment Sass::Environment.new(@environment, node.options) do
391
+ @environment.selector = node.resolved_rules
392
+ node.children = node.children.map {|c| visit(c)}.flatten
393
+ end
394
+ node
395
+ ensure
396
+ @at_root_without_rule = old_at_root_without_rule
397
+ end
398
+
399
+ # Sets a variable that indicates that the first level of rule nodes
400
+ # shouldn't include the parent selector by default.
401
+ def visit_atroot(node)
402
+ if node.query
403
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
404
+ node.filename, node.options[:importer], node.line)
405
+ node.resolved_type, node.resolved_value = parser.parse_static_at_root_query
406
+ else
407
+ node.resolved_type, node.resolved_value = :without, ['rule']
319
408
  end
409
+
410
+ old_at_root_without_rule = @at_root_without_rule
411
+ @at_root_without_rule = true if node.exclude?('rule')
320
412
  yield
413
+ ensure
414
+ @at_root_without_rule = old_at_root_without_rule
321
415
  end
322
416
 
323
417
  # Loads the new variable value into the environment.
324
418
  def visit_variable(node)
325
- var = @environment.var(node.name)
419
+ env = @environment
420
+ if node.global
421
+ env = env.global_env
422
+ elsif env.parent && env.is_var_global?(node.name)
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
+ WARNING
433
+ end
434
+
435
+ var = env.var(node.name)
326
436
  return [] if node.guarded && var && !var.null?
327
437
  val = node.expr.perform(@environment)
328
- @environment.set_var(node.name, val)
438
+ if node.expr.source_range
439
+ val.source_range = node.expr.source_range
440
+ else
441
+ val.source_range = node.source_range
442
+ end
443
+ env.set_var(node.name, val)
329
444
  []
330
445
  end
331
446
 
332
447
  # Prints the expression to STDERR with a stylesheet trace.
333
448
  def visit_warn(node)
334
- @stack.push(:filename => node.filename, :line => node.line)
335
449
  res = node.expr.perform(@environment)
336
- res = res.value if res.is_a?(Sass::Script::String)
450
+ res = res.value if res.is_a?(Sass::Script::Value::String)
337
451
  msg = "WARNING: #{res}\n "
338
- msg << stack_trace.join("\n ") << "\n"
452
+ msg << @environment.stack.to_s.gsub("\n", "\n ") << "\n"
339
453
  Sass::Util.sass_warn msg
340
454
  []
341
- ensure
342
- @stack.pop
343
455
  end
344
456
 
345
457
  # Runs the child nodes until the continuation expression becomes false.
@@ -353,11 +465,15 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
353
465
 
354
466
  def visit_directive(node)
355
467
  node.resolved_value = run_interp(node.value)
356
- yield
468
+ with_environment Sass::Environment.new(@environment) do
469
+ node.children = node.children.map {|c| visit(c)}.flatten
470
+ node
471
+ end
357
472
  end
358
473
 
359
474
  def visit_media(node)
360
- parser = Sass::SCSS::StaticParser.new(run_interp(node.query), node.filename, node.line)
475
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
476
+ node.filename, node.options[:importer], node.line)
361
477
  node.resolved_query ||= parser.parse_media_query_list
362
478
  yield
363
479
  end
@@ -371,7 +487,8 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
371
487
  def visit_cssimport(node)
372
488
  node.resolved_uri = run_interp([node.uri])
373
489
  if node.query
374
- parser = Sass::SCSS::StaticParser.new(run_interp(node.query), node.filename, node.line)
490
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
491
+ node.filename, node.options[:importer], node.line)
375
492
  node.resolved_query ||= parser.parse_media_query_list
376
493
  end
377
494
  yield
@@ -379,25 +496,12 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
379
496
 
380
497
  private
381
498
 
382
- def stack_trace
383
- trace = []
384
- stack = @stack.map {|e| e.dup}.reverse
385
- stack.each_cons(2) {|(e1, e2)| e1[:caller] = e2[:name]; [e1, e2]}
386
- stack.each_with_index do |entry, i|
387
- msg = "#{i == 0 ? "on" : "from"} line #{entry[:line]}"
388
- msg << " of #{entry[:filename] || "an unknown file"}"
389
- msg << ", in `#{entry[:caller]}'" if entry[:caller]
390
- trace << msg
391
- end
392
- trace
393
- end
394
-
395
499
  def run_interp_no_strip(text)
396
500
  text.map do |r|
397
501
  next r if r.is_a?(String)
398
502
  val = r.perform(@environment)
399
503
  # Interpolated strings should never render with quotes
400
- next val.value if val.is_a?(Sass::Script::String)
504
+ next val.value if val.is_a?(Sass::Script::Value::String)
401
505
  val.to_s
402
506
  end.join
403
507
  end
@@ -409,8 +513,9 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
409
513
  def handle_include_loop!(node)
410
514
  msg = "An @include loop has been found:"
411
515
  content_count = 0
412
- mixins = @stack.reverse.map {|s| s[:name]}.compact.select do |s|
413
- if s == '@content'
516
+ mixins = @environment.stack.frames.select {|f| f.is_mixin?}.reverse!.map! {|f| f.name}
517
+ mixins = mixins.select do |name|
518
+ if name == '@content'
414
519
  content_count += 1
415
520
  false
416
521
  elsif content_count > 0
@@ -421,7 +526,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
421
526
  end
422
527
  end
423
528
 
424
- return if mixins.empty?
529
+ return unless mixins.include?(node.name)
425
530
  raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1
426
531
 
427
532
  msg << "\n" << Sass::Util.enum_cons(mixins.reverse + [node.name], 2).map do |m1, m2|
@@ -432,7 +537,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
432
537
 
433
538
  def handle_import_loop!(node)
434
539
  msg = "An @import loop has been found:"
435
- files = @stack.map {|s| s[:filename]}.compact
540
+ files = @environment.stack.frames.select {|f| f.is_import?}.map {|f| f.filename}.compact
436
541
  if node.filename == node.imported_file.options[:filename]
437
542
  raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself")
438
543
  end
@@ -26,7 +26,7 @@ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
26
26
  end
27
27
 
28
28
  def visit_extend(node)
29
- node.selector.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
29
+ node.selector.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
30
30
  yield
31
31
  end
32
32
 
@@ -73,7 +73,7 @@ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
73
73
  end
74
74
 
75
75
  def visit_prop(node)
76
- node.name.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
76
+ node.name.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
77
77
  node.value.options = @options
78
78
  yield
79
79
  end
@@ -84,7 +84,7 @@ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
84
84
  end
85
85
 
86
86
  def visit_rule(node)
87
- node.rule.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
87
+ node.rule.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
88
88
  yield
89
89
  end
90
90
 
@@ -104,17 +104,17 @@ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
104
104
  end
105
105
 
106
106
  def visit_directive(node)
107
- node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
107
+ node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
108
108
  yield
109
109
  end
110
110
 
111
111
  def visit_media(node)
112
- node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
112
+ node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
113
113
  yield
114
114
  end
115
115
 
116
116
  def visit_cssimport(node)
117
- node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)} if node.query
117
+ node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)} if node.query
118
118
  yield
119
119
  end
120
120