sass 3.2.0.alpha.35 → 3.2.0.alpha.49

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 (41) hide show
  1. data/README.md +1 -1
  2. data/REVISION +1 -1
  3. data/VERSION +1 -1
  4. data/lib/sass.rb +22 -0
  5. data/lib/sass/engine.rb +2 -2
  6. data/lib/sass/environment.rb +0 -1
  7. data/lib/sass/exec.rb +1 -1
  8. data/lib/sass/importers/filesystem.rb +1 -1
  9. data/lib/sass/plugin.rb +4 -8
  10. data/lib/sass/plugin/compiler.rb +42 -17
  11. data/lib/sass/plugin/configuration.rb +0 -2
  12. data/lib/sass/repl.rb +0 -1
  13. data/lib/sass/script/funcall.rb +6 -1
  14. data/lib/sass/script/functions.rb +2 -2
  15. data/lib/sass/script/number.rb +1 -1
  16. data/lib/sass/script/parser.rb +12 -5
  17. data/lib/sass/script/variable.rb +0 -1
  18. data/lib/sass/scss/css_parser.rb +1 -0
  19. data/lib/sass/scss/parser.rb +37 -12
  20. data/lib/sass/scss/rx.rb +8 -3
  21. data/lib/sass/selector.rb +21 -0
  22. data/lib/sass/selector/abstract_sequence.rb +7 -0
  23. data/lib/sass/tree/media_node.rb +4 -4
  24. data/lib/sass/tree/rule_node.rb +5 -0
  25. data/lib/sass/tree/visitors/check_nesting.rb +10 -10
  26. data/lib/sass/tree/visitors/convert.rb +3 -3
  27. data/lib/sass/tree/visitors/cssize.rb +5 -1
  28. data/lib/sass/tree/visitors/perform.rb +16 -2
  29. data/lib/sass/tree/visitors/to_css.rb +2 -1
  30. data/lib/sass/util.rb +4 -1
  31. data/test/sass/conversion_test.rb +32 -2
  32. data/test/sass/engine_test.rb +105 -1
  33. data/test/sass/extend_test.rb +65 -0
  34. data/test/sass/importer_test.rb +7 -0
  35. data/test/sass/plugin_test.rb +16 -13
  36. data/test/sass/script_conversion_test.rb +2 -0
  37. data/test/sass/script_test.rb +18 -0
  38. data/test/sass/scss/scss_test.rb +34 -0
  39. data/test/sass/test_helper.rb +1 -1
  40. data/test/test_helper.rb +1 -0
  41. metadata +3 -3
@@ -112,6 +112,7 @@ module Sass
112
112
  INTERP_START = /#\{/
