Browse files

Allow mixin recursion.

Closes #943
  • Loading branch information...
1 parent f09ce25 commit 054b4867df8eb4617c8635517cda0a68efe63c34 @nex3 nex3 committed Jan 21, 2014
Showing with 25 additions and 136 deletions.
  1. +4 −0 doc-src/SASS_CHANGELOG.md
  2. +2 −2 doc-src/SASS_REFERENCE.md
  3. +2 −35 lib/sass/tree/visitors/perform.rb
  4. +17 −99 test/sass/engine_test.rb
View
4 doc-src/SASS_CHANGELOG.md
@@ -289,6 +289,10 @@ maps instead.
list based on `$list`, with the nth element changed to the value
specified.
+* In order to make it easier for mixins to process maps, they may now
+ recursively call themselves and one another. It is no longer an
+ error to have a mixin `@include` loop.
+
* Add "grey" and "transparent" as recognized SassScript colors. Thanks to [Rob
Wierzbowski](https://github.com/robwierzbowski).
View
4 doc-src/SASS_REFERENCE.md
@@ -2189,8 +2189,8 @@ For example:
@mixin highlighted-background { background-color: #fc0; }
@mixin header-text { font-size: 20px; }
-A mixin may not include itself, directly or indirectly. That is,
-mixin recursion is forbidden.
+Mixins may include themselves. This is different than the behavior of
+Sass versions prior to 3.3, where mixin recursion was forbidden.
Mixins that only define descendent selectors can be safely mixed
into the top most level of a document.
View
37 lib/sass/tree/visitors/perform.rb
@@ -315,12 +315,6 @@ def visit_mixindef(node)
# Runs a mixin.
def visit_mixin(node)
- include_loop = true
- if @environment.stack.frames.any? {|f| f.is_mixin? && f.name == node.name}
- handle_include_loop!(node)
- end
- include_loop = false
-
@environment.stack.with_mixin(node.filename, node.line, node.name) do
mixin = @environment.mixin(node.name)
raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin
@@ -343,10 +337,8 @@ def visit_mixin(node)
end
end
rescue Sass::SyntaxError => e
- unless include_loop
- e.modify_backtrace(:mixin => node.name, :line => node.line)
- e.add_backtrace(:line => node.line)
- end
+ e.modify_backtrace(:mixin => node.name, :line => node.line)
+ e.add_backtrace(:line => node.line)
raise e
end
@@ -516,31 +508,6 @@ def run_interp(text)
run_interp_no_strip(text).strip
end
- def handle_include_loop!(node)
- msg = "An @include loop has been found:"
- content_count = 0
- mixins = @environment.stack.frames.select {|f| f.is_mixin?}.reverse!.map! {|f| f.name}
- mixins = mixins.select do |name|
- if name == '@content'
- content_count += 1
- false
- elsif content_count > 0
- content_count -= 1
- false
- else
- true
- end
- end
-
- return unless mixins.include?(node.name)
- raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1
-
- msg << "\n" << Sass::Util.enum_cons(mixins.reverse + [node.name], 2).map do |m1, m2|
- " #{m1} includes #{m2}"
- end.join("\n")
- raise Sass::SyntaxError.new(msg)
- end
-
def handle_import_loop!(node)
msg = "An @import loop has been found:"
files = @environment.stack.frames.select {|f| f.is_import?}.map {|f| f.filename}.compact
View
116 test/sass/engine_test.rb
@@ -469,89 +469,25 @@ def test_mixin_and_import_exception
assert_hash_has(err.sass_backtrace[4], :filename => nil, :mixin => nil, :line => 1)
end
- def test_basic_mixin_loop_exception
- render <<SASS
-@mixin foo
- @include foo
-@include foo
-SASS
- assert(false, "Exception not raised")
- rescue Sass::SyntaxError => err
- assert_equal("An @include loop has been found: foo includes itself", err.message)
- assert_hash_has(err.sass_backtrace[0], :mixin => "foo", :line => 2)
- end
+ def test_recursive_mixin
+ assert_equal <<CSS, render(<<SASS)
+.foo .bar .baz {
+ color: blue; }
+.foo .bar .qux {
+ color: red; }
+.foo .zap {
+ color: green; }
+CSS
+@mixin map-to-rule($map-or-color)
+ @if type-of($map-or-color) == map
+ @each $key, $value in $map-or-color
+ .\#{$key}
+ @include map-to-rule($value)
+ @else
+ color: $map-or-color
- def test_double_mixin_loop_exception
- render <<SASS
-@mixin foo
- @include bar
-@mixin bar
- @include foo
-@include foo
+@include map-to-rule((foo: (bar: (baz: blue, qux: red), zap: green)))
SASS
- assert(false, "Exception not raised")
- rescue Sass::SyntaxError => err
- assert_equal(<<MESSAGE.rstrip, err.message)
-An @include loop has been found:
- foo includes bar
- bar includes foo
-MESSAGE
- assert_hash_has(err.sass_backtrace[0], :mixin => "bar", :line => 4)
- assert_hash_has(err.sass_backtrace[1], :mixin => "foo", :line => 2)
- end
-
- def test_deep_mixin_loop_exception
- render <<SASS
-@mixin foo
- @include bar
-
-@mixin bar
- @include baz
-
-@mixin baz
- @include foo
-
-@include foo
-SASS
- assert(false, "Exception not raised")
- rescue Sass::SyntaxError => err
- assert_equal(<<MESSAGE.rstrip, err.message)
-An @include loop has been found:
- foo includes bar
- bar includes baz
- baz includes foo
-MESSAGE
- assert_hash_has(err.sass_backtrace[0], :mixin => "baz", :line => 8)
- assert_hash_has(err.sass_backtrace[1], :mixin => "bar", :line => 5)
- assert_hash_has(err.sass_backtrace[2], :mixin => "foo", :line => 2)
- end
-
- def test_mixin_loop_with_content
- render <<SASS
-=foo
- @content
-=bar
- +foo
- +bar
-+bar
-SASS
- assert(false, "Exception not raised")
- rescue Sass::SyntaxError => err
- assert_equal("An @include loop has been found: bar includes itself", err.message)
- assert_hash_has(err.sass_backtrace[0], :mixin => "@content", :line => 5)
- end
-
- def test_basic_import_loop_exception
- import = filename_for_test
- importer = MockImporter.new
- importer.add_import(import, "@import '#{import}'")
-
- engine = Sass::Engine.new("@import '#{import}'", :filename => import,
- :load_paths => [importer])
-
- assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
-An @import loop has been found: #{import} imports itself
-ERR
end
def test_double_import_loop_exception
@@ -2591,24 +2527,6 @@ def test_variable_in_media_in_mixin
SASS
end
- def test_tricky_mixin_loop_exception
- render <<SASS
-@mixin foo($a)
- @if $a
- @include foo(false)
- @include foo(true)
- @else
- a: b
-
-a
- @include foo(true)
-SASS
- assert(false, "Exception not raised")
- rescue Sass::SyntaxError => err
- assert_equal("An @include loop has been found: foo includes itself", err.message)
- assert_hash_has(err.sass_backtrace[0], :mixin => "foo", :line => 3)
- end
-
def test_interpolated_comment_in_mixin
assert_equal <<CSS, render(<<SASS)
/*! color: red */

0 comments on commit 054b486

Please sign in to comment.