Skip to content

Commit

Permalink
Allow mixins and functions to be defined in nested contexts.
Browse files Browse the repository at this point in the history
Closes #427
  • Loading branch information
nex3 committed Jul 20, 2012
1 parent 1edbe58 commit 9b1820a
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 51 deletions.
4 changes: 4 additions & 0 deletions doc-src/SASS_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ that make use of `@media` and other directives dynamically.

### Smaller Improvements

* Mixins and functions may now be defined in a nested context, for example
within `@media` rules. This also allows files containing them to be imported
in such contexts.

* Previously, only the `:-moz-any` selector was supported; this has been
expanded to support any vendor prefix, as well as the plain `:any` selector.

Expand Down
38 changes: 19 additions & 19 deletions lib/sass/tree/visitors/check_nesting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,6 @@ def invalid_extend_parent?(parent, child)
end
end

def invalid_function_parent?(parent, child)
"Functions may only be defined at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
end

VALID_FUNCTION_CHILDREN = [
Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::ReturnNode,
Sass::Tree::VariableNode, Sass::Tree::WarnNode
] + CONTROL_NODES
def invalid_function_child?(parent, child)
unless is_any_of?(child, VALID_FUNCTION_CHILDREN)
"Functions can only contain variable declarations and control directives."
end
end

INVALID_IMPORT_PARENTS = CONTROL_NODES +
[Sass::Tree::MixinDefNode, Sass::Tree::MixinNode]
def invalid_import_parent?(parent, child)
Expand All @@ -95,18 +81,32 @@ def invalid_import_parent?(parent, child)
end
return if parent.is_a?(Sass::Tree::RootNode)
return "CSS import directives may only be used at the root of a document." if child.css_import?
# If this is a nested @import, we need to make sure it doesn't have anything
# that's legal at top-level but not in the current context (e.g. mixin defs).
child.imported_file.to_tree.children.each {|c| visit(c)}
nil
rescue Sass::SyntaxError => e
e.modify_backtrace(:filename => child.imported_file.options[:filename])
e.add_backtrace(:filename => child.filename, :line => child.line)
raise e
end

