Coverage Report

Created: 2025-10-10 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libsass/src/emitter.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 "emitter.hpp"
5
#include "util_string.hpp"
6
#include "util.hpp"
7
8
namespace Sass {
9
10
  Emitter::Emitter(struct Sass_Output_Options& opt)
11
637
  : wbuf(),
12
637
    opt(opt),
13
637
    indentation(0),
14
637
    scheduled_space(0),
15
637
    scheduled_linefeed(0),
16
637
    scheduled_delimiter(false),
17
637
    scheduled_crutch(0),
18
637
    scheduled_mapping(0),
19
637
    in_custom_property(false),
20
637
    in_comment(false),
21
637
    in_wrapped(false),
22
637
    in_media_block(false),
23
637
    in_declaration(false),
24
637
    in_space_array(false),
25
637
    in_comma_array(false)
26
637
  { }
27
28
  // return buffer as string
29
  sass::string Emitter::get_buffer(void)
30
614
  {
31
614
    return wbuf.buffer;
32
614
  }
33
34
  Sass_Output_Style Emitter::output_style(void) const
35
50
  {
36
50
    return opt.output_style;
37
50
  }
38
39
  // PROXY METHODS FOR SOURCE MAPS
40
41
  void Emitter::add_source_index(size_t idx)
42
17
  { wbuf.smap.source_index.push_back(idx); }
43
44
  sass::string Emitter::render_srcmap(Context &ctx)
45
0
  { return wbuf.smap.render_srcmap(ctx); }
46
47
  void Emitter::set_filename(const sass::string& str)
48
17
  { wbuf.smap.file = str; }
49
50
  void Emitter::schedule_mapping(const AST_Node* node)
51
1
  { scheduled_mapping = node; }
52
  void Emitter::add_open_mapping(const AST_Node* node)
53
619
  { wbuf.smap.add_open_mapping(node); }
54
  void Emitter::add_close_mapping(const AST_Node* node)
55
618
  { wbuf.smap.add_close_mapping(node); }
56
  SourceSpan Emitter::remap(const SourceSpan& pstate)
57
0
  { return wbuf.smap.remap(pstate); }
58
59
  // MAIN BUFFER MANIPULATION
60
61
  // add outstanding delimiter
62
  void Emitter::finalize(bool final)
63
12
  {
64
12
    scheduled_space = 0;
65
12
    if (output_style() == SASS_STYLE_COMPRESSED)
66
0
      if (final) scheduled_delimiter = false;
67
12
    if (scheduled_linefeed)
68
1
      scheduled_linefeed = 1;
69
12
    flush_schedules();
70
12
  }
71
72
  // flush scheduled space/linefeed
73
  void Emitter::flush_schedules(void)
74
1.25k
  {
75
    // check the schedule
76
1.25k
    if (scheduled_linefeed) {
77
2
      sass::string linefeeds = "";
78
79
4
      for (size_t i = 0; i < scheduled_linefeed; i++)
80
2
        linefeeds += opt.linefeed;
81
2
      scheduled_space = 0;
82
2
      scheduled_linefeed = 0;
83
2
      append_string(linefeeds);
84
85
1.25k
    } else if (scheduled_space) {
86
3
      sass::string spaces(scheduled_space, ' ');
87
3
      scheduled_space = 0;
88
3
      append_string(spaces);
89
3
    }
90
1.25k
    if (scheduled_delimiter) {
91
1
      scheduled_delimiter = false;
92
1
      append_string(";");
93
1
    }
94
1.25k
  }
95
96
  // prepend some text or token to the buffer
97
  void Emitter::prepend_output(const OutputBuffer& output)
98
6
  {
99
6
    wbuf.smap.prepend(output);
100
6
    wbuf.buffer = output.buffer + wbuf.buffer;
101
6
  }
102
103
  // prepend some text or token to the buffer
104
  void Emitter::prepend_string(const sass::string& text)
105
1
  {
106
    // do not adjust mappings for utf8 bom
107
    // seems they are not counted in any UA
108
1
    if (text.compare("\xEF\xBB\xBF") != 0) {
109
1
      wbuf.smap.prepend(Offset(text));
110
1
    }
111
1
    wbuf.buffer = text + wbuf.buffer;
112
1
  }
113
114
  char Emitter::last_char()
115
3
  {
116
3
    return wbuf.buffer.back();
117
3
  }
118
119
  // append a single char to the buffer
120
  void Emitter::append_char(const char chr)
121
0
  {
122
    // write space/lf
123
0
    flush_schedules();
124
    // add to buffer
125
0
    wbuf.buffer += chr;
126
    // account for data in source-maps
127
0
    wbuf.smap.append(Offset(chr));
128
0
  }
129
130
  // append some text or token to the buffer
131
  void Emitter::append_string(const sass::string& text)
132
628
  {
133
134
    // write space/lf
135
628
    flush_schedules();
136
137
628
    if (in_comment) {
138
0
      sass::string out = Util::normalize_newlines(text);
139
0
      if (output_style() == COMPACT) {
140
0
        out = comment_to_compact_string(out);
141
0
      }
142
0
      wbuf.smap.append(Offset(out));
143
0
      wbuf.buffer += std::move(out);
144
628
    } else {
145
      // add to buffer
146
628
      wbuf.buffer += text;
147
      // account for data in source-maps
148
628
      wbuf.smap.append(Offset(text));
149
628
    }
150
628
  }
151
152
  // append some white-space only text
153
  void Emitter::append_wspace(const sass::string& text)
154
0
  {
155
0
    if (text.empty()) return;
156
0
    if (peek_linefeed(text.c_str())) {
157
0
      scheduled_space = 0;
158
0
      append_mandatory_linefeed();
159
0
    }
160
0
  }
161
162
  // append some text or token to the buffer
163
  // this adds source-mappings for node start and end
164
  void Emitter::append_token(const sass::string& text, const AST_Node* node)
165
617
  {
166
617
    flush_schedules();
167
617
    add_open_mapping(node);
168
    // hotfix for browser issues
169
    // this is pretty ugly indeed
170
617
    if (scheduled_crutch) {
171
1
      add_open_mapping(scheduled_crutch);
172
1
      scheduled_crutch = 0;
173
1
    }
174
617
    append_string(text);
175
617
    add_close_mapping(node);
176
617
  }
177
178
  // HELPER METHODS
179
180
  void Emitter::append_indentation()
181
2
  {
182
2
    if (output_style() == COMPRESSED) return;
183
2
    if (output_style() == COMPACT) return;
184
2
    if (in_declaration && in_comma_array) return;
185
2
    if (scheduled_linefeed && indentation)
186
1
      scheduled_linefeed = 1;
187
2
    sass::string indent = "";
188
3
    for (size_t i = 0; i < indentation; i++)
189
1
      indent += opt.indent;
190
2
    append_string(indent);
191
2
  }
192
193
  void Emitter::append_delimiter()
194
1
  {
195
1
    scheduled_delimiter = true;
196
1
    if (output_style() == COMPACT) {
197
0
      if (indentation == 0) {
198
0
        append_mandatory_linefeed();
199
0
      } else {
200
0
        append_mandatory_space();
201
0
      }
202
1
    } else if (output_style() != COMPRESSED) {
203
1
      append_optional_linefeed();
204
1
    }
205
1
  }
206
207
  void Emitter::append_comma_separator()
208
0
  {
209
    // scheduled_space = 0;
210
0
    append_string(",");
211
0
    append_optional_space();
212
0
  }
213
214
  void Emitter::append_colon_separator()
215
1
  {
216
1
    scheduled_space = 0;
217
1
    append_string(":");
218
1
    if (!in_custom_property) append_optional_space();
219
1
  }
220
221
  void Emitter::append_mandatory_space()
222
3
  {
223
3
    scheduled_space = 1;
224
3
  }
225
226
  void Emitter::append_optional_space()
227
3
  {
228
3
    if ((output_style() != COMPRESSED) && buffer().size()) {
229
3
      unsigned char lst = buffer().at(buffer().length() - 1);
230
3
      if (!isspace(lst) || scheduled_delimiter) {
231
3
        if (last_char() != '(') {
232
3
          append_mandatory_space();
233
3
        }
234
3
      }
235
3
    }
236
3
  }
237
238
  void Emitter::append_special_linefeed()
239
0
  {
240
0
    if (output_style() == COMPACT) {
241
0
      append_mandatory_linefeed();
242
0
      for (size_t p = 0; p < indentation; p++)
243
0
        append_string(opt.indent);
244
0
    }
245
0
  }
246
247
  void Emitter::append_optional_linefeed()
248
3
  {
249
3
    if (in_declaration && in_comma_array) return;
250
3
    if (output_style() == COMPACT) {
251
0
      append_mandatory_space();
252
3
    } else {
253
3
      append_mandatory_linefeed();
254
3
    }
255
3
  }
256
257
  void Emitter::append_mandatory_linefeed()
258
3
  {
259
3
    if (output_style() != COMPRESSED) {
260
3
      scheduled_linefeed = 1;
261
3
      scheduled_space = 0;
262
      // flush_schedules();
263
3
    }
264
3
  }
265
266
  void Emitter::append_scope_opener(AST_Node* node)
267
1
  {
268
1
    scheduled_linefeed = 0;
269
1
    append_optional_space();
270
1
    flush_schedules();
271
1
    if (node) add_open_mapping(node);
272
1
    append_string("{");
273
1
    append_optional_linefeed();
274
    // append_optional_space();
275
1
    ++ indentation;
276
1
  }
277
  void Emitter::append_scope_closer(AST_Node* node)
278
1
  {
279
1
    -- indentation;
280
1
    scheduled_linefeed = 0;
281
1
    if (output_style() == COMPRESSED)
282
0
      scheduled_delimiter = false;
283
1
    if (output_style() == EXPANDED) {
284
0
      append_optional_linefeed();
285
0
      append_indentation();
286
1
    } else {
287
1
      append_optional_space();
288
1
    }
289
1
    append_string("}");
290
1
    if (node) add_close_mapping(node);
291
1
    append_optional_linefeed();
292
1
    if (indentation != 0) return;
293
1
    if (output_style() != COMPRESSED)
294
1
      scheduled_linefeed = 2;
295
1
  }
296
297
}