sass 3.3.0.rc.2 → 3.3.0.rc.3

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 (76) hide show
  1. checksums.yaml +15 -0
  2. data/CONTRIBUTING +1 -1
  3. data/README.md +7 -7
  4. data/Rakefile +4 -2
  5. data/VERSION +1 -1
  6. data/VERSION_DATE +1 -1
  7. data/bin/sass +5 -1
  8. data/bin/sass-convert +5 -1
  9. data/bin/scss +5 -1
  10. data/ext/mkrf_conf.rb +23 -0
  11. data/lib/sass/css.rb +1 -1
  12. data/lib/sass/engine.rb +19 -9
  13. data/lib/sass/environment.rb +8 -0
  14. data/lib/sass/exec.rb +4 -4
  15. data/lib/sass/features.rb +0 -1
  16. data/lib/sass/importers/base.rb +13 -6
  17. data/lib/sass/importers/deprecated_path.rb +8 -2
  18. data/lib/sass/importers/filesystem.rb +33 -7
  19. data/lib/sass/logger.rb +1 -4
  20. data/lib/sass/logger/base.rb +0 -2
  21. data/lib/sass/logger/log_level.rb +0 -2
  22. data/lib/sass/plugin.rb +2 -2
  23. data/lib/sass/plugin/compiler.rb +23 -11
  24. data/lib/sass/plugin/configuration.rb +0 -1
  25. data/lib/sass/railtie.rb +1 -0
  26. data/lib/sass/script/css_lexer.rb +0 -1
  27. data/lib/sass/script/css_parser.rb +0 -1
  28. data/lib/sass/script/functions.rb +158 -96
  29. data/lib/sass/script/lexer.rb +29 -35
  30. data/lib/sass/script/parser.rb +10 -3
  31. data/lib/sass/script/tree.rb +0 -1
  32. data/lib/sass/script/tree/funcall.rb +21 -5
  33. data/lib/sass/script/tree/list_literal.rb +0 -1
  34. data/lib/sass/script/value/arg_list.rb +7 -3
  35. data/lib/sass/script/value/bool.rb +0 -1
  36. data/lib/sass/script/value/null.rb +0 -1
  37. data/lib/sass/script/value/number.rb +2 -6
  38. data/lib/sass/scss/css_parser.rb +0 -1
  39. data/lib/sass/scss/parser.rb +5 -5
  40. data/lib/sass/scss/script_lexer.rb +0 -1
  41. data/lib/sass/scss/script_parser.rb +0 -1
  42. data/lib/sass/selector.rb +11 -1
  43. data/lib/sass/selector/comma_sequence.rb +3 -4
  44. data/lib/sass/selector/sequence.rb +11 -7
  45. data/lib/sass/selector/simple_sequence.rb +42 -11
  46. data/lib/sass/source/map.rb +6 -19
  47. data/lib/sass/tree/at_root_node.rb +1 -1
  48. data/lib/sass/tree/mixin_node.rb +2 -2
  49. data/lib/sass/tree/prop_node.rb +0 -1
  50. data/lib/sass/tree/variable_node.rb +0 -5
  51. data/lib/sass/tree/visitors/check_nesting.rb +0 -1
  52. data/lib/sass/tree/visitors/convert.rb +2 -2
  53. data/lib/sass/tree/visitors/cssize.rb +184 -84
  54. data/lib/sass/tree/visitors/deep_copy.rb +0 -1
  55. data/lib/sass/tree/visitors/perform.rb +14 -44
  56. data/lib/sass/util.rb +59 -12
  57. data/lib/sass/util/cross_platform_random.rb +19 -0
  58. data/lib/sass/util/normalized_map.rb +17 -1
  59. data/test/sass/compiler_test.rb +10 -0
  60. data/test/sass/conversion_test.rb +36 -0
  61. data/test/sass/css2sass_test.rb +19 -0
  62. data/test/sass/engine_test.rb +54 -105
  63. data/test/sass/functions_test.rb +233 -26
  64. data/test/sass/importer_test.rb +72 -10
  65. data/test/sass/plugin_test.rb +14 -0
  66. data/test/sass/script_conversion_test.rb +4 -4
  67. data/test/sass/script_test.rb +58 -21
  68. data/test/sass/scss/css_test.rb +8 -1
  69. data/test/sass/scss/scss_test.rb +376 -179
  70. data/test/sass/source_map_test.rb +8 -0
  71. data/test/sass/templates/subdir/import_up1.scss +1 -0
  72. data/test/sass/templates/subdir/import_up2.scss +1 -0
  73. data/test/sass/util_test.rb +16 -0
  74. data/test/test_helper.rb +12 -4
  75. metadata +269 -287
  76. data/lib/sass/script/tree/selector.rb +0 -30
