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

Sign up to get free protection for your applications and to get access to all the features.
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 */