113
113
  ANY = /:(-[-\w]+-)?any\(/i
114
114
 
115
+ IDENT_HYPHEN_INTERP = /-(#\{)/
115
116
  STRING1_NOINTERP = /\"((?:[^\n\r\f\\"#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\"/
116
117
  STRING2_NOINTERP = /\'((?:[^\n\r\f\\'#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\'/
117
118
  STRING_NOINTERP = /#{STRING1_NOINTERP}|#{STRING2_NOINTERP}/
@@ -119,9 +120,13 @@ module Sass
119
120
  # We could use it for 1.9 only, but I don't want to introduce a cross-version
120
121
  # behavior difference.
121
122
  # In any case, almost all CSS idents will be matched by this.
122
- STATIC_VALUE = /(-?#{NMSTART}|#{STRING_NOINTERP}|\s(?!%)|#[a-f0-9]|[,%]|#{NUM}|\!important)+(?=[;}])/i
123
-
124
- STATIC_SELECTOR = /(#{NMCHAR}|\s|[,>+*]|[:#.]#{NMSTART})+(?=[{])/i
123
+ #
124
+ # We explicitly avoid parsing newlines or values/selectors longer than
125
+ # about 50 characters. This mitigates the problem of exponential parsing
126
+ # time when a value has a long string of valid, parsable content followed
127
+ # by something invalid.
128
+ STATIC_VALUE = /(-?#{NMSTART}|#{STRING_NOINTERP}|[ \t](?!%)|#[a-f0-9]|[,%]|#{NUM}|\!important){0,50}([;}])/i
129
+ STATIC_SELECTOR = /(#{NMCHAR}|[ \t]|[,>+*]|[:#.]#{NMSTART}){0,50}([{])/i
125
130
  end
126
131
  end
127
132
  end
@@ -81,6 +81,27 @@ module Sass
81
81
  end
82
82
  end
83
83
 
84
+ # A placeholder selector (e.g. `%foo`).
85
+ # This exists to be replaced via `@extend`.
86
+ # Rulesets using this selector will not be printed, but can be extended.
87
+ # Otherwise, this acts just like a class selector.
88
+ class Placeholder < Simple
89
+ # The placeholder name.
90
+ #
91
+ # @return [Array<String, Sass::Script::Node>]
92
+ attr_reader :name
93
+
94
+ # @param name [Array<String, Sass::Script::Node>] The placeholder name
95
+ def initialize(name)
96
+ @name = name
97
+ end
98
+
99
+ # @see Selector#to_a
100
+ def to_a
101
+ ["%", *@name]
102
+ end
103
+ end
104
+
84
105
  # A universal selector (`*` in CSS).
85
106
  class Universal < Simple
86
107
  # The selector namespace.
@@ -57,6 +57,13 @@ module Sass
57
57
  other.class == self.class && other.hash == self.hash && _eql?(other)
58
58
  end
59
59
  alias_method :==, :eql?
60
+
61
+ # Whether or not this selector sequence contains a placeholder selector.
62
+ # Checks recursively.
63
+ def has_placeholder?
64
+ @has_placeholder ||=
65
+ members.any? {|m| m.is_a?(AbstractSequence) ? m.has_placeholder? : m.is_a?(Placeholder)}
66
+ end
60
67
  end
61
68
  end
62
69
  end
@@ -6,9 +6,9 @@ module Sass::Tree
6
6
  #
7
7
  # @see Sass::Tree
8
8
  class MediaNode < DirectiveNode
9
- # The media query (e.g. `print` or `screen`).
9
+ # The media query. A list of comma-separated queries (e.g. `print` or `screen`).
10
10
  #
11
- # @return [String]
11
+ # @return [Array<String>]
12
12
  attr_accessor :query
13
13
 
14
14
  # @see RuleNode#tabs
@@ -17,7 +17,7 @@ module Sass::Tree
17
17
  # @see RuleNode#group_end
18
18
  attr_accessor :group_end
19
19
 
20
- # @param query [String] See \{#query}
20
+ # @param query [Array<String>] See \{#query}
21
21
  def initialize(query)
22
22
  @query = query
23
23
  @tabs = 0
@@ -26,7 +26,7 @@ module Sass::Tree
26
26
 
27
27
  # @see DirectiveNode#value
28
28
  def value
29
- "@media #{query}"
29
+ "@media #{query.join(', ')}"
30
30
  end
31
31
  end
32
32
  end
@@ -122,6 +122,11 @@ module Sass::Tree
122
122
  :line => self.line}
123
123
  end
124
124
 
125
+ # A rule node is invisible if it has only placeholder selectors.
126
+ def invisible?
127
+ resolved_rules.members.all? {|seq| seq.has_placeholder?}
128
+ end
129
+
125
130
  private
126
131
 
127
132
  def try_to_parse_non_interpolated_rules
@@ -17,11 +17,12 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
17
17
  raise e
18
18
  end
19
19
 
20
- PARENT_CLASSES = [ Sass::Tree::EachNode, Sass::Tree::ForNode, Sass::Tree::IfNode,
21
- Sass::Tree::ImportNode, Sass::Tree::TraceNode, Sass::Tree::WhileNode]
20
+ CONTROL_NODES = [Sass::Tree::EachNode, Sass::Tree::ForNode, Sass::Tree::IfNode,
21
+ Sass::Tree::WhileNode, Sass::Tree::TraceNode]
22
+ SCRIPT_NODES = [Sass::Tree::ImportNode] + CONTROL_NODES
22
23
  def visit_children(parent)
23
24
  old_parent = @parent
24
- @parent = parent unless is_any_of?(parent, PARENT_CLASSES)
25
+ @parent = parent unless is_any_of?(parent, SCRIPT_NODES)
25
26
  old_real_parent, @real_parent = @real_parent, parent
26
27
  super
27
28
  ensure
@@ -76,22 +77,21 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
76
77
  end
77
78
 
78
79
  VALID_FUNCTION_CHILDREN = [
79
- Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::EachNode,
80
- Sass::Tree::ForNode, Sass::Tree::IfNode, Sass::Tree::ReturnNode,
81
- Sass::Tree::VariableNode, Sass::Tree::WarnNode, Sass::Tree::WhileNode
82
- ]
80
+ Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::ReturnNode,
81
+ Sass::Tree::VariableNode, Sass::Tree::WarnNode
82
+ ] + CONTROL_NODES
83
83
  def invalid_function_child?(parent, child)
84
84
  unless is_any_of?(child, VALID_FUNCTION_CHILDREN)
85
85
  "Functions can only contain variable declarations and control directives."
86
86
  end
87
87
  end
88
88
 
89
- INVALID_IMPORT_PARENTS = [
89
+ VALID_IMPORT_PARENTS = [
90
90
  Sass::Tree::IfNode, Sass::Tree::ForNode, Sass::Tree::WhileNode,
91
91
  Sass::Tree::EachNode, Sass::Tree::MixinDefNode, Sass::Tree::MixinNode
92
92
  ]
93
93
  def invalid_import_parent?(parent, child)
94
- if is_any_of?(@real_parent, INVALID_IMPORT_PARENTS)
94
+ if is_any_of?(@real_parent, VALID_IMPORT_PARENTS)
95
95
  return "Import directives may not be used within control directives or mixins."
96
96
  end
97
97
  return if parent.is_a?(Sass::Tree::RootNode)
@@ -114,7 +114,7 @@ class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
114
114
  "Mixins may only be defined at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
115
115
  end
116
116
 
117
- VALID_PROP_CHILDREN = [Sass::Tree::CommentNode, Sass::Tree::PropNode]
117
+ VALID_PROP_CHILDREN = [Sass::Tree::CommentNode, Sass::Tree::PropNode, Sass::Tree::MixinNode] + CONTROL_NODES
118
118
  def invalid_prop_child?(parent, child)
119
119
  unless is_any_of?(child, VALID_PROP_CHILDREN)
120
120
  "Illegal nesting: Only properties may be nested beneath properties."
@@ -148,7 +148,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
148
148
  end
149
149
 
150
150
  def visit_media(node)
151
- "#{tab_str}@media #{node.query}#{yield}"
151
+ "#{tab_str}@media #{node.query.join(', ')}#{yield}"
152
152
  end
153
153
 
154
154
  def visit_mixindef(node)
@@ -228,7 +228,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
228
228
  def selector_to_sass(sel)
229
229
  sel.map do |r|
230
230
  if r.is_a?(String)
231
- r.gsub(/(,[ \t]*)?\n\s*/) {$1 ? $1 + "\n" : " "}
231
+ r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "}
232
232
  else
233
233
  "\#{#{r.to_sass(@options)}}"
234
234
  end
@@ -237,7 +237,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
237
237
 
238
238
  def selector_to_scss(sel)
239
239
  sel.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(@options)}}"}.
240
- join.gsub(/^[ \t]*/, tab_str)
240
+ join.gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '')
241
241
  end
242
242
 
243
243
  def semi
@@ -76,6 +76,8 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
76
76
  sseq = seq.members.first
77
77
  if !sseq.is_a?(Sass::Selector::SimpleSequence)
78
78
  raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
79
+ elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)}
80
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend parent selectors")
79
81
  end