@@ -1,6 +1,5 @@
1
1
  # A visitor for copying the full structure of a Sass tree.
2
2
  class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
3
-
4
3
  protected
5
4
 
6
5
  def visit(node)
@@ -55,15 +55,14 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
55
55
  splat_sep = splat.separator
56
56
  end
57
57
 
58
- keywords = keywords.dup
59
58
  env = Sass::Environment.new(callable.environment)
60
59
  callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
61
- if value && keywords.include?(var.underscored_name)
60
+ if value && keywords.has_key?(var.name)
62
61
  raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} " +
63
62
  "both by position and by name.")
64
63
  end
65
64
 
66
- value ||= keywords.delete(var.underscored_name)
65
+ value ||= keywords.delete(var.name)
67
66
  value ||= default && default.perform(env)
68
67
  raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
69
68
  env.set_local_var(var.name, value)
@@ -71,7 +70,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
71
70
 
72
71
  if callable.splat
73
72
  rest = args[callable.args.length..-1] || []
74
- arg_list = Sass::Script::Value::ArgList.new(rest, keywords.dup, splat_sep)
73
+ arg_list = Sass::Script::Value::ArgList.new(rest, keywords, splat_sep)
75
74
  arg_list.options = env.options
76
75
  env.set_local_var(callable.splat.name, arg_list)
77
76
  end
@@ -113,7 +112,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
113
112
  args = splat.to_a
114
113
  end
115
114
  end
116
- kwargs ||= Sass::Util.ordered_hash
115
+ kwargs ||= Sass::Util::NormalizedMap.new
117
116
  kwargs.update(performed_keywords)
118
117
 
119
118
  if kwarg_splat
@@ -243,12 +242,14 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
243
242
  to.assert_int!
244
243
 
245
244
  to = to.coerce(from.numerator_units, from.denominator_units)
246
- 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)
247
247
 
248
248
  with_environment Sass::Environment.new(@environment) do
249
249
  range.map do |i|
250
250
  @environment.set_local_var(node.var,
251
- Sass::Script::Value::Number.new(i, from.numerator_units, from.denominator_units))
251
+ Sass::Script::Value::Number.new(direction * i,
252
+ from.numerator_units, from.denominator_units))
252
253
  node.children.map {|c| visit(c)}
253
254
  end.flatten
254
255
  end
@@ -314,12 +315,6 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
314
315
 
315
316
  # Runs a mixin.
316
317
  def visit_mixin(node)
317
- include_loop = true
318
- if @environment.stack.frames.any? {|f| f.is_mixin? && f.name == node.name}
319
- handle_include_loop!(node)
320
- end
321
- include_loop = false
322
-
323
318
  @environment.stack.with_mixin(node.filename, node.line, node.name) do
324
319
  mixin = @environment.mixin(node.name)
325
320
  raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin
@@ -342,10 +337,8 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
342
337
  end
343
338
  end
344
339
  rescue Sass::SyntaxError => e
