diff --git a/src/check_nesting.cpp b/src/check_nesting.cpp index 98199cebf2..93a4d0485a 100644 --- a/src/check_nesting.cpp +++ b/src/check_nesting.cpp @@ -6,52 +6,374 @@ namespace Sass { CheckNesting::CheckNesting() - : parent_stack(std::vector()) + : parents(std::vector()), + parent(0), + current_mixin_definition(0) { } - AST_Node* CheckNesting::parent() - { - if (parent_stack.size() > 0) - return parent_stack.back(); - return 0; + Statement* CheckNesting::before(Statement* s) { + if (this->should_visit(s)) return s; + return 0; } + Statement* CheckNesting::visit_children(Statement* parent) { + + Statement* old_parent = this->parent; + + if (dynamic_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* p = this->parents.at(i); + if (!dynamic_cast(parent)->exclude_node(p)) { + new_parents.push_back(p); + } + } + this->parents = new_parents; + + for (size_t i = this->parents.size(); i > 0; i--) { + Statement* p = 0; + Statement* 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* ar = dynamic_cast(parent); + Statement* 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* b = dynamic_cast(parent); + + if (!b) { + if (Has_Block* bb = dynamic_cast(parent)) { + b = bb->block(); + } + } + + if (b) { + for (auto n : *b) { + n->perform(this); + } + } + + this->parent = old_parent; + this->parents.pop_back(); + + return b; + } + + Statement* CheckNesting::operator()(Block* b) { - parent_stack.push_back(b); + return this->visit_children(b); + } + + Statement* CheckNesting::operator()(Definition* n) + { + if (!is_mixin(n)) return n; + + Definition* old_mixin_definition = this->current_mixin_definition; + this->current_mixin_definition = n; - for (auto n : *b) { - n->perform(this); + visit_children(n); + + this->current_mixin_definition = old_mixin_definition; + + return n; + } + + Statement* CheckNesting::fallback_impl(Statement* s) + { + if (dynamic_cast(s) || dynamic_cast(s)) { + return visit_children(s); } + return s; + } - parent_stack.pop_back(); - return b; + bool CheckNesting::should_visit(Statement* node) + { + if (!this->parent) return true; + + if (dynamic_cast(node)) + { this->invalid_content_parent(this->parent); } + + if (is_charset(node)) + { this->invalid_charset_parent(this->parent); } + + if (dynamic_cast(node)) + { this->invalid_extend_parent(this->parent); } + + // if (dynamic_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 (dynamic_cast(node)) + { this->invalid_prop_parent(this->parent); } + + if ( + dynamic_cast(this->parent) + ) { this->invalid_prop_child(node); } + + if (dynamic_cast(node)) + { this->invalid_return_parent(this->parent); } + + return true; } - Statement* CheckNesting::operator()(Declaration* d) + void CheckNesting::invalid_content_parent(Statement* parent) { - if (!is_valid_prop_parent(parent())) { - throw Exception::InvalidSass(d->pstate(), "Properties are only allowed " - "within rules, directives, mixin includes, or other properties."); + if (!this->current_mixin_definition) { + throw Exception::InvalidSass( + parent->pstate(), + "@content may only be used within a mixin." + ); } - return static_cast(d); } - Statement* CheckNesting::fallback_impl(AST_Node* n) + void CheckNesting::invalid_charset_parent(Statement* parent) { - return static_cast(n); + if (!( + is_root_node(parent) + )) { + throw Exception::InvalidSass( + parent->pstate(), + "@charset may only be used at the root of a document." + ); + } } - bool CheckNesting::is_valid_prop_parent(AST_Node* p) + void CheckNesting::invalid_extend_parent(Statement* parent) { - if (Definition* def = dynamic_cast(p)) { - return def->type() == Definition::MIXIN; + if (!( + dynamic_cast(parent) || + dynamic_cast(parent) || + is_mixin(parent) + )) { + throw Exception::InvalidSass( + parent->pstate(), + "Extend directives may only be used within rules." + ); } + } - return dynamic_cast(p) || - dynamic_cast(p) || - dynamic_cast(p) || - dynamic_cast(p) || - dynamic_cast(p); + // void CheckNesting::invalid_import_parent(Statement* parent) + // { + // for (auto pp : this->parents) { + // if ( + // dynamic_cast(pp) || + // dynamic_cast(pp) || + // dynamic_cast(pp) || + // dynamic_cast(pp) || + // dynamic_cast(pp) || + // dynamic_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* parent) + { + for (auto pp : this->parents) { + if ( + dynamic_cast(pp) || + dynamic_cast(pp) || + dynamic_cast(pp) || + dynamic_cast(pp) || + dynamic_cast(pp) || + dynamic_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* parent) + { + for (auto pp : this->parents) { + if ( + dynamic_cast(pp) || + dynamic_cast(pp) || + dynamic_cast(pp) || + dynamic_cast(pp) || + dynamic_cast(pp) || + dynamic_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* child) + { + if (!( + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) + )) { + throw Exception::InvalidSass( + child->pstate(), + "Functions can only contain variable declarations and control directives." + ); + } + } + + void CheckNesting::invalid_prop_child(Statement* child) + { + if (!( + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) || + dynamic_cast(child) + )) { + throw Exception::InvalidSass( + child->pstate(), + "Illegal nesting: Only properties may be nested beneath properties." + ); + } + } + + void CheckNesting::invalid_prop_parent(Statement* parent) + { + if (!( + is_mixin(parent) || + is_directive_node(parent) || + dynamic_cast(parent) || + dynamic_cast(parent) || + dynamic_cast(parent) || + dynamic_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* 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* parent, Statement* grandparent) + { + bool parent_bubbles = parent && parent->bubbles(); + + bool valid_bubble_node = parent_bubbles && + !is_root_node(grandparent) && + !is_at_root_node(grandparent); + + return dynamic_cast(parent) || + dynamic_cast(parent) || + dynamic_cast(parent) || + dynamic_cast(parent) || + dynamic_cast(parent) || + dynamic_cast(parent) || + valid_bubble_node; + } + + bool CheckNesting::is_charset(Statement* n) + { + Directive* d = dynamic_cast(n); + return d && d->keyword() == "charset"; + } + + bool CheckNesting::is_mixin(Statement* n) + { + Definition* def = dynamic_cast(n); + return def && def->type() == Definition::MIXIN; + } + + bool CheckNesting::is_function(Statement* n) + { + Definition* def = dynamic_cast(n); + return def && def->type() == Definition::FUNCTION; + } + + bool CheckNesting::is_root_node(Statement* n) + { + if (dynamic_cast(n)) return false; + + Block* b = dynamic_cast(n); + return b && b->is_root(); + } + + bool CheckNesting::is_at_root_node(Statement* n) + { + return dynamic_cast(n); + } + + bool CheckNesting::is_directive_node(Statement* n) + { + return dynamic_cast(n) || + dynamic_cast(n) || + dynamic_cast(n) || + dynamic_cast(n); } } diff --git a/src/check_nesting.hpp b/src/check_nesting.hpp index e24e774362..42588bd4f2 100644 --- a/src/check_nesting.hpp +++ b/src/check_nesting.hpp @@ -10,23 +10,49 @@ namespace Sass { class CheckNesting : public Operation_CRTP { - std::vector parent_stack; + std::vector parents; + Statement* parent; + Definition* current_mixin_definition; - AST_Node* parent(); - - Statement* fallback_impl(AST_Node* n); + Statement* fallback_impl(Statement*); + Statement* before(Statement*); + Statement* visit_children(Statement*); public: CheckNesting(); ~CheckNesting() { } Statement* operator()(Block*); - Statement* operator()(Declaration*); + Statement* operator()(Definition*); template - Statement* fallback(U x) { return fallback_impl(x); } - - bool is_valid_prop_parent(AST_Node*); + Statement* fallback(U x) { + return fallback_impl(this->before(dynamic_cast(x))); + } + + private: + void invalid_content_parent(Statement*); + void invalid_charset_parent(Statement*); + void invalid_extend_parent(Statement*); + // void invalid_import_parent(Statement*); + void invalid_mixin_definition_parent(Statement*); + void invalid_function_parent(Statement*); + + void invalid_function_child(Statement*); + void invalid_prop_child(Statement*); + void invalid_prop_parent(Statement*); + void invalid_return_parent(Statement*); + + bool is_transparent_parent(Statement*, Statement*); + + bool should_visit(Statement*); + + bool is_charset(Statement*); + bool is_mixin(Statement*); + bool is_function(Statement*); + bool is_root_node(Statement*); + bool is_at_root_node(Statement*); + bool is_directive_node(Statement*); }; } diff --git a/src/expand.cpp b/src/expand.cpp index 1935a22d4e..3d5d6c80f8 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -21,7 +21,8 @@ namespace Sass { selector_stack(std::vector()), media_block_stack(std::vector()), backtrace_stack(std::vector()), - in_keyframes(false) + in_keyframes(false), + at_root_without_rule(false) { env_stack.push_back(0); env_stack.push_back(env); @@ -86,6 +87,7 @@ namespace Sass { Statement* Expand::operator()(Ruleset* r) { + bool old_at_root_without_rule = this->at_root_without_rule; // reset when leaving scope if (in_keyframes) { @@ -98,6 +100,8 @@ namespace Sass { return k; } + this->at_root_without_rule = false; + // do some special checks for the base level rules if (r->is_root()) { if (Selector_List* selector_list = dynamic_cast(r->selector())) { @@ -150,6 +154,8 @@ namespace Sass { rr->is_root(r->is_root()); rr->tabs(r->tabs()); + this->at_root_without_rule = old_at_root_without_rule; + return rr; } @@ -185,16 +191,26 @@ namespace Sass { Statement* Expand::operator()(At_Root_Block* a) { Block* ab = a->block(); - // if (ab) ab->is_root(true); Expression* ae = a->expression(); + if (ae) ae = ae->perform(&eval); else ae = SASS_MEMORY_NEW(ctx.mem, At_Root_Query, a->pstate()); + + bool old_at_root_without_rule = this->at_root_without_rule; + bool old_in_keyframes = this->in_keyframes; + + this->at_root_without_rule = true; + this->in_keyframes = false; + Block* bb = ab ? ab->perform(this)->block() : 0; At_Root_Block* aa = SASS_MEMORY_NEW(ctx.mem, At_Root_Block, a->pstate(), bb, static_cast(ae)); - // aa->block()->is_root(true); + + this->at_root_without_rule = old_at_root_without_rule; + this->in_keyframes = old_in_keyframes; + return aa; } @@ -675,10 +691,22 @@ namespace Sass { } bind(std::string("Mixin"), c->name(), params, args, &ctx, &new_env, &eval); - append_block(body); - backtrace_stack.pop_back(); + + Block* trace_block = SASS_MEMORY_NEW(ctx.mem, Block, c->pstate()); + Trace* trace = SASS_MEMORY_NEW(ctx.mem, Trace, c->pstate(), c->name(), trace_block); + + + block_stack.push_back(trace_block); + for (auto bb : *body) { + Statement* ith = bb->perform(this); + if (ith) *trace->block() << ith; + } + block_stack.pop_back(); + env_stack.pop_back(); - return 0; + backtrace_stack.pop_back(); + + return trace; } Statement* Expand::operator()(Content* c) @@ -686,18 +714,23 @@ namespace Sass { Env* env = environment(); // convert @content directives into mixin calls to the underlying thunk if (!env->has("@content[m]")) return 0; + if (block_stack.back()->is_root()) { selector_stack.push_back(0); } + Mixin_Call* call = SASS_MEMORY_NEW(ctx.mem, Mixin_Call, c->pstate(), "@content", SASS_MEMORY_NEW(ctx.mem, Arguments, c->pstate())); - Statement* stm = call->perform(this); + + Trace* trace = dynamic_cast(call->perform(this)); + if (block_stack.back()->is_root()) { selector_stack.pop_back(); } - return stm; + + return trace; } // produce an error if something is not implemented diff --git a/src/expand.hpp b/src/expand.hpp index 5acbd4d4a6..6ffe8dbf5b 100644 --- a/src/expand.hpp +++ b/src/expand.hpp @@ -28,14 +28,15 @@ namespace Sass { Eval eval; // it's easier to work with vectors - std::vector env_stack; - std::vector block_stack; - std::vector call_stack; - std::vector property_stack; + std::vector env_stack; + std::vector block_stack; + std::vector call_stack; + std::vector property_stack; std::vector selector_stack; - std::vector media_block_stack; - std::vectorbacktrace_stack; - bool in_keyframes; + std::vector media_block_stack; + std::vector backtrace_stack; + bool in_keyframes; + bool at_root_without_rule; Statement* fallback_impl(AST_Node* n); diff --git a/src/parser.cpp b/src/parser.cpp index 99dc7c8b92..fa295c840a 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1851,16 +1851,6 @@ namespace Sass { Content* Parser::parse_content_directive() { - bool missing_mixin_parent = true; - for (auto parent : stack) { - if (parent == Scope::Mixin) { - missing_mixin_parent = false; - break; - } - } - if (missing_mixin_parent) { - error("@content may only be used within a mixin", pstate); - } return SASS_MEMORY_NEW(ctx.mem, Content, pstate); }