80
82
 
81
83
  sel = sseq.members
@@ -117,7 +119,9 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
117
119
 
118
120
  media = node.children.select {|c| c.is_a?(Sass::Tree::MediaNode)}
119
121
  node.children.reject! {|c| c.is_a?(Sass::Tree::MediaNode)}
120
- media.each {|n| n.query = "#{node.query} and #{n.query}"}
122
+ media.each do |n|
123
+ n.query = node.query.map {|pq| n.query.map {|cq| "#{pq} and #{cq}"}}.flatten
124
+ end
121
125
  (node.children.empty? ? [] : [node]) + media
122
126
  end
123
127
 
@@ -87,7 +87,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
87
87
  # Runs SassScript interpolation in the selector,
88
88
  # and then parses the result into a {Sass::Selector::CommaSequence}.
89
89
  def visit_extend(node)
90
- parser = Sass::SCSS::CssParser.new(run_interp(node.selector), node.filename, node.line)
90
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.selector), node.filename, node.line)
91
91
  node.resolved_selector = parser.parse_selector
92
92
  node
93
93
  end
@@ -137,9 +137,11 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
137
137
  if path = node.css_import?
138
138
  return Sass::Tree::DirectiveNode.new("@import url(#{path})")
139
139
  end
140
+ file = node.imported_file
141
+ handle_import_loop!(node) if @stack.any? {|e| e[:filename] == file.options[:filename]}
140
142
 