345
- unless include_loop
346
- e.modify_backtrace(:mixin => node.name, :line => node.line)
347
- e.add_backtrace(:line => node.line)
348
- end
340
+ e.modify_backtrace(:mixin => node.name, :line => node.line)
341
+ e.add_backtrace(:line => node.line)
349
342
  raise e
350
343
  end
351
344
 
@@ -421,10 +414,12 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
421
414
  # Loads the new variable value into the environment.
422
415
  def visit_variable(node)
423
416
  env = @environment
417
+ identifier = [node.name, node.filename, node.line]
424
418
  if node.global
425
419
  env = env.global_env
426
- elsif env.parent && env.is_var_global?(node.name) && !node.global_warning_given
427
- node.global_warning_given = true
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)
428
423
  var_expr = "$#{node.name}: #{node.expr.to_sass(env.options)} !global"
429
424
  var_expr << " !default" if node.guarded
430
425
  location = "on line #{node.line}"
@@ -515,31 +510,6 @@ WARNING
515
510
  run_interp_no_strip(text).strip
516
511
  end
517
512
 
518
- def handle_include_loop!(node)
519
- msg = "An @include loop has been found:"
520
- content_count = 0
521
- mixins = @environment.stack.frames.select {|f| f.is_mixin?}.reverse!.map! {|f| f.name}
522
- mixins = mixins.select do |name|
523
- if name == '@content'
524
- content_count += 1
525
- false
526
- elsif content_count > 0
527
- content_count -= 1
528
- false
529
- else
530
- true
531
- end
532
- end
533
-
534
- return unless mixins.include?(node.name)
535
- raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1
536
-
537
- msg << "\n" << Sass::Util.enum_cons(mixins.reverse + [node.name], 2).map do |m1, m2|
538
- " #{m1} includes #{m2}"
539
- end.join("\n")
540
- raise Sass::SyntaxError.new(msg)
541
- end
542
-
543
513
  def handle_import_loop!(node)
544
514
  msg = "An @import loop has been found:"
545
515
  files = @environment.stack.frames.select {|f| f.is_import?}.map {|f| f.filename}.compact
@@ -74,6 +74,7 @@ module Sass
74
74
  # We don't delegate to map_hash for performance here
75
75
  # because map_hash does more than is necessary.
76
76
  rv = hash.class.new
77
+ hash = hash.as_stored if hash.is_a?(NormalizedMap)
77
78
  hash.each do |k, v|
78
79
  rv[k] = yield(v)
79
80
  end
@@ -99,7 +100,7 @@ module Sass
99
100
  rv = hash.class.new
100
101
  hash.each do |k, v|
101
102
  new_key, new_value = yield(k, v)
102
- rv.delete(k)
103
+ new_key = hash.denormalize(new_key) if hash.is_a?(NormalizedMap) && new_key == k
103
104
  rv[new_key] = new_value
104
105
  end
105
106
  rv
@@ -170,6 +171,19 @@ module Sass
170
171
  enum.inject([]) {|a, e| a << e << val}[0...-1]
171
172
  end
172
173
 
174
+ def slice_by(enum)
175
+ results = []
176
+ enum.each do |value|
177
+ key = yield(value)
178
+ if !results.empty? && results.last.first == key
179
+ results.last.last << value
180
+ else
181
+ results << [key, [value]]
182
+ end
183
+ end
184
+ results
185
+ end
186
+
173
187
  # Substitutes a sub-array of one array with another sub-array.
174
188
  #
175
189
  # @param ary [Array] The array in which to make the substitution
@@ -487,6 +501,15 @@ module Sass
487
501
  version_geq(ActionPack::VERSION::STRING, version)
488
502
  end
489
503
 
504
+ # Returns whether this environment is using Listen
505
+ # version 2.0.0 or greater.
506
+ #
507
+ # @return [Boolean]
508
+ def listen_geq_2?
509
+ require 'listen/version'
510
+ version_geq(::Listen::VERSION, '2.0.0')
511
+ end
512
+
490
513
  # Returns an ActionView::Template* class.
