1 | // sass.hpp must go before all system headers to get the |
2 | // __EXTENSIONS__ fix on Solaris. |
3 | #include "sass.hpp" |
4 | #include "ast.hpp" |
5 | #include "check_nesting.hpp" |
6 | |
7 | namespace Sass { |
8 | |
9 | CheckNesting::CheckNesting() |
10 | : parents(sass::vector<Statement*>()), |
11 | traces(sass::vector<Backtrace>()), |
12 | parent(0), current_mixin_definition(0) |
13 | { } |
14 | |
15 | void error(AST_Node* node, Backtraces traces, sass::string msg) { |
16 | traces.push_back(x: Backtrace(node->pstate())); |
17 | throw Exception::InvalidSass(node->pstate(), traces, msg); |
18 | } |
19 | |
20 | Statement* CheckNesting::visit_children(Statement* parent) |
21 | { |
22 | Statement* old_parent = this->parent; |
23 | |
24 | if (AtRootRule* root = Cast<AtRootRule>(ptr: parent)) { |
25 | sass::vector<Statement*> old_parents = this->parents; |
26 | sass::vector<Statement*> new_parents; |
27 | |
28 | for (size_t i = 0, L = this->parents.size(); i < L; i++) { |
29 | Statement* p = this->parents.at(n: i); |
30 | if (!root->exclude_node(s: p)) { |
31 | new_parents.push_back(x: p); |
32 | } |
33 | } |
34 | this->parents = new_parents; |
35 | |
36 | for (size_t i = this->parents.size(); i > 0; i--) { |
37 | Statement* p = 0; |
38 | Statement* gp = 0; |
39 | if (i > 0) p = this->parents.at(n: i - 1); |
40 | if (i > 1) gp = this->parents.at(n: i - 2); |
41 | |
42 | if (!this->is_transparent_parent(p, gp)) { |
43 | this->parent = p; |
44 | break; |
45 | } |
46 | } |
47 | |
48 | AtRootRule* ar = Cast<AtRootRule>(ptr: parent); |
49 | Block* ret = ar->block(); |
50 | |
51 | if (ret != NULL) { |
52 | for (auto n : ret->elements()) { |
53 | n->perform(op: this); |
54 | } |
55 | } |
56 | |
57 | this->parent = old_parent; |
58 | this->parents = old_parents; |
59 | |
60 | return ret; |
61 | } |
62 | |
63 | if (!this->is_transparent_parent(parent, old_parent)) { |
64 | this->parent = parent; |
65 | } |
66 | |
67 | this->parents.push_back(x: parent); |
68 | |
69 | Block* b = Cast<Block>(ptr: parent); |
70 | |
71 | if (Trace* trace = Cast<Trace>(ptr: parent)) { |
72 | if (trace->type() == 'i') { |
73 | this->traces.push_back(x: Backtrace(trace->pstate())); |
74 | } |
75 | } |
76 | |
77 | if (!b) { |
78 | if (ParentStatement* bb = Cast<ParentStatement>(ptr: parent)) { |
79 | b = bb->block(); |
80 | } |
81 | } |
82 | |
83 | if (b) { |
84 | for (auto n : b->elements()) { |
85 | n->perform(op: this); |
86 | } |
87 | } |
88 | |
89 | this->parent = old_parent; |
90 | this->parents.pop_back(); |
91 | |
92 | if (Trace* trace = Cast<Trace>(ptr: parent)) { |
93 | if (trace->type() == 'i') { |
94 | this->traces.pop_back(); |
95 | } |
96 | } |
97 | |
98 | return b; |
99 | } |
100 | |
101 | |
102 | Statement* CheckNesting::operator()(Block* b) |
103 | { |
104 | return this->visit_children(parent: b); |
105 | } |
106 | |
107 | Statement* CheckNesting::operator()(Definition* n) |
108 | { |
109 | if (!this->should_visit(n)) return NULL; |
110 | if (!is_mixin(n)) { |
111 | visit_children(parent: n); |
112 | return n; |
113 | } |
114 | |
115 | Definition* old_mixin_definition = this->current_mixin_definition; |
116 | this->current_mixin_definition = n; |
117 | |
118 | visit_children(parent: n); |
119 | |
120 | this->current_mixin_definition = old_mixin_definition; |
121 | |
122 | return n; |
123 | } |
124 | |
125 | Statement* CheckNesting::operator()(If* i) |
126 | { |
127 | this->visit_children(parent: i); |
128 | |
129 | if (Block* b = Cast<Block>(ptr: i->alternative())) { |
130 | for (auto n : b->elements()) n->perform(op: this); |
131 | } |
132 | |
133 | return i; |
134 | } |
135 | |
136 | bool CheckNesting::should_visit(Statement* node) |
137 | { |
138 | if (!this->parent) return true; |
139 | |
140 | if (Cast<Content>(ptr: node)) |
141 | { this->invalid_content_parent(this->parent, node); } |
142 | |
143 | if (is_charset(node)) |
144 | { this->invalid_charset_parent(this->parent, node); } |
145 | |
146 | if (Cast<ExtendRule>(ptr: node)) |
147 | { this->invalid_extend_parent(this->parent, node); } |
148 | |
149 | // if (Cast<Import>(node)) |
150 | // { this->invalid_import_parent(this->parent); } |
151 | |
152 | if (this->is_mixin(node)) |
153 | { this->invalid_mixin_definition_parent(this->parent, node); } |
154 | |
155 | if (this->is_function(node)) |
156 | { this->invalid_function_parent(this->parent, node); } |
157 | |
158 | if (this->is_function(this->parent)) |
159 | { this->invalid_function_child(node); } |
160 | |
161 | if (Declaration* d = Cast<Declaration>(ptr: node)) |
162 | { |
163 | this->invalid_prop_parent(this->parent, node); |
164 | this->invalid_value_child(d->value()); |
165 | } |
166 | |
167 | if (Cast<Declaration>(ptr: this->parent)) |
168 | { this->invalid_prop_child(node); } |
169 | |
170 | if (Cast<Return>(ptr: node)) |
171 | { this->invalid_return_parent(this->parent, node); } |
172 | |
173 | return true; |
174 | } |
175 | |
176 | void CheckNesting::invalid_content_parent(Statement* parent, AST_Node* node) |
177 | { |
178 | if (!this->current_mixin_definition) { |
179 | error(node, traces, msg: "@content may only be used within a mixin." ); |
180 | } |
181 | } |
182 | |
183 | void CheckNesting::invalid_charset_parent(Statement* parent, AST_Node* node) |
184 | { |
185 | if (!( |
186 | is_root_node(parent) |
187 | )) { |
188 | error(node, traces, msg: "@charset may only be used at the root of a document." ); |
189 | } |
190 | } |
191 | |
192 | void CheckNesting::invalid_extend_parent(Statement* parent, AST_Node* node) |
193 | { |
194 | if (!( |
195 | Cast<StyleRule>(ptr: parent) || |
196 | Cast<Mixin_Call>(ptr: parent) || |
197 | is_mixin(parent) |
198 | )) { |
199 | error(node, traces, msg: "Extend directives may only be used within rules." ); |
200 | } |
201 | } |
202 | |
203 | // void CheckNesting::invalid_import_parent(Statement* parent, AST_Node* node) |
204 | // { |
205 | // for (auto pp : this->parents) { |
206 | // if ( |
207 | // Cast<EachRule>(pp) || |
208 | // Cast<ForRule>(pp) || |
209 | // Cast<If>(pp) || |
210 | // Cast<WhileRule>(pp) || |
211 | // Cast<Trace>(pp) || |
212 | // Cast<Mixin_Call>(pp) || |
213 | // is_mixin(pp) |
214 | // ) { |
215 | // error(node, traces, "Import directives may not be defined within control directives or other mixins."); |
216 | // } |
217 | // } |
218 | |
219 | // if (this->is_root_node(parent)) { |
220 | // return; |
221 | // } |
222 | |
223 | // if (false/*n.css_import?*/) { |
224 | // error(node, traces, "CSS import directives may only be used at the root of a document."); |
225 | // } |
226 | // } |
227 | |
228 | void CheckNesting::invalid_mixin_definition_parent(Statement* parent, AST_Node* node) |
229 | { |
230 | for (Statement* pp : this->parents) { |
231 | if ( |
232 | Cast<EachRule>(ptr: pp) || |
233 | Cast<ForRule>(ptr: pp) || |
234 | Cast<If>(ptr: pp) || |
235 | Cast<WhileRule>(ptr: pp) || |
236 | Cast<Trace>(ptr: pp) || |
237 | Cast<Mixin_Call>(ptr: pp) || |
238 | is_mixin(pp) |
239 | ) { |
240 | error(node, traces, msg: "Mixins may not be defined within control directives or other mixins." ); |
241 | } |
242 | } |
243 | } |
244 | |
245 | void CheckNesting::invalid_function_parent(Statement* parent, AST_Node* node) |
246 | { |
247 | for (Statement* pp : this->parents) { |
248 | if ( |
249 | Cast<EachRule>(ptr: pp) || |
250 | Cast<ForRule>(ptr: pp) || |
251 | Cast<If>(ptr: pp) || |
252 | Cast<WhileRule>(ptr: pp) || |
253 | Cast<Trace>(ptr: pp) || |
254 | Cast<Mixin_Call>(ptr: pp) || |
255 | is_mixin(pp) |
256 | ) { |
257 | error(node, traces, msg: "Functions may not be defined within control directives or other mixins." ); |
258 | } |
259 | } |
260 | } |
261 | |
262 | void CheckNesting::invalid_function_child(Statement* child) |
263 | { |
264 | if (!( |
265 | Cast<EachRule>(ptr: child) || |
266 | Cast<ForRule>(ptr: child) || |
267 | Cast<If>(ptr: child) || |
268 | Cast<WhileRule>(ptr: child) || |
269 | Cast<Trace>(ptr: child) || |
270 | Cast<Comment>(ptr: child) || |
271 | Cast<DebugRule>(ptr: child) || |
272 | Cast<Return>(ptr: child) || |
273 | Cast<Variable>(ptr: child) || |
274 | // Ruby Sass doesn't distinguish variables and assignments |
275 | Cast<Assignment>(ptr: child) || |
276 | Cast<WarningRule>(ptr: child) || |
277 | Cast<ErrorRule>(ptr: child) |
278 | )) { |
279 | error(node: child, traces, msg: "Functions can only contain variable declarations and control directives." ); |
280 | } |
281 | } |
282 | |
283 | void CheckNesting::invalid_prop_child(Statement* child) |
284 | { |
285 | if (!( |
286 | Cast<EachRule>(ptr: child) || |
287 | Cast<ForRule>(ptr: child) || |
288 | Cast<If>(ptr: child) || |
289 | Cast<WhileRule>(ptr: child) || |
290 | Cast<Trace>(ptr: child) || |
291 | Cast<Comment>(ptr: child) || |
292 | Cast<Declaration>(ptr: child) || |
293 | Cast<Mixin_Call>(ptr: child) |
294 | )) { |
295 | error(node: child, traces, msg: "Illegal nesting: Only properties may be nested beneath properties." ); |
296 | } |
297 | } |
298 | |
299 | void CheckNesting::invalid_prop_parent(Statement* parent, AST_Node* node) |
300 | { |
301 | if (!( |
302 | is_mixin(parent) || |
303 | is_directive_node(parent) || |
304 | Cast<StyleRule>(ptr: parent) || |
305 | Cast<Keyframe_Rule>(ptr: parent) || |
306 | Cast<Declaration>(ptr: parent) || |
307 | Cast<Mixin_Call>(ptr: parent) |
308 | )) { |
309 | error(node, traces, msg: "Properties are only allowed within rules, directives, mixin includes, or other properties." ); |
310 | } |
311 | } |
312 | |
313 | void CheckNesting::invalid_value_child(AST_Node* d) |
314 | { |
315 | if (Map* m = Cast<Map>(ptr: d)) { |
316 | traces.push_back(x: Backtrace(m->pstate())); |
317 | throw Exception::InvalidValue(traces, *m); |
318 | } |
319 | if (Number* n = Cast<Number>(ptr: d)) { |
320 | if (!n->is_valid_css_unit()) { |
321 | traces.push_back(x: Backtrace(n->pstate())); |
322 | throw Exception::InvalidValue(traces, *n); |
323 | } |
324 | } |
325 | |
326 | // error(dbg + " isn't a valid CSS value.", m->pstate(),); |
327 | |
328 | } |
329 | |
330 | void CheckNesting::invalid_return_parent(Statement* parent, AST_Node* node) |
331 | { |
332 | if (!this->is_function(parent)) { |
333 | error(node, traces, msg: "@return may only be used within a function." ); |
334 | } |
335 | } |
336 | |
337 | bool CheckNesting::is_transparent_parent(Statement* parent, Statement* grandparent) |
338 | { |
339 | bool parent_bubbles = parent && parent->bubbles(); |
340 | |
341 | bool valid_bubble_node = parent_bubbles && |
342 | !is_root_node(grandparent) && |
343 | !is_at_root_node(grandparent); |
344 | |
345 | return Cast<Import>(ptr: parent) || |
346 | Cast<EachRule>(ptr: parent) || |
347 | Cast<ForRule>(ptr: parent) || |
348 | Cast<If>(ptr: parent) || |
349 | Cast<WhileRule>(ptr: parent) || |
350 | Cast<Trace>(ptr: parent) || |
351 | valid_bubble_node; |
352 | } |
353 | |
354 | bool CheckNesting::is_charset(Statement* n) |
355 | { |
356 | AtRule* d = Cast<AtRule>(ptr: n); |
357 | return d && d->keyword() == "charset" ; |
358 | } |
359 | |
360 | bool CheckNesting::is_mixin(Statement* n) |
361 | { |
362 | Definition* def = Cast<Definition>(ptr: n); |
363 | return def && def->type() == Definition::MIXIN; |
364 | } |
365 | |
366 | bool CheckNesting::is_function(Statement* n) |
367 | { |
368 | Definition* def = Cast<Definition>(ptr: n); |
369 | return def && def->type() == Definition::FUNCTION; |
370 | } |
371 | |
372 | bool CheckNesting::is_root_node(Statement* n) |
373 | { |
374 | if (Cast<StyleRule>(ptr: n)) return false; |
375 | |
376 | Block* b = Cast<Block>(ptr: n); |
377 | return b && b->is_root(); |
378 | } |
379 | |
380 | bool CheckNesting::is_at_root_node(Statement* n) |
381 | { |
382 | return Cast<AtRootRule>(ptr: n) != NULL; |
383 | } |
384 | |
385 | bool CheckNesting::is_directive_node(Statement* n) |
386 | { |
387 | return Cast<AtRule>(ptr: n) || |
388 | Cast<Import>(ptr: n) || |
389 | Cast<MediaRule>(ptr: n) || |
390 | Cast<CssMediaRule>(ptr: n) || |
391 | Cast<SupportsRule>(ptr: n); |
392 | } |
393 | } |
394 | |