141
143
  @stack.push(:filename => node.filename, :line => node.line)
142
- root = node.imported_file.to_tree
144
+ root = file.to_tree
143
145
  Sass::Tree::Visitors::CheckNesting.visit(root)
144
146
  node.children = root.children.map {|c| visit(c)}.flatten
145
147
  node
@@ -347,4 +349,16 @@ END
347
349
  end.join("\n")
348
350
  raise Sass::SyntaxError.new(msg)
349
351
  end
352
+
353
+ def handle_import_loop!(node)
354
+ msg = "An @import loop has been found:"
355
+ files = @stack.map {|s| s[:filename]}.compact
356
+ raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself") if files.size == 1
357
+
358
+ files << node.filename << node.imported_file.options[:filename]
359
+ msg << "\n" << Sass::Util.enum_cons(files, 2).map do |m1, m2|
360
+ " #{m1} imports #{m2}"
361
+ end.join("\n")
362
+ raise Sass::SyntaxError.new(msg)
363
+ end
350
364
  end
@@ -131,10 +131,11 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
131
131
  per_rule_indent, total_indent = [:nested, :expanded].include?(node.style) ? [rule_indent, ''] : ['', rule_indent]
132
132
 
133
133
  joined_rules = node.resolved_rules.members.map do |seq|
134
+ next if seq.has_placeholder?
134
135
  rule_part = seq.to_a.join
135
136
  rule_part.gsub!(/\s*([^,])\s*\n\s*/m, '\1 ') if node.style == :compressed
136
137
  rule_part
137
- end.join(rule_separator)
138
+ end.compact.join(rule_separator)
138
139
 
139
140
  joined_rules.sub!(/\A\s*/, per_rule_indent)
140
141
  joined_rules.gsub!(/\s*\n\s*/, "#{line_separator}#{per_rule_indent}")
@@ -467,7 +467,8 @@ MSG
467
467
  # We allow any printable ASCII characters but double quotes in the charset decl
468
468
  bin = str.dup.force_encoding("BINARY")
469
469
  encoding = Sass::Util::ENCODINGS_TO_CHECK.find do |enc|
470
- bin =~ Sass::Util::CHARSET_REGEXPS[enc]
470
+ re = Sass::Util::CHARSET_REGEXPS[enc]
471
+ re && bin =~ re
471
472
  end
472
473
  charset, bom = $1, $2
473
474
  if charset
@@ -508,6 +509,8 @@ MSG
508
509
  Regexp.new(/\A(?:#{_enc("\uFEFF", e)})?#{
509
510
  _enc('@charset "', e)}(.*?)#{_enc('"', e)}|\A(#{
510
511
  _enc("\uFEFF", e)})/)
512
+ rescue Encoding::ConverterNotFound => _
513
+ nil # JRuby on Java 5 doesn't support UTF-32
511
514
  rescue
512
515
  # /\A@charset "(.*?)"/
513
516
  Regexp.new(/\A#{_enc('@charset "', e)}(.*?)#{_enc('"', e)}/)
@@ -180,7 +180,7 @@ SCSS
180
180
  def test_multiline_properties
181
181
  assert_scss_to_sass <<SASS, <<SCSS
182
182
  foo bar
183
- baz: bip bam boon
183
+ baz: bip bam boon
184
184
  SASS
185
185
  foo bar {
186
186
  baz:
@@ -191,7 +191,7 @@ SCSS
191
191
 
192
192
  assert_scss_to_scss <<OUT, <<IN
193
193
  foo bar {
194
- baz: bip bam boon; }
194
+ baz: bip bam boon; }
195
195
  OUT
196
196
  foo bar {
197
197
  baz:
@@ -1177,6 +1177,36 @@ SCSS
1177
1177
  SCSS
1178
1178
  end
1179
1179
 
1180
+ def test_placeholder_conversion
1181
+ assert_renders(<<SASS, <<SCSS)
1182
+ #content a%foo.bar
1183
+ color: blue
1184
+ SASS
1185
+ #content a%foo.bar {
1186
+ color: blue; }
1187
+ SCSS
1188
+ end
1189
+
1190
+ def test_placeholder_interoplation_conversion
1191
+ assert_renders(<<SASS, <<SCSS)
1192
+ $foo: foo
1193
+
1194
+ %\#{$foo}
1195
+ color: blue
1196
+
1197
+ .bar
1198
+ @extend %foo
1199
+ SASS
1200
+ $foo: foo;
1201
+
1202
+ %\#{$foo} {
1203
+ color: blue; }
1204
+
1205
+ .bar {
1206
+ @extend %foo; }
1207
+ SCSS
1208
+ end
1209
+
1180
1210
  private