491
514
  # In pre-3.0 versions of Rails, most of these classes
492
515
  # were of the form `ActionView::TemplateFoo`,
@@ -537,15 +560,10 @@ module Sass
537
560
  @jruby = RUBY_PLATFORM =~ /java/
538
561
  end
539
562
 
540
- # @see #jruby_version-class_method
541
- def jruby_version
542
- Sass::Util.jruby_version
543
- end
544
-
545
563
  # Returns an array of ints representing the JRuby version number.
546
564
  #
547
565
  # @return [Array<Fixnum>]
548
- def self.jruby_version
566
+ def jruby_version
549
567
  @jruby_version ||= ::JRUBY_VERSION.split(".").map {|s| s.to_i}
550
568
  end
551
569
 
@@ -651,7 +669,6 @@ module Sass
651
669
  (pairs_or_hash.is_a?(NormalizedMap) ? NormalizedMap : OrderedHash)[*flatten(pairs_or_hash, 1)]
652
670
  end
653
671
 
654
-
655
672
  # Checks that the encoding of a string is valid in Ruby 1.9
656
673
  # and cleans up potential encoding gotchas like the UTF-8 BOM.
657
674
  # If it's not, yields an error string describing the invalid character
@@ -844,6 +861,24 @@ MSG
844
861
  arr.inject([]) {|res, e| e.is_a?(Array) ? res.concat(flatten(e, n - 1)) : res << e}
845
862
  end
846
863
 
864
+ # Flattens the first level of nested arrays in `arrs`. Unlike
865
+ # `Array#flatten`, this orders the result by taking the first
866
+ # values from each array in order, then the second, and so on.
867
+ #
868
+ # @param arrs [Array] The array to flatten.
869
+ # @return [Array] The flattened array.
870
+ def flatten_vertically(arrs)
871
+ result = []
872
+ arrs = arrs.map {|sub| sub.is_a?(Array) ? sub.dup : Array(sub)}
873
+ until arrs.empty?
874
+ arrs.reject! do |arr|
875
+ result << arr.shift
876
+ arr.empty?
877
+ end
878
+ end
879
+ result
880
+ end
881
+
847
882
  # Returns the hash code for a set in a cross-version manner.
848
883
  # Aggravatingly, this is order-dependent in Ruby 1.8.6.
849
884
  #
@@ -1040,6 +1075,20 @@ MSG
1040
1075
  URI_ESCAPE.escape string
1041
1076
  end
1042
1077
 
1078
+ # A cross-platform implementation of `File.absolute_path`.
1079
+ #
1080
+ # @param path [String]
1081
+ # @param dir_string [String] The directory to consider [path] relative to.
1082
+ # @return [String] The absolute version of `path`.
1083
+ def absolute_path(path, dir_string = nil)
1084
+ # Ruby 1.8 doesn't support File.absolute_path.
1085
+ return File.absolute_path(path, dir_string) unless ruby1_8?
1086
+
1087
+ # File.expand_path expands "~", which we don't want.
1088
+ return File.expand_path(path, dir_string) unless path[0] == ?~
1089
+ File.expand_path(File.join(".", path), dir_string)
1090
+ end
1091
+
1043
1092
  ## Static Method Stuff
1044
1093
 
1045
1094
  # The context in which the ERB for \{#def\_static\_method} will be run.
@@ -1091,7 +1140,6 @@ MSG
1091
1140
 
1092
1141
  # rubocop:disable LineLength
1093
1142
 
1094
-
1095
1143
  # Calculates the memoization table for the Least Common Subsequence algorithm.
1096
1144
  # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS)
1097
1145
  def lcs_table(x, y)
@@ -1112,10 +1160,8 @@ MSG
1112
1160
  end
1113
1161
  c
1114
1162
  end
