1
0
Fork 0
mirror of https://github.com/haml/haml.git synced 2022-11-09 12:33:31 -05:00

[Sass] Better error messages for recursive mixins.

Note that this completely disallows recursive mixins,
something that I doubt will actually affect anyone.
This commit is contained in:
Nathan Weizenbaum 2010-05-22 21:41:22 -07:00
parent f94592d328
commit bd3d4cbdc5
5 changed files with 117 additions and 5 deletions

View file

@ -31,6 +31,26 @@ is compiled to:
This is useful, since normally {file:SASS_REFERENCE.md#division-and-slash
a slash with variables is treated as division}.
### Recursive Mixins
Mixins that include themselves will now print
much more informative error messages.
For example:
@mixin foo {@include bar}
@mixin bar {@include foo}
@include foo
will print:
An @include loop has been found:
foo includes bar
bar includes foo
Although it was previously possible to use recursive mixins
without causing infinite looping, this is now disallowed,
since there's no good reason to do it.
### Rails 3 Support
Fix Sass configuration under Rails 3.

View file

@ -1,3 +1,5 @@
require 'set'
module Sass
# The lexical environment for SassScript.
# This keeps track of variable and mixin definitions.
@ -24,6 +26,7 @@ module Sass
@mixins = {}
@parent = parent
@stack = [] unless parent
@mixins_in_use = Set.new unless parent
set_var("important", Script::String.new("!important")) unless @parent
end
@ -56,6 +59,7 @@ module Sass
else
stack.push(frame_info)
end
mixins_in_use << stack.last[:mixin] if stack.last[:mixin] && !stack.last[:prepared]
end
# Like \{#push\_frame}, but next time a stack frame is pushed,
@ -69,7 +73,8 @@ module Sass
# Pop a stack frame from the mixin/include stack.
def pop_frame
stack.pop if stack.last && stack.last[:prepared]
stack.pop
popped = stack.pop
mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
end
# A list of stack frames in the mixin/include stack.
@ -81,6 +86,13 @@ module Sass
@stack ||= @parent.stack
end
# A set of names of mixins currently present in the stack.
#
# @return [Set<String>] The mixin names.
def mixins_in_use
@mixins_in_use ||= @parent.mixins_in_use
end
class << self
private

View file

@ -137,7 +137,10 @@ module Sass
# @see #sass_backtrace
# @return [String]
def sass_backtrace_str(default_filename = "an unknown file")
"Syntax error: #{message}" +
lines = self.message.split("\n")
msg = lines[0] + lines[1..-1].
map {|l| "\n" + (" " * "Syntax error: ".size) + l}.join
"Syntax error: #{msg}" +
Haml::Util.enum_with_index(sass_backtrace).map do |entry, i|
"\n #{i == 0 ? "on" : "from"} line #{entry[:line]}" +
" of #{entry[:filename] || default_filename}" +

View file

@ -66,6 +66,8 @@ module Sass::Tree
# @raise [Sass::SyntaxError] if an incorrect number of arguments was passed
# @see Sass::Tree
def perform!(environment)
handle_include_loop!(environment) if environment.mixins_in_use.include?(@name)
original_env = environment
original_env.push_frame(:filename => filename, :line => line)
original_env.prepare_frame(:mixin => @name)
@ -93,11 +95,29 @@ END
self.children = mixin.tree.map {|c| c.perform(environment)}.flatten
rescue Sass::SyntaxError => e
if original_env # Don't add backtrace info if this is an @include loop
e.modify_backtrace(:mixin => @name, :line => @line)
e.add_backtrace(:line => @line)
end
raise e
ensure
original_env.pop_frame
original_env.pop_frame if original_env
end
private
def handle_include_loop!(environment)
msg = "An @include loop has been found:"
mixins = environment.stack.map {|s| s[:mixin]}.compact
if mixins.size == 2 && mixins[0] == mixins[1]
raise Sass::SyntaxError.new("#{msg} #{@name} includes itself")
end
mixins << @name
msg << "\n" << Haml::Util.enum_cons(mixins, 2).map do |m1, m2|
" #{m1} includes #{m2}"
end.join("\n")
raise Sass::SyntaxError.new(msg)
end
end
end

View file

@ -342,6 +342,63 @@ SASS
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_double_mixin_loop_exception
render <<SASS
@mixin foo
@include bar
@mixin bar
@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 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_exception_css_with_offset
opts = {:full_exception => true, :line => 362}
render(("a\n b: c\n" * 10) + "d\n e:\n" + ("f\n g: h\n" * 10), opts)