Skip to content

Commit

Permalink
[Sass] Modify the stack stuff so that warnings look like error messages.
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed Apr 11, 2010
1 parent a5ce4ee commit 11f2cf1
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 75 deletions.
52 changes: 24 additions & 28 deletions lib/sass/environment.rb
Expand Up @@ -23,8 +23,7 @@ def initialize(parent = nil)
@vars = {}
@mixins = {}
@parent = parent
@stack = []
@ignore_parent_stack = false
@stack = [] unless parent
set_var("important", Script::String.new("!important")) unless @parent
end

Expand All @@ -36,7 +35,8 @@ def options
@options || (parent && parent.options) || {}
end

# Push lexical frame information onto the runtime stack.
# Push a new stack frame onto the mixin/include stack.
#
# @param frame_info [{Symbol => Object}]
# Frame information has the following keys:
#
Expand All @@ -49,40 +49,36 @@ def options
#
# `:line`
# : The line of the file on which the lexical scope changed. Never nil.
def push_frame(frame_info)
if stack.last && stack.last[:prepared]
stack.last.delete(:prepared)
stack.last.merge!(frame_info)
else
stack.push(frame_info)
end
end

# Like \{#push\_frame}, but next time a stack frame is pushed,
# it will be merged with this frame.
#
# `:import`
# : Set to `true` when the lexical scope is changing due to an import.
def push(frame_info)
@stack.push frame_info
# @param frame_info [{Symbol => Object}] Same as for \{#push\_frame}.
def prepare_frame(frame_info)
push_frame(frame_info.merge(:prepared => true))
end

# Pop runtime frame information from the stack.
def pop
@stack.pop
# Pop a stack frame from the mixin/include stack.
def pop_frame
stack.pop if stack.last[:prepared]
stack.pop
end

# A list of the runtime stack frame information
# The last element in the list was pushed onto the stack most recently.
# A list of stack frames in the mixin/include stack.
# The last element in the list is the most deeply-nested frame.
#
# @return [Array<{Symbol => Object}>] The stack frames,
# of the form passed to \{#push}.
def stack
prev = (!@ignore_parent_stack && parent && parent.stack) || []
prev + @stack
end

# Temporarily assume the runtime stack that is passed in.
#
# @param stk [Array<{Symbol => Object}>] A call stack from another environment,
# of the form returned by \{#stack}.
# @yield A block in which the stack is set to `stk`.
def with_stack(stk)
@stack, old_stack = stk, @stack
@ignore_parent_stack = true
yield self
ensure
@ignore_parent_stack = false
@stack = old_stack
@stack ||= @parent.stack
end

class << self
Expand Down
4 changes: 2 additions & 2 deletions lib/sass/tree/import_node.rb
Expand Up @@ -62,7 +62,7 @@ def _perform(environment)
# @param environment [Sass::Environment] The lexical environment containing
# variable and mixin values
def perform!(environment)
environment.push(:filename => @filename, :line => @line, :import => true)
environment.push_frame(:filename => @filename, :line => @line)
root = Sass::Files.tree_for(full_filename, @options)
@template = root.template
self.children = root.children
Expand All @@ -72,7 +72,7 @@ def perform!(environment)
e.add_backtrace(:filename => @filename, :line => @line)
raise e
ensure
environment.pop
environment.pop_frame
end

private
Expand Down
39 changes: 19 additions & 20 deletions lib/sass/tree/mixin_node.rb
Expand Up @@ -51,38 +51,37 @@ def _cssize(parent)
# @see Sass::Tree
def perform!(environment)
original_env = environment
original_env.push(:filename => filename, :mixin => @name, :line => line)
original_env.push_frame(:filename => filename, :line => line)
original_env.prepare_frame(:mixin => @name)
raise Sass::SyntaxError.new("Undefined mixin '#{@name}'.") unless mixin = environment.mixin(@name)

raise Sass::SyntaxError.new(<<END.gsub("\n", "")) if mixin.args.size < @args.size
Mixin #{@name} takes #{mixin.args.size} argument#{'s' if mixin.args.size != 1}
but #{@args.size} #{@args.size == 1 ? 'was' : 'were'} passed.
END

mixin.environment.with_stack(original_env.stack) do |mixin_env|
environment = mixin.args.zip(@args).
inject(Sass::Environment.new(mixin_env)) do |env, ((var, default), value)|
env.set_local_var(var.name,
if value
value.perform(environment)
elsif default
val = default.perform(env)
if default.context == :equals && val.is_a?(Sass::Script::String)
val = Sass::Script::String.new(val.value)
end
val
end)
raise Sass::SyntaxError.new("Mixin #{@name} is missing parameter #{var.inspect}.") unless env.var(var.name)
env
end
self.children = mixin.tree.map {|c| c.perform(environment)}.flatten
environment = mixin.args.zip(@args).
inject(Sass::Environment.new(mixin.environment)) do |env, ((var, default), value)|
env.set_local_var(var.name,
if value
value.perform(environment)
elsif default
val = default.perform(env)
if default.context == :equals && val.is_a?(Sass::Script::String)
val = Sass::Script::String.new(val.value)
end
val
end)
raise Sass::SyntaxError.new("Mixin #{@name} is missing parameter #{var.inspect}.") unless env.var(var.name)
env
end