1115
-
1116
1163
  # rubocop:disable ParameterLists, LineLength
1117
1164
 
1118
-
1119
1165
  # Computes a single longest common subsequence for arrays x and y.
1120
1166
  # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS)
1121
1167
  def lcs_backtrace(c, x, y, i, j, &block)
@@ -1129,9 +1175,10 @@ MSG
1129
1175
  lcs_backtrace(c, x, y, i - 1, j, &block)
1130
1176
  end
1131
1177
 
1132
- (Sass::Util.methods - Module.methods).each {|method| module_function method}
1178
+ singleton_methods.each {|method| module_function method}
1133
1179
  end
1134
1180
  end
1135
1181
 
1136
1182
  require 'sass/util/multibyte_string_scanner'
1137
1183
  require 'sass/util/normalized_map'
1184
+ require 'sass/util/cross_platform_random'
@@ -0,0 +1,19 @@
1
+ module Sass
2
+ module Util
3
+ # Ruby 1.8 doesn't support an actual Random class with a settable seed.
4
+ class CrossPlatformRandom
5
+ def initialize(seed = nil)
6
+ if Sass::Util.ruby1_8?
7
+ srand(seed) if seed
8
+ else
9
+ @random = seed ? ::Random.new(seed) : ::Random.new
10
+ end
11
+ end
12
+
13
+ def rand(*args)
14
+ return @random.rand(*args) if @random
15
+ Kernel.rand(*args)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -9,9 +9,11 @@ module Sass
9
9
  require 'sass/util/ordered_hash' if ruby1_8?
10
10
  class NormalizedMap
11
11
  # Create a normalized map
12
- def initialize
12
+ def initialize(map = nil)
13
13
  @key_strings = {}
14
14
  @map = Util.ruby1_8? ? OrderedHash.new : {}
15
+
16
+ map.each {|key, value| self[key] = value} if map
15
17
  end
16
18
 
17
19
  # Specifies how to transform the key.
@@ -21,6 +23,15 @@ module Sass
21
23
  key.tr("-", "_")
22
24
  end
23
25
 
26
+ # Returns the version of `key` as it was stored before
27
+ # normalization. If `key` isn't in the map, returns it as it was
28
+ # passed in.
29
+ #
30
+ # @return [String]
31
+ def denormalize(key)
32
+ @key_strings[normalize(key)] || key
33
+ end
34
+
24
35
  # @private
25
36
  def []=(k, v)
26
37
  normalized = normalize(k)
@@ -93,6 +104,11 @@ module Sass
93
104
  @map.sort_by {|k, v| yield k, v}
94
105
  end
95
106
 
107
+ def update(map)
108
+ map = map.as_stored if map.is_a?(NormalizedMap)
109
+ map.each {|k, v| self[k] = v}
110
+ end
111
+
96
112
  def method_missing(method, *args, &block)
97
113
  if Sass.tests_running
98
114
  raise ArgumentError.new("The method #{method} must be implemented explicitly")
@@ -9,6 +9,7 @@ class CompilerTest < Test::Unit::TestCase
9
9
  attr_accessor :options
10
10
  attr_accessor :directories
11
11
  attr_reader :start_called
12
+ attr_reader :thread
12
13
 
13
14
  def initialize(*args, &on_filesystem_event)
14
15
  self.options = args.last.is_a?(Hash) ? args.pop : {}
@@ -39,10 +40,19 @@ class CompilerTest < Test::Unit::TestCase
39
40
  @run_during_start = run_during_start
40
41
  end
41
42
 
43
+ # used for Listen < 2.0
42
44
  def start!
43
45
  @run_during_start.call(self) if @run_during_start
44
46
  end
45
47
 
48
+ # used for Listen >= 2.0
49
+ def start
50
+ @thread = Thread.new {@run_during_start.call(self) if @run_during_start}
51
+ end
52
+
53
+ def stop
54
+ end
55
+
46
56
  def reset_events!
