#include "sass.hpp" #include #include "check_nesting.hpp" namespace Sass { CheckNesting::CheckNesting() : parents(std::vector()), parent(0), current_mixin_definition(0) { } Statement_Ptr CheckNesting::visit_children(Statement_Ptr parent) { Statement_Ptr old_parent = this->parent; if (At_Root_Block_Ptr root = Cast(parent)) { std::vector old_parents = this->parents; std::vector new_parents; for (size_t i = 0, L = this->parents.size(); i < L; i++) { Statement_Ptr p = this->parents.at(i); if (!root->exclude_node(p)) { new_parents.push_back(p); } } this->parents = new_parents; for (size_t i = this->parents.size(); i > 0; i--) { Statement_Ptr p = 0; Statement_Ptr gp = 0; if (i > 0) p = this->parents.at(i - 1); if (i > 1) gp = this->parents.at(i - 2); if (!this->is_transparent_parent(p, gp)) { this->parent = p; break; } } At_Root_Block_Ptr ar = Cast(parent); Statement_Ptr ret = this->visit_children(ar->block()); this->parent = old_parent; this->parents = old_parents; return ret; } if (!this->is_transparent_parent(parent, old_parent)) { this->parent = parent; } this->parents.push_back(parent); Block_Ptr b = Cast(parent); if (!b) { if (Has_Block_Ptr bb = Cast(parent)) { b = bb->block(); } } if (b) { for (auto n : b->elements()) { n->perform(this); } } this->parent = old_parent; this->parents.pop_back(); return b; } Statement_Ptr CheckNesting::operator()(Block_Ptr b) { return this->visit_children(b); } Statement_Ptr CheckNesting::operator()(Definition_Ptr n) { if (!this->should_visit(n)) return NULL; if (!is_mixin(n)) { visit_children(n); return n; } Definition_Ptr old_mixin_definition = this->current_mixin_definition; this->current_mixin_definition = n; visit_children(n); this->current_mixin_definition = old_mixin_definition; return n; } Statement_Ptr CheckNesting::fallback_impl(Statement_Ptr s) { Block_Ptr b1 = Cast(s); Has_Block_Ptr b2 = Cast(s); return b1 || b2 ? visit_children(s) : s; } bool CheckNesting::should_visit(Statement_Ptr node) { if (!this->parent) return true; if (Cast(node)) { this->invalid_content_parent(this->parent); } if (is_charset(node)) { this->invalid_charset_parent(this->parent); } if (Cast(node)) { this->invalid_extend_parent(this->parent); } // if (Cast(node)) // { this->invalid_import_parent(this->parent); } if (this->is_mixin(node)) { this->invalid_mixin_definition_parent(this->parent); } if (this->is_function(node)) { this->invalid_function_parent(this->parent); } if (this->is_function(this->parent)) { this->invalid_function_child(node); } if (Cast(node)) { this->invalid_prop_parent(this->parent); } if (Cast(this->parent)) { this->invalid_prop_child(node); } if (Cast(node)) { this->invalid_return_parent(this->parent); } return true; } void CheckNesting::invalid_content_parent(Statement_Ptr parent) { if (!this->current_mixin_definition) { throw Exception::InvalidSass( parent->pstate(), "@content may only be used within a mixin." ); } } void CheckNesting::invalid_charset_parent(Statement_Ptr parent) { if (!( is_root_node(parent) )) { throw Exception::InvalidSass( parent->pstate(), "@charset may only be used at the root of a document." ); } } void CheckNesting::invalid_extend_parent(Statement_Ptr parent) { if (!( Cast(parent) || Cast(parent) || is_mixin(parent) )) { throw Exception::InvalidSass( parent->pstate(), "Extend directives may only be used within rules." ); } } // void CheckNesting::invalid_import_parent(Statement_Ptr parent) // { // for (auto pp : this->parents) { // if ( // Cast(pp) || // Cast(pp) || // Cast(pp) || // Cast(pp) || // Cast(pp) || // Cast(pp) || // is_mixin(pp) // ) { // throw Exception::InvalidSass( // parent->pstate(), // "Import directives may not be defined within control directives or other mixins." // ); // } // } // if (this->is_root_node(parent)) { // return; // } // if (false/*n.css_import?*/) { // throw Exception::InvalidSass( // parent->pstate(), // "CSS import directives may only be used at the root of a document." // ); // } // } void CheckNesting::invalid_mixin_definition_parent(Statement_Ptr parent) { for (Statement_Ptr pp : this->parents) { if ( Cast(pp) || Cast(pp) || Cast(pp) || Cast(pp) || Cast(pp) || Cast(pp) || is_mixin(pp) ) { throw Exception::InvalidSass( parent->pstate(), "Mixins may not be defined within control directives or other mixins." ); } } } void CheckNesting::invalid_function_parent(Statement_Ptr parent) { for (Statement_Ptr pp : this->parents) { if ( Cast(pp) || Cast(pp) || Cast(pp) || Cast(pp) || Cast(pp) || Cast(pp) || is_mixin(pp) ) { throw Exception::InvalidSass( parent->pstate(), "Functions may not be defined within control directives or other mixins." ); } } } void CheckNesting::invalid_function_child(Statement_Ptr child) { if (!( Cast(child) || Cast(child) || Cast(child) || Cast(child) || Cast(child) || Cast(child) || Cast(child) || Cast(child) || Cast(child) || // Ruby Sass doesn't distinguish variables and assignments Cast(child) || Cast(child) || Cast(child) )) { throw Exception::InvalidSass( child->pstate(), "Functions can only contain variable declarations and control directives." ); } } void CheckNesting::invalid_prop_child(Statement_Ptr child) { if (!( Cast(child) || Cast(child) || Cast(child) || Cast(child) || Cast(child) || Cast(child) || Cast(child) || Cast(child) )) { throw Exception::InvalidSass( child->pstate(), "Illegal nesting: Only properties may be nested beneath properties." ); } } void CheckNesting::invalid_prop_parent(Statement_Ptr parent) { if (!( is_mixin(parent) || is_directive_node(parent) || Cast(parent) || Cast(parent) || Cast(parent) || Cast(parent) )) { throw Exception::InvalidSass( parent->pstate(), "Properties are only allowed within rules, directives, mixin includes, or other properties." ); } } void CheckNesting::invalid_return_parent(Statement_Ptr parent) { if (!this->is_function(parent)) { throw Exception::InvalidSass( parent->pstate(), "@return may only be used within a function." ); } } bool CheckNesting::is_transparent_parent(Statement_Ptr parent, Statement_Ptr grandparent) { bool parent_bubbles = parent && parent->bubbles(); bool valid_bubble_node = parent_bubbles && !is_root_node(grandparent) && !is_at_root_node(grandparent); return Cast(parent) || Cast(parent) || Cast(parent) || Cast(parent) || Cast(parent) || Cast(parent) || valid_bubble_node; } bool CheckNesting::is_charset(Statement_Ptr n) { Directive_Ptr d = Cast(n); return d && d->keyword() == "charset"; } bool CheckNesting::is_mixin(Statement_Ptr n) { Definition_Ptr def = Cast(n); return def && def->type() == Definition::MIXIN; } bool CheckNesting::is_function(Statement_Ptr n) { Definition_Ptr def = Cast(n); return def && def->type() == Definition::FUNCTION; } bool CheckNesting::is_root_node(Statement_Ptr n) { if (Cast(n)) return false; Block_Ptr b = Cast(n); return b && b->is_root(); } bool CheckNesting::is_at_root_node(Statement_Ptr n) { return Cast(n) != NULL; } bool CheckNesting::is_directive_node(Statement_Ptr n) { return Cast(n) || Cast(n) || Cast(n) || Cast(n); } }