1181
1211
 
1182
1212
  def assert_sass_to_sass(sass, options = {})
@@ -39,6 +39,9 @@ MSG
39
39
  "a\n b: c;" => 'Invalid CSS after "c": expected expression (e.g. 1px, bold), was ";"',
40
40
  ".foo ^bar\n a: b" => ['Invalid CSS after ".foo ": expected selector, was "^bar"', 1],
41
41
  "a\n @extend .foo ^bar" => 'Invalid CSS after ".foo ": expected selector, was "^bar"',
42
+ "a\n @extend .foo .bar" => "Can't extend .foo .bar: can't extend nested selectors",
43
+ "a\n @extend >" => "Can't extend >: invalid selector",
44
+ "a\n @extend &.foo" => "Can't extend &.foo: can't extend parent selectors",
42
45
  "a: b" => 'Properties are only allowed within rules, directives, mixin includes, or other properties.',
43
46
  ":a b" => 'Properties are only allowed within rules, directives, mixin includes, or other properties.',
44
47
  "$" => 'Invalid variable: "$".',
@@ -64,7 +67,7 @@ MSG
64
67
  "@mixin foo\n @import foo" => "Import directives may not be used within control directives or mixins.",
65
68
  "@import foo;" => "Invalid @import: expected end of line, was \";\".",
66
69
  '$foo: "bar" "baz" !' => %Q{Invalid CSS after ""bar" "baz" ": expected expression (e.g. 1px, bold), was "!"},
67
- '$foo: "bar" "baz" $' => %Q{Invalid CSS after ""bar" "baz" ": expected expression (e.g. 1px, bold), was "$"},
70
+ '$foo: "bar" "baz" $' => %Q{Invalid CSS after ""bar" "baz" ": expected expression (e.g. 1px, bold), was "$"}, #'
68
71
  "=foo\n :color red\n.bar\n +bang" => "Undefined mixin 'bang'.",
69
72
  "=foo\n :color red\n.bar\n +bang_bop" => "Undefined mixin 'bang_bop'.",
70
73
  "=foo\n :color red\n.bar\n +bang-bop" => "Undefined mixin 'bang-bop'.",
@@ -141,6 +144,7 @@ MSG
141
144
  "@mixin foo\n @extend .bar\n@include foo" => ["Extend directives may only be used within rules.", 2],
142
145
  "foo\n &a\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"a\"\n\n\"a\" may only be used at the beginning of a selector.", 2],
143
146
  "foo\n &1\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"1\"\n\n\"1\" may only be used at the beginning of a selector.", 2],
147
+ "foo %\n a: b" => ['Invalid CSS after "foo %": expected placeholder name, was ""', 1],
144
148
  "=foo\n @content error" => "Invalid content directive. Trailing characters found: \"error\".",
145
149
  "=foo\n @content\n b: c" => "Illegal nesting: Nothing may be nested beneath @content directives.",
146
150
  "@content" => '@content may only be used within a mixin.',
@@ -476,6 +480,53 @@ MESSAGE
476
480
  assert_hash_has(err.sass_backtrace[2], :mixin => "foo", :line => 2)
477
481
  end
478
482
 