self.children = mixin.tree.map {|c| c.perform(environment)}.flatten
rescue Sass::SyntaxError => e
e.modify_backtrace(:mixin => @name, :line => @line)
e.add_backtrace(:line => @line)
raise e
ensure
original_env.pop
original_env.pop_frame
end
end
end
23 changes: 9 additions & 14 deletions lib/sass/tree/warn_node.rb
Expand Up @@ -21,25 +21,20 @@ def to_src(tabs, opts, fmt)
# @param environment [Sass::Environment] The lexical environment containing
# variable and mixin values
def _perform(environment)
environment.push(:filename => filename, :line => line)
environment.push_frame(:filename => filename, :line => line)
res = @expr.perform(environment)
res = res.value if res.is_a?(Sass::Script::String)
Haml::Util.haml_warn "WARNING: #{res}"
Haml::Util.enum_with_index(environment.stack.reverse).each do |entry, i|
where = " "
if entry[:mixin]
where << "via '#{entry[:mixin]}' mixed in at line #{entry[:line]}"
elsif entry[:import]
where << "imported from line #{entry[:line]}"
else
where << "#{"issued" if i == 0} from line #{entry[:line]}"
end
where << " of #{entry[:filename] || "(sass)"}"
Haml::Util.haml_warn where
msg = "WARNING: #{res}\n"
environment.stack.reverse.each_with_index do |entry, i|
msg << " #{i == 0 ? "on" : "from"} line #{entry[:line]}" <<
" of #{entry[:filename] || "an unknown file"}"
msg << ", in `#{entry[:mixin]}'" if entry[:mixin]
msg << "\n"
end
Haml::Util.haml_warn msg
[]
ensure
environment.pop
environment.pop_frame
end
end
end
Expand Down
19 changes: 11 additions & 8 deletions test/sass/engine_test.rb
Expand Up @@ -1715,10 +1715,11 @@ def test_equals_properties_force_division
def test_warn_directive
expected_warning = <<EXPECTATION
WARNING: this is a warning
issued from line 4 of test_warn_directive_inline.sass
on line 4 of test_warn_directive_inline.sass
WARNING: this is a mixin warning
issued from line 2 of test_warn_directive_inline.sass
via 'foo' mixed in at line 7 of test_warn_directive_inline.sass
on line 2 of test_warn_directive_inline.sass, in `foo'
from line 7 of test_warn_directive_inline.sass
EXPECTATION
assert_warning expected_warning do
assert_equal <<CSS, render(<<SASS)
Expand Down Expand Up @@ -1748,13 +1749,15 @@ def test_warn_directive_when_quiet
def test_warn_with_imports
expected_warning = <<WARN
WARNING: In the main file
issued from line 1 of #{File.dirname(__FILE__)}/templates/warn.sass
on line 1 of #{File.dirname(__FILE__)}/templates/warn.sass
WARNING: Imported
issued from line 1 of #{File.dirname(__FILE__)}/templates/warn_imported.sass
imported from line 2 of #{File.dirname(__FILE__)}/templates/warn.sass
on line 1 of #{File.dirname(__FILE__)}/templates/warn_imported.sass
from line 2 of #{File.dirname(__FILE__)}/templates/warn.sass
WARNING: In an imported mixin
issued from line 4 of #{File.dirname(__FILE__)}/templates/warn_imported.sass
via 'emits-a-warning' mixed in at line 3 of #{File.dirname(__FILE__)}/templates/warn.sass
on line 4 of #{File.dirname(__FILE__)}/templates/warn_imported.sass, in `emits-a-warning'
from line 3 of #{File.dirname(__FILE__)}/templates/warn.sass
WARN
assert_warning expected_warning do
renders_correctly "warn", :style => :compact, :load_paths => [File.dirname(__FILE__) + "/templates"]
Expand Down
7 changes: 4 additions & 3 deletions test/sass/scss/scss_test.rb
Expand Up @@ -116,10 +116,11 @@ def test_debug_directive
def test_warn_directive
expected_warning = <<EXPECTATION
WARNING: this is a warning
issued from line 2 of test_warn_directive_inline.scss
on line 2 of test_warn_directive_inline.scss
WARNING: this is a mixin
issued from line 1 of test_warn_directive_inline.scss
via 'foo' mixed in at line 3 of test_warn_directive_inline.scss
on line 1 of test_warn_directive_inline.scss, in `foo'
from line 3 of test_warn_directive_inline.scss
EXPECTATION
assert_warning expected_warning do
assert_equal <<CSS, render(<<SCSS)
Expand Down

0 comments on commit 11f2cf1

Please sign in to comment.