def invalid_mixindef_parent?(parent, child)
"Mixins may only be defined at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
unless (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
return "Mixins may not be defined within control directives or other mixins."
end
end

def invalid_function_parent?(parent, child)
unless (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
return "Functions may not be defined within control directives or other mixins."
end
end

VALID_FUNCTION_CHILDREN = [
Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::ReturnNode,
Sass::Tree::VariableNode, Sass::Tree::WarnNode
] + CONTROL_NODES
def invalid_function_child?(parent, child)
unless is_any_of?(child, VALID_FUNCTION_CHILDREN)
"Functions can only contain variable declarations and control directives."
end
end

VALID_PROP_CHILDREN = [Sass::Tree::CommentNode, Sass::Tree::PropNode, Sass::Tree::MixinNode] + CONTROL_NODES
Expand Down
4 changes: 2 additions & 2 deletions lib/sass/tree/visitors/perform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def visit_for(node)

# Loads the function into the environment.
def visit_function(node)
@environment.set_function(node.name,
@environment.set_local_function(node.name,
Sass::Callable.new(node.name, node.args, @environment, node.children, !:has_content))
[]
end
Expand Down Expand Up @@ -155,7 +155,7 @@ def visit_import(node)

# Loads a mixin into the environment.
def visit_mixindef(node)
@environment.set_mixin(node.name,
@environment.set_local_mixin(node.name,
Sass::Callable.new(node.name, node.args, @environment, node.children, node.has_content))
[]
end
Expand Down
25 changes: 3 additions & 22 deletions test/sass/engine_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class SassEngineTest < Test::Unit::TestCase
"=foo\n :color red\n.bar\n +bang" => "Undefined mixin 'bang'.",
"=foo\n :color red\n.bar\n +bang_bop" => "Undefined mixin 'bang_bop'.",
"=foo\n :color red\n.bar\n +bang-bop" => "Undefined mixin 'bang-bop'.",
".bar\n =foo\n :color red\n" => ["Mixins may only be defined at the root of a document.", 2],
".foo\n =foo\n :color red\n.bar\n +foo" => "Undefined mixin 'foo'.",
" a\n b: c" => ["Indenting at the beginning of the document is illegal.", 1],
" \n \n\t\n a\n b: c" => ["Indenting at the beginning of the document is illegal.", 4],
"a\n b: c\n b: c" => ["Inconsistent indentation: 1 space was used for indentation, but the rest of the document was indented using 2 spaces.", 3],
Expand Down Expand Up @@ -105,7 +105,6 @@ class SassEngineTest < Test::Unit::TestCase
"@function foo($)\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was "$)"', 1],
"@function foo()\n @return" => 'Invalid @return: expected expression.',
"@function foo()\n @return 1\n $var: val" => 'Illegal nesting: Nothing may be nested beneath return directives.',
"foo\n @function bar()\n @return 1" => ['Functions may only be defined at the root of a document.', 2],
"@function foo($a)\n @return 1\na\n b: foo()" => 'Function foo is missing argument $a',
"@function foo()\n @return 1\na\n b: foo(2)" => 'Wrong number of arguments (1 for 0) for `foo\'',
"@function foo()\n @return 1\na\n b: foo($a: 1)" => "Function foo doesn't have an argument named $a",
Expand Down Expand Up @@ -274,7 +273,7 @@ def test_exception_location
end

def test_imported_exception
[1, 2, 3, 4, 5].each do |i|
[1, 2, 3, 4].each do |i|
begin
Sass::Engine.new("@import bork#{i}", :load_paths => [File.dirname(__FILE__) + '/templates/']).render
rescue Sass::SyntaxError => err
Expand All @@ -296,7 +295,7 @@ def test_imported_exception
end

def test_double_imported_exception
[1, 2, 3, 4, 5].each do |i|
[1, 2, 3, 4].each do |i|
begin
Sass::Engine.new("@import nested_bork#{i}", :load_paths => [File.dirname(__FILE__) + '/templates/']).render
rescue Sass::SyntaxError => err
Expand Down Expand Up @@ -723,24 +722,6 @@ def test_import_in_rule
SASS
end

def test_nested_import_with_toplevel_constructs
Sass::Engine.new(".foo\n @import importee", :load_paths => [File.dirname(__FILE__) + '/templates/']).render
rescue Sass::SyntaxError => err
assert_equal(3, err.sass_line)
assert_match(/(\/|^)importee\.sass$/, err.sass_filename)

assert_hash_has(err.sass_backtrace.first,
:filename => err.sass_filename, :line => err.sass_line)

assert_nil(err.sass_backtrace[1][:filename])
assert_equal(2, err.sass_backtrace[1][:line])

assert_match(/(\/|^)importee\.sass:3$/, err.backtrace.first)
assert_equal("(sass):2", err.backtrace[1])
else
assert(false, "Exception not raised for importing mixins nested")
end

def test_units
renders_correctly "units"
end
Expand Down
72 changes: 69 additions & 3 deletions test/sass/scss/scss_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1073,17 +1073,83 @@ def test_random_directive_interpolation
SCSS
end

def test_nested_mixin_def
assert_equal <<CSS, render(<<SCSS)
foo {
a: b; }
CSS
foo {
@mixin bar {a: b}
@include bar; }
SCSS
end

def test_nested_mixin_shadow
assert_equal <<CSS, render(<<SCSS)
foo {
c: d; }
baz {
a: b; }
CSS
@mixin bar {a: b}
foo {
@mixin bar {c: d}
@include bar;
}
baz {@include bar}
SCSS
end

def test_nested_function_def
assert_equal <<CSS, render(<<SCSS)
foo {
a: 1; }
bar {
b: foo(); }
CSS
foo {
@function foo() {@return 1}
a: foo(); }
bar {b: foo()}
SCSS
end

def test_nested_function_shadow
assert_equal <<CSS, render(<<SCSS)
foo {
a: 2; }
baz {
b: 1; }
CSS
@function foo() {@return 1}
foo {
@function foo() {@return 2}
a: foo();
}
baz {b: foo()}
SCSS
end

## Errors

def test_mixin_defs_only_at_toplevel
def test_nested_mixin_def_is_scoped
render <<SCSS
foo {
@mixin bar {a: b}}
bar {@include bar}
SCSS
assert(false, "Expected syntax error")
rescue Sass::SyntaxError => e
assert_equal "Mixins may only be defined at the root of a document.", e.message
assert_equal 2, e.sass_line
assert_equal "Undefined mixin 'bar'.", e.message
assert_equal 3, e.sass_line
end

def test_rules_beneath_properties
Expand Down
3 changes: 0 additions & 3 deletions test/sass/templates/bork5.sass

This file was deleted.

2 changes: 0 additions & 2 deletions test/sass/templates/nested_bork5.sass

This file was deleted.

0 comments on commit 9b1820a

Please sign in to comment.