483
+ def test_basic_import_loop_exception
484
+ import = filename_for_test
485
+ importer = MockImporter.new
486
+ importer.add_import(import, "@import '#{import}'")
487
+
488
+ engine = Sass::Engine.new("@import '#{import}'", :filename => import,
489
+ :load_paths => [importer])
490
+
491
+ assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
492
+ An @import loop has been found: #{import} imports itself
493
+ ERR
494
+ end
495
+
496
+ def test_double_import_loop_exception
497
+ importer = MockImporter.new
498
+ importer.add_import("foo", "@import 'bar'")
499
+ importer.add_import("bar", "@import 'foo'")
500
+
501
+ engine = Sass::Engine.new('@import "foo"', :filename => filename_for_test,
502
+ :load_paths => [importer])
503
+
504
+ assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
505
+ An @import loop has been found:
506
+ #{filename_for_test} imports foo
507
+ foo imports bar
508
+ bar imports foo
509
+ ERR
510
+ end
511
+
512
+ def test_deep_import_loop_exception
513
+ importer = MockImporter.new
514
+ importer.add_import("foo", "@import 'bar'")
515
+ importer.add_import("bar", "@import 'baz'")
516
+ importer.add_import("baz", "@import 'foo'")
517
+
518
+ engine = Sass::Engine.new('@import "foo"', :filename => filename_for_test,
519
+ :load_paths => [importer])
520
+
521
+ assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
522
+ An @import loop has been found:
523
+ #{filename_for_test} imports foo
524
+ foo imports bar
525
+ bar imports baz
526
+ baz imports foo
527
+ ERR
528
+ end
529
+
479
530
  def test_exception_css_with_offset
480
531
  opts = {:full_exception => true, :line => 362}
481
532
  render(("a\n b: c\n" * 10) + "d\n e:\n" + ("f\n g: h\n" * 10), opts)
@@ -598,6 +649,16 @@ CSS
598
649
  assert File.exists?(sassc_file)
599
650
  end
600
651
 
652
+ def test_import_from_global_load_paths
653
+ importer = MockImporter.new
654
+ importer.add_import("imported", "div{color:red}")
655
+ Sass.load_paths << importer
656
+
657
+ assert_equal "div {\n color: red; }\n", Sass::Engine.new('@import "imported"').render
658
+ ensure
659
+ Sass.load_paths.clear
660
+ end
661
+
601
662
  def test_nonexistent_import
602
663
  assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) do
603
664
  File to import not found or unreadable: nonexistent.sass.
@@ -1209,6 +1270,18 @@ bar
1209
1270
  SASS
1210
1271
  end
1211
1272
 
1273
+ def test_control_directive_in_nested_property
1274
+ assert_equal(<<CSS, render(<<SASS))
1275
+ foo {
1276
+ a-b: c; }
1277
+ CSS
1278
+ foo
1279
+ a:
1280
+ @if true
1281
+ b: c
1282
+ SASS
1283
+ end
1284
+
1212
1285
  def test_interpolation
1213
1286
  assert_equal("a-1 {\n b-2-3: c-3; }\n", render(<<SASS))
1214
1287
  $a: 1
@@ -1956,6 +2029,19 @@ CSS
1956
2029
  SASS
1957
2030
  end
1958
2031
 
2032
+ def test_double_media_bubbling_with_commas
2033
+ assert_equal <<CSS, render(<<SASS)
2034
+ @media foo and baz, foo and bang, bar and baz, bar and bang {
2035
+ .foo {
2036
+ c: d; } }
2037
+ CSS
2038
+ @media foo, bar
2039
+ @media baz, bang
2040
+ .foo
2041
+ c: d
2042
+ SASS
2043
+ end
2044
+
1959
2045
  def test_rule_media_rule_bubbling
1960
2046
  assert_equal <<CSS, render(<<SASS)
1961
2047
  @media bar {
@@ -2018,6 +2104,24 @@ CSS
2018
2104
 
2019
2105
  # Regression tests
2020
2106
 
2107
+ def test_tricky_mixin_loop_exception
2108
+ render <<SASS
2109
+ @mixin foo($a)
2110
+ @if $a
2111
+ @include foo(false)
2112
+ @include foo(true)
2113
+ @else
2114
+ a: b
2115
+
2116
+ a
2117
+ @include foo(true)
2118
+ SASS
2119
+ assert(false, "Exception not raised")
2120
+ rescue Sass::SyntaxError => err
2121
+ assert_equal("An @include loop has been found: foo includes itself", err.message)
2122
+ assert_hash_has(err.sass_backtrace[0], :mixin => "foo", :line => 3)
2123
+ end
2124
+
2021
2125
  def test_interpolated_comment_in_mixin
2022
2126
  assert_equal <<CSS, render(<<SASS)
2023
2127
  /* color: red */