Coverage Report

Created: 2026-03-15 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}