47
57
  @modified = []
48
58
  @added = []
@@ -499,6 +499,29 @@ foo
499
499
  SASS
500
500
  end
501
501
 
502
+ def test_loud_comment_containing_silent_comment
503
+ assert_scss_to_sass <<SASS, <<SCSS
504
+ /*
505
+ *// foo bar
506
+ SASS
507
+ /*
508
+ // foo bar
509
+ */
510
+ SCSS
511
+ end
512
+
513
+ def test_silent_comment_containing_loud_comment
514
+ assert_scss_to_sass <<SASS, <<SCSS
515
+ // /*
516
+ // * foo bar
517
+ // */
518
+ SASS
519
+ // /*
520
+ // * foo bar
521
+ // */
522
+ SCSS
523
+ end
524
+
502
525
  def test_immediately_preceding_comments
503
526
  assert_renders <<SASS, <<SCSS
504
527
  /* Foo
@@ -1009,6 +1032,19 @@ foo {
1009
1032
  SCSS
1010
1033
  end
1011
1034
 
1035
+ def test_mixin_include_with_hyphen_conversion_keyword_arg
1036
+ assert_renders <<SASS, <<SCSS
1037
+ foo
1038
+ +foo-bar($a-b_c: val)
1039
+ a: blip
1040
+ SASS
1041
+ foo {
1042
+ @include foo-bar($a-b_c: val);
1043
+ a: blip;
1044
+ }
1045
+ SCSS
1046
+ end
1047
+
1012
1048
  def test_argless_function_definition
1013
1049
  assert_renders <<SASS, <<SCSS
1014
1050
  @function foo()
@@ -265,6 +265,25 @@ CSS
265
265
 
266
266
  # Regressions
267
267
 
268
+ def test_empty_rule
269
+ assert_equal(<<SASS, css2sass(<<CSS))
270
+ a
271
+ SASS
272
+ a {}
273
+ CSS
274
+ end
275
+
276
+ def test_empty_rule_with_selector_combinator
277
+ assert_equal(<<SASS, css2sass(<<CSS))
278
+ a
279
+ color: red
280
+ > b
281
+ SASS
282
+ a {color: red}
283
+ a > b {}
284
+ CSS
285
+ end
286
+
268
287
  def test_nesting_within_media
269
288
  assert_equal(<<SASS, css2sass(<<CSS))
270
289
  @media all
@@ -67,7 +67,7 @@ MSG
67
67
  "$a: 1b <= 2c" => "Incompatible units: 'c' and 'b'.",
68
68
  "$a: 1b >= 2c" => "Incompatible units: 'c' and 'b'.",
69
69
  "a\n b: 1b * 2c" => "2b*c isn't a valid CSS value.",
70
- "a\n b: 1b % 2c" => "Cannot modulo by a number with units: 2c.",
70
+ "a\n b: 1b % 2c" => "Incompatible units: 'c' and 'b'.",
71
71
  "$a: 2px + #ccc" => "Cannot add a number with units (2px) to a color (#cccccc).",
72
72
  "$a: #ccc + 2px" => "Cannot add a number with units (2px) to a color (#cccccc).",
73
73
  "& a\n :b c" => ["Base-level rules cannot contain the parent-selector-referencing character '&'.", 1],
@@ -158,19 +158,19 @@ MSG
158
158
  "$var: true\n@while $var\n @extend .bar\n $var: false" => ["Extend directives may only be used within rules.", 3],
159
159
  "@for $i from 0 to 1\n @extend .bar" => ["Extend directives may only be used within rules.", 2],
160
160
  "@mixin foo\n @extend .bar\n@include foo" => ["Extend directives may only be used within rules.", 2],
161
- "foo\n &a\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"a\"\n\n\"a\" may only be used at the beginning of a compound selector.", 2],
162
- "foo\n &1\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"1\"\n\n\"1\" may only be used at the beginning of a compound selector.", 2],
163
161
  "foo %\n a: b" => ['Invalid CSS after "foo %": expected placeholder name, was ""', 1],
164
162
  "=foo\n @content error" => "Invalid content directive. Trailing characters found: \"error\".",
165
163
  "=foo\n @content\n b: c" => "Illegal nesting: Nothing may be nested beneath @content directives.",
166
164
  "@content" => '@content may only be used within a mixin.',
167
165
  "=simple\n .simple\n color: red\n+simple\n color: blue" => ['Mixin "simple" does not accept a content block.', 4],
168
166
  "@import \"foo\" // bar" => "Invalid CSS after \"\"foo\" \": expected media query list, was \"// bar\"",
167
+ "@at-root\n a: b" => "Properties are only allowed within rules, directives, mixin includes, or other properties.",
169
168
 
170
169
  # Regression tests
171
170
  "a\n b:\n c\n d" => ["Illegal nesting: Only properties may be nested beneath properties.", 3],
172
171
  "& foo\n bar: baz\n blat: bang" => ["Base-level rules cannot contain the parent-selector-referencing character '&'.", 1],
173
172
  "a\n b: c\n& foo\n bar: baz\n blat: bang" => ["Base-level rules cannot contain the parent-selector-referencing character '&'.", 3],
173
+ "@" => "Invalid directive: '@'.",
174
174
  }
175
175
 
176
176
  def teardown
@@ -467,89 +467,25 @@ SASS
467
467
  assert_hash_has(err.sass_backtrace[4], :filename => nil, :mixin => nil, :line => 1)
468
468
  end
469
469
 
470
- def test_basic_mixin_loop_exception
471
- render <<SASS
472
- @mixin foo
473
- @include foo
474
- @include foo
475
- SASS
476
- assert(false, "Exception not raised")
477
- rescue Sass::SyntaxError => err
478
- assert_equal("An @include loop has been found: foo includes itself", err.message)
479
- assert_hash_has(err.sass_backtrace[0], :mixin => "foo", :line => 2)
480
- end
481
-
482
- def test_double_mixin_loop_exception
483
- render <<SASS
484
- @mixin foo
485
- @include bar
486
- @mixin bar
487
- @include foo
488
- @include foo
489
- SASS
490
- assert(false, "Exception not raised")
491
- rescue Sass::SyntaxError => err
492
- assert_equal(<<MESSAGE.rstrip, err.message)
493
- An @include loop has been found:
494
- foo includes bar
495
- bar includes foo
496
- MESSAGE
497
- assert_hash_has(err.sass_backtrace[0], :mixin => "bar", :line => 4)
498
- assert_hash_has(err.sass_backtrace[1], :mixin => "foo", :line => 2)
499
- end
500
-
501
- def test_deep_mixin_loop_exception
502
- render <<SASS
503
- @mixin foo
504
- @include bar
505
-
506
- @mixin bar
507
- @include baz
508
-
509
- @mixin baz
510
- @include foo
470
+ def test_recursive_mixin
471
+ assert_equal <<CSS, render(<<SASS)
472
+ .foo .bar .baz {
473
+ color: blue; }
474
+ .foo .bar .qux {
475
+ color: red; }
476
+ .foo .zap {
477
+ color: green; }
478
+ CSS
479
+ @mixin map-to-rule($map-or-color)
480
+ @if type-of($map-or-color) == map
481
+ @each $key, $value in $map-or-color
482
+ .\#{$key}
483
+ @include map-to-rule($value)
484
+ @else
485
+ color: $map-or-color
511
486
 
512
- @include foo
513
- SASS
514
- assert(false, "Exception not raised")
515
- rescue Sass::SyntaxError => err
516
- assert_equal(<<MESSAGE.rstrip, err.message)
517
- An @include loop has been found:
518
- foo includes bar
519
- bar includes baz
520
- baz includes foo
521
- MESSAGE
522
- assert_hash_has(err.sass_backtrace[0], :mixin => "baz", :line => 8)
523
- assert_hash_has(err.sass_backtrace[1], :mixin => "bar", :line => 5)
524
- assert_hash_has(err.sass_backtrace[2], :mixin => "foo", :line => 2)
525
- end
526
-
527
- def test_mixin_loop_with_content
528
- render <<SASS
529
- =foo
530
- @content
531
- =bar
532
- +foo
533
- +bar
534
- +bar
487
+ @include map-to-rule((foo: (bar: (baz: blue, qux: red), zap: green)))
535
488
  SASS
536
- assert(false, "Exception not raised")
537
- rescue Sass::SyntaxError => err
538
- assert_equal("An @include loop has been found: bar includes itself", err.message)
539
- assert_hash_has(err.sass_backtrace[0], :mixin => "@content", :line => 5)
540
- end
541
-
542
- def test_basic_import_loop_exception
543
- import = filename_for_test
544
- importer = MockImporter.new
545
- importer.add_import(import, "@import '#{import}'")
546
-
547
- engine = Sass::Engine.new("@import '#{import}'", :filename => import,
548
- :load_paths => [importer])
549
-
550
- assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
551
- An @import loop has been found: #{import} imports itself
552
- ERR
553
489
  end
554
490
 
555
491
  def test_double_import_loop_exception
@@ -2282,6 +2218,36 @@ CSS
2282
2218
  SASS
2283
2219
  end
2284
2220
 
2221
+ def test_double_media_bubbling_with_surrounding_rules
2222
+ assert_equal <<CSS, render(<<SASS)
2223
+ @media (min-width: 0) {
2224
+ a {
2225
+ a: a; }
2226
+
2227
+ b {
2228
+ before: b;
2229
+ after: b; } }
2230
+ @media (min-width: 0) and (max-width: 5000px) {
2231
+ b {
2232
+ x: x; } }
2233
+
2234
+ @media (min-width: 0) {
2235
+ c {
2236
+ c: c; } }
2237
+ CSS
2238
+ @media (min-width: 0)
2239
+ a
2240
+ a: a
2241
+ b
2242
+ before: b
2243
+ @media (max-width: 5000px)
2244
+ x: x
2245
+ after: b
2246
+ c
2247
+ c: c
2248
+ SASS
2249
+ end
2250
+
2285
2251
  def test_rule_media_rule_bubbling
2286
2252
  assert_equal <<CSS, render(<<SASS)
2287
2253
  @media bar {
@@ -2308,9 +2274,10 @@ SASS
2308
2274
  @media print {
2309
2275
  .outside {
2310
2276
  color: black; } }
2311
- @media print and (a: b) {
2312
- .outside .inside {
2313
- border: 1px solid black; } }
2277
+ @media print and (a: b) {
2278
+ .outside .inside {
2279
+ border: 1px solid black; } }
2280
+
2314
2281
  .outside .middle {
2315
2282
  display: block; }
2316
2283
  CSS
@@ -2588,24 +2555,6 @@ body
2588
2555
  SASS
2589
2556
  end
2590
2557
 
2591
- def test_tricky_mixin_loop_exception
2592
- render <<SASS
2593
- @mixin foo($a)
2594
- @if $a
2595
- @include foo(false)
2596
- @include foo(true)
2597
- @else
2598
- a: b
2599
-
2600
- a
2601
- @include foo(true)
2602
- SASS
2603
- assert(false, "Exception not raised")
2604
- rescue Sass::SyntaxError => err
2605
- assert_equal("An @include loop has been found: foo includes itself", err.message)
2606
- assert_hash_has(err.sass_backtrace[0], :mixin => "foo", :line => 3)
2607
- end
2608
-
2609
2558
  def test_interpolated_comment_in_mixin
2610
2559
  assert_equal <<CSS, render(<<SASS)
2611
2560
  /*! color: red */