Coverage Report

Created: 2025-08-29 06:11

/src/libsass/src/output.cpp
Line
Count
Source (jump to first uncovered line)
1
// sass.hpp must go before all system headers to get the
2
// __EXTENSIONS__ fix on Solaris.
3
#include "sass.hpp"
4
5
#include "ast.hpp"
6
#include "output.hpp"
7
#include "util.hpp"
8
9
namespace Sass {
10
11
  Output::Output(Sass_Output_Options& opt)
12
16
  : Inspect(Emitter(opt)),
13
16
    charset(""),
14
16
    top_nodes(0)
15
16
  {}
16
17
16
  Output::~Output() { }
18
19
  void Output::fallback_impl(AST_Node* n)
20
0
  {
21
0
    return n->perform(this);
22
0
  }
23
24
  void Output::operator()(Number* n)
25
0
  {
26
    // check for a valid unit here
27
    // includes result for reporting
28
0
    if (!n->is_valid_css_unit()) {
29
      // should be handle in check_expression
30
0
      throw Exception::InvalidValue({}, *n);
31
0
    }
32
    // use values to_string facility
33
0
    sass::string res = n->to_string(opt);
34
    // output the final token
35
0
    append_token(res, n);
36
0
  }
37
38
  void Output::operator()(Import* imp)
39
0
  {
40
0
    top_nodes.push_back(imp);
41
0
  }
42
43
  void Output::operator()(Map* m)
44
0
  {
45
    // should be handle in check_expression
46
0
    throw Exception::InvalidValue({}, *m);
47
0
  }
48
49
  OutputBuffer Output::get_buffer(void)
50
4
  {
51
52
4
    Emitter emitter(opt);
53
4
    Inspect inspect(emitter);
54
55
4
    size_t size_nodes = top_nodes.size();
56
4
    for (size_t i = 0; i < size_nodes; i++) {
57
0
      top_nodes[i]->perform(&inspect);
58
0
      inspect.append_mandatory_linefeed();
59
0
    }
60
61
    // flush scheduled outputs
62
    // maybe omit semicolon if possible
63
4
    inspect.finalize(wbuf.buffer.size() == 0);
64
    // prepend buffer on top
65
4
    prepend_output(inspect.output());
66
    // make sure we end with a linefeed
67
4
    if (!ends_with(wbuf.buffer, opt.linefeed)) {
68
      // if the output is not completely empty
69
3
      if (!wbuf.buffer.empty()) append_string(opt.linefeed);
70
3
    }
71
72
    // search for unicode char
73
61
    for(const char& chr : wbuf.buffer) {
74
      // skip all ascii chars
75
      // static cast to unsigned to handle `char` being signed / unsigned
76
61
      if (static_cast<unsigned>(chr) < 128) continue;
77
      // declare the charset
78
1
      if (output_style() != COMPRESSED)
79
1
        charset = "@charset \"UTF-8\";"
80
1
                + sass::string(opt.linefeed);
81
0
      else charset = "\xEF\xBB\xBF";
82
      // abort search
83
1
      break;
84
61
    }
85
86
    // add charset as first line, before comments and imports
87
4
    if (!charset.empty()) prepend_string(charset);
88
89
4
    return wbuf;
90
91
4
  }
92
93
  void Output::operator()(Comment* c)
94
0
  {
95
    // if (indentation && txt == "/**/") return;
96
0
    bool important = c->is_important();
97
0
    if (output_style() != COMPRESSED || important) {
98
0
      if (buffer().size() == 0) {
99
0
        top_nodes.push_back(c);
100
0
      } else {
101
0
        in_comment = true;
102
0
        append_indentation();
103
0
        c->text()->perform(this);
104
0
        in_comment = false;
105
0
        if (indentation == 0) {
106
0
          append_mandatory_linefeed();
107
0
        } else {
108
0
          append_optional_linefeed();
109
0
        }
110
0
      }
111
0
    }
112
0
  }
113
114
  void Output::operator()(StyleRule* r)
115
1
  {
116
1
    Block_Obj b = r->block();
117
1
    SelectorListObj s = r->selector();
118
119
1
    if (!s || s->empty()) return;
120
121
    // Filter out rulesets that aren't printable (process its children though)
122
1
    if (!Util::isPrintable(r, output_style())) {
123
0
      for (size_t i = 0, L = b->length(); i < L; ++i) {
124
0
        const Statement_Obj& stm = b->get(i);
125
0
        if (Cast<ParentStatement>(stm)) {
126
0
          if (!Cast<Declaration>(stm)) {
127
0
            stm->perform(this);
128
0
          }
129
0
        }
130
0
      }
131
0
      return;
132
0
    }
133
134
1
    if (output_style() == NESTED) {
135
1
      indentation += r->tabs();
136
1
    }
137
1
    if (opt.source_comments) {
138
0
      sass::ostream ss;
139
0
      append_indentation();
140
0
      sass::string path(File::abs2rel(r->pstate().getPath()));
141
0
      ss << "/* line " << r->pstate().getLine() << ", " << path << " */";
142
0
      append_string(ss.str());
143
0
      append_optional_linefeed();
144
0
    }
145
1
    scheduled_crutch = s;
146
1
    if (s) s->perform(this);
147
1
    append_scope_opener(b);
148
2
    for (size_t i = 0, L = b->length(); i < L; ++i) {
149
1
      Statement_Obj stm = b->get(i);
150
1
      bool bPrintExpression = true;
151
      // Check print conditions
152
1
      if (Declaration* dec = Cast<Declaration>(stm)) {
153
1
        if (const String_Constant* valConst = Cast<String_Constant>(dec->value())) {
154
1
          const sass::string& val = valConst->value();
155
1
          if (const String_Quoted* qstr = Cast<const String_Quoted>(valConst)) {
156
0
            if (!qstr->quote_mark() && val.empty()) {
157
0
              bPrintExpression = false;
158
0
            }
159
0
          }
160
1
        }
161
0
        else if (List* list = Cast<List>(dec->value())) {
162
0
          bool all_invisible = true;
163
0
          for (size_t list_i = 0, list_L = list->length(); list_i < list_L; ++list_i) {
164
0
            Expression* item = list->get(list_i);
165
0
            if (!item->is_invisible()) all_invisible = false;
166
0
          }
167
0
          if (all_invisible && !list->is_bracketed()) bPrintExpression = false;
168
0
        }
169
1
      }
170
      // Print if OK
171
1
      if (bPrintExpression) {
172
1
        stm->perform(this);
173
1
      }
174
1
    }
175
1
    if (output_style() == NESTED) indentation -= r->tabs();
176
1
    append_scope_closer(b);
177
178
1
  }
179
  void Output::operator()(Keyframe_Rule* r)
180
0
  {
181
0
    Block_Obj b = r->block();
182
0
    Selector_Obj v = r->name();
183
184
0
    if (!v.isNull()) {
185
0
      v->perform(this);
186
0
    }
187
188
0
    if (!b) {
189
0
      append_colon_separator();
190
0
      return;
191
0
    }
192
193
0
    append_scope_opener();
194
0
    for (size_t i = 0, L = b->length(); i < L; ++i) {
195
0
      Statement_Obj stm = b->get(i);
196
0
      stm->perform(this);
197
0
      if (i < L - 1) append_special_linefeed();
198
0
    }
199
0
    append_scope_closer();
200
0
  }
201
202
  void Output::operator()(SupportsRule* f)
203
0
  {
204
0
    if (f->is_invisible()) return;
205
206
0
    SupportsConditionObj c = f->condition();
207
0
    Block_Obj b              = f->block();
208
209
    // Filter out feature blocks that aren't printable (process its children though)
210
0
    if (!Util::isPrintable(f, output_style())) {
211
0
      for (size_t i = 0, L = b->length(); i < L; ++i) {
212
0
        Statement_Obj stm = b->get(i);
213
0
        if (Cast<ParentStatement>(stm)) {
214
0
          stm->perform(this);
215
0
        }
216
0
      }
217
0
      return;
218
0
    }
219
220
0
    if (output_style() == NESTED) indentation += f->tabs();
221
0
    append_indentation();
222
0
    append_token("@supports", f);
223
0
    append_mandatory_space();
224
0
    c->perform(this);
225
0
    append_scope_opener();
226
227
0
    for (size_t i = 0, L = b->length(); i < L; ++i) {
228
0
      Statement_Obj stm = b->get(i);
229
0
      stm->perform(this);
230
0
      if (i < L - 1) append_special_linefeed();
231
0
    }
232
233
0
    if (output_style() == NESTED) indentation -= f->tabs();
234
235
0
    append_scope_closer();
236
237
0
  }
238
239
  void Output::operator()(CssMediaRule* rule)
240
0
  {
241
    // Avoid null pointer exception
242
0
    if (rule == nullptr) return;
243
    // Skip empty/invisible rule
244
0
    if (rule->isInvisible()) return;
245
    // Avoid null pointer exception
246
0
    if (rule->block() == nullptr) return;
247
    // Skip empty/invisible rule
248
0
    if (rule->block()->isInvisible()) return;
249
    // Skip if block is empty/invisible
250
0
    if (Util::isPrintable(rule, output_style())) {
251
      // Let inspect do its magic
252
0
      Inspect::operator()(rule);
253
0
    }
254
0
  }
255
256
  void Output::operator()(AtRule* a)
257
0
  {
258
0
    sass::string      kwd   = a->keyword();
259
0
    Selector_Obj   s     = a->selector();
260
0
    ExpressionObj v     = a->value();
261
0
    Block_Obj      b     = a->block();
262
263
0
    append_indentation();
264
0
    append_token(kwd, a);
265
0
    if (s) {
266
0
      append_mandatory_space();
267
0
      in_wrapped = true;
268
0
      s->perform(this);
269
0
      in_wrapped = false;
270
0
    }
271
0
    if (v) {
272
0
      append_mandatory_space();
273
      // ruby sass bug? should use options?
274
0
      append_token(v->to_string(/* opt */), v);
275
0
    }
276
0
    if (!b) {
277
0
      append_delimiter();
278
0
      return;
279
0
    }
280
281
0
    if (b->is_invisible() || b->length() == 0) {
282
0
      append_optional_space();
283
0
      return append_string("{}");
284
0
    }
285
286
0
    append_scope_opener();
287
288
0
    bool format = kwd != "@font-face";;
289
290
0
    for (size_t i = 0, L = b->length(); i < L; ++i) {
291
0
      Statement_Obj stm = b->get(i);
292
0
      if (stm) stm->perform(this);
293
0
      if (i < L - 1 && format) append_special_linefeed();
294
0
    }
295
296
0
    append_scope_closer();
297
0
  }
298
299
  void Output::operator()(String_Quoted* s)
300
0
  {
301
0
    if (s->quote_mark()) {
302
0
      append_token(quote(s->value(), s->quote_mark()), s);
303
0
    } else if (!in_comment) {
304
0
      append_token(string_to_output(s->value()), s);
305
0
    } else {
306
0
      append_token(s->value(), s);
307
0
    }
308
0
  }
309
310
  void Output::operator()(String_Constant* s)
311
2
  {
312
2
    sass::string value(s->value());
313
2
    if (!in_comment && !in_custom_property) {
314
2
      append_token(string_to_output(value), s);
315
2
    } else {
316
0
      append_token(value, s);
317
0
    }
318
2
  }
319
320
}