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