1 | // sass.hpp must go before all system headers to get the |
2 | // __EXTENSIONS__ fix on Solaris. |
3 | #include "sass.hpp" |
4 | |
5 | #include <cmath> |
6 | #include <string> |
7 | #include <iostream> |
8 | #include <iomanip> |
9 | #include <stdint.h> |
10 | #include <stdint.h> |
11 | |
12 | #include "ast.hpp" |
13 | #include "inspect.hpp" |
14 | #include "context.hpp" |
15 | #include "listize.hpp" |
16 | #include "color_maps.hpp" |
17 | #include "utf8/checked.h" |
18 | |
19 | namespace Sass { |
20 | |
21 | Inspect::Inspect(const Emitter& emi) |
22 | : Emitter(emi) |
23 | { } |
24 | Inspect::~Inspect() { } |
25 | |
26 | // statements |
27 | void Inspect::operator()(Block* block) |
28 | { |
29 | if (!block->is_root()) { |
30 | add_open_mapping(node: block); |
31 | append_scope_opener(); |
32 | } |
33 | if (output_style() == NESTED) indentation += block->tabs(); |
34 | for (size_t i = 0, L = block->length(); i < L; ++i) { |
35 | (*block)[i]->perform(op: this); |
36 | } |
37 | if (output_style() == NESTED) indentation -= block->tabs(); |
38 | if (!block->is_root()) { |
39 | append_scope_closer(); |
40 | add_close_mapping(node: block); |
41 | } |
42 | |
43 | } |
44 | |
45 | void Inspect::operator()(StyleRule* ruleset) |
46 | { |
47 | if (ruleset->selector()) { |
48 | ruleset->selector()->perform(op: this); |
49 | } |
50 | if (ruleset->block()) { |
51 | ruleset->block()->perform(op: this); |
52 | } |
53 | } |
54 | |
55 | void Inspect::operator()(Keyframe_Rule* rule) |
56 | { |
57 | if (rule->name()) rule->name()->perform(op: this); |
58 | if (rule->block()) rule->block()->perform(op: this); |
59 | } |
60 | |
61 | void Inspect::operator()(Bubble* bubble) |
62 | { |
63 | append_indentation(); |
64 | append_token(text: "::BUBBLE" , node: bubble); |
65 | append_scope_opener(); |
66 | bubble->node()->perform(op: this); |
67 | append_scope_closer(); |
68 | } |
69 | |
70 | void Inspect::operator()(MediaRule* rule) |
71 | { |
72 | append_indentation(); |
73 | append_token(text: "@media" , node: rule); |
74 | append_mandatory_space(); |
75 | if (rule->block()) { |
76 | rule->block()->perform(op: this); |
77 | } |
78 | } |
79 | |
80 | void Inspect::operator()(CssMediaRule* rule) |
81 | { |
82 | if (output_style() == NESTED) |
83 | indentation += rule->tabs(); |
84 | append_indentation(); |
85 | append_token(text: "@media" , node: rule); |
86 | append_mandatory_space(); |
87 | in_media_block = true; |
88 | bool joinIt = false; |
89 | for (auto query : rule->elements()) { |
90 | if (joinIt) { |
91 | append_comma_separator(); |
92 | append_optional_space(); |
93 | } |
94 | operator()(query); |
95 | joinIt = true; |
96 | } |
97 | if (rule->block()) { |
98 | rule->block()->perform(op: this); |
99 | } |
100 | in_media_block = false; |
101 | if (output_style() == NESTED) |
102 | indentation -= rule->tabs(); |
103 | } |
104 | |
105 | void Inspect::operator()(CssMediaQuery* query) |
106 | { |
107 | bool joinIt = false; |
108 | if (!query->modifier().empty()) { |
109 | append_string(text: query->modifier()); |
110 | append_mandatory_space(); |
111 | } |
112 | if (!query->type().empty()) { |
113 | append_string(text: query->type()); |
114 | joinIt = true; |
115 | } |
116 | for (auto feature : query->features()) { |
117 | if (joinIt) { |
118 | append_mandatory_space(); |
119 | append_string(text: "and" ); |
120 | append_mandatory_space(); |
121 | } |
122 | append_string(text: feature); |
123 | joinIt = true; |
124 | } |
125 | } |
126 | |
127 | void Inspect::operator()(SupportsRule* feature_block) |
128 | { |
129 | append_indentation(); |
130 | append_token(text: "@supports" , node: feature_block); |
131 | append_mandatory_space(); |
132 | feature_block->condition()->perform(op: this); |
133 | feature_block->block()->perform(op: this); |
134 | } |
135 | |
136 | void Inspect::operator()(AtRootRule* at_root_block) |
137 | { |
138 | append_indentation(); |
139 | append_token(text: "@at-root " , node: at_root_block); |
140 | append_mandatory_space(); |
141 | if(at_root_block->expression()) at_root_block->expression()->perform(op: this); |
142 | if(at_root_block->block()) at_root_block->block()->perform(op: this); |
143 | } |
144 | |
145 | void Inspect::operator()(AtRule* at_rule) |
146 | { |
147 | append_indentation(); |
148 | append_token(text: at_rule->keyword(), node: at_rule); |
149 | if (at_rule->selector()) { |
150 | append_mandatory_space(); |
151 | bool was_wrapped = in_wrapped; |
152 | in_wrapped = true; |
153 | at_rule->selector()->perform(op: this); |
154 | in_wrapped = was_wrapped; |
155 | } |
156 | if (at_rule->value()) { |
157 | append_mandatory_space(); |
158 | at_rule->value()->perform(op: this); |
159 | } |
160 | if (at_rule->block()) { |
161 | at_rule->block()->perform(op: this); |
162 | } |
163 | else { |
164 | append_delimiter(); |
165 | } |
166 | } |
167 | |
168 | void Inspect::operator()(Declaration* dec) |
169 | { |
170 | if (dec->value()->concrete_type() == Expression::NULL_VAL) return; |
171 | bool was_decl = in_declaration; |
172 | in_declaration = true; |
173 | LOCAL_FLAG(in_custom_property, dec->is_custom_property()); |
174 | |
175 | if (output_style() == NESTED) |
176 | indentation += dec->tabs(); |
177 | append_indentation(); |
178 | if (dec->property()) |
179 | dec->property()->perform(op: this); |
180 | append_colon_separator(); |
181 | |
182 | if (dec->value()->concrete_type() == Expression::SELECTOR) { |
183 | ExpressionObj ls = Listize::perform(node: dec->value()); |
184 | ls->perform(op: this); |
185 | } else { |
186 | dec->value()->perform(op: this); |
187 | } |
188 | |
189 | if (dec->is_important()) { |
190 | append_optional_space(); |
191 | append_string(text: "!important" ); |
192 | } |
193 | append_delimiter(); |
194 | if (output_style() == NESTED) |
195 | indentation -= dec->tabs(); |
196 | in_declaration = was_decl; |
197 | } |
198 | |
199 | void Inspect::operator()(Assignment* assn) |
200 | { |
201 | append_token(text: assn->variable(), node: assn); |
202 | append_colon_separator(); |
203 | assn->value()->perform(op: this); |
204 | if (assn->is_default()) { |
205 | append_optional_space(); |
206 | append_string(text: "!default" ); |
207 | } |
208 | append_delimiter(); |
209 | } |
210 | |
211 | void Inspect::operator()(Import* import) |
212 | { |
213 | if (!import->urls().empty()) { |
214 | append_token(text: "@import" , node: import); |
215 | append_mandatory_space(); |
216 | |
217 | import->urls().front()->perform(op: this); |
218 | if (import->urls().size() == 1) { |
219 | if (import->import_queries()) { |
220 | append_mandatory_space(); |
221 | import->import_queries()->perform(op: this); |
222 | } |
223 | } |
224 | append_delimiter(); |
225 | for (size_t i = 1, S = import->urls().size(); i < S; ++i) { |
226 | append_mandatory_linefeed(); |
227 | append_token(text: "@import" , node: import); |
228 | append_mandatory_space(); |
229 | |
230 | import->urls()[i]->perform(op: this); |
231 | if (import->urls().size() - 1 == i) { |
232 | if (import->import_queries()) { |
233 | append_mandatory_space(); |
234 | import->import_queries()->perform(op: this); |
235 | } |
236 | } |
237 | append_delimiter(); |
238 | } |
239 | } |
240 | } |
241 | |
242 | void Inspect::operator()(Import_Stub* import) |
243 | { |
244 | append_indentation(); |
245 | append_token(text: "@import" , node: import); |
246 | append_mandatory_space(); |
247 | append_string(text: import->imp_path()); |
248 | append_delimiter(); |
249 | } |
250 | |
251 | void Inspect::operator()(WarningRule* warning) |
252 | { |
253 | append_indentation(); |
254 | append_token(text: "@warn" , node: warning); |
255 | append_mandatory_space(); |
256 | warning->message()->perform(op: this); |
257 | append_delimiter(); |
258 | } |
259 | |
260 | void Inspect::operator()(ErrorRule* error) |
261 | { |
262 | append_indentation(); |
263 | append_token(text: "@error" , node: error); |
264 | append_mandatory_space(); |
265 | error->message()->perform(op: this); |
266 | append_delimiter(); |
267 | } |
268 | |
269 | void Inspect::operator()(DebugRule* debug) |
270 | { |
271 | append_indentation(); |
272 | append_token(text: "@debug" , node: debug); |
273 | append_mandatory_space(); |
274 | debug->value()->perform(op: this); |
275 | append_delimiter(); |
276 | } |
277 | |
278 | void Inspect::(Comment* ) |
279 | { |
280 | in_comment = true; |
281 | comment->text()->perform(op: this); |
282 | in_comment = false; |
283 | } |
284 | |
285 | void Inspect::operator()(If* cond) |
286 | { |
287 | append_indentation(); |
288 | append_token(text: "@if" , node: cond); |
289 | append_mandatory_space(); |
290 | cond->predicate()->perform(op: this); |
291 | cond->block()->perform(op: this); |
292 | if (cond->alternative()) { |
293 | append_optional_linefeed(); |
294 | append_indentation(); |
295 | append_string(text: "else" ); |
296 | cond->alternative()->perform(op: this); |
297 | } |
298 | } |
299 | |
300 | void Inspect::operator()(ForRule* loop) |
301 | { |
302 | append_indentation(); |
303 | append_token(text: "@for" , node: loop); |
304 | append_mandatory_space(); |
305 | append_string(text: loop->variable()); |
306 | append_string(text: " from " ); |
307 | loop->lower_bound()->perform(op: this); |
308 | append_string(text: loop->is_inclusive() ? " through " : " to " ); |
309 | loop->upper_bound()->perform(op: this); |
310 | loop->block()->perform(op: this); |
311 | } |
312 | |
313 | void Inspect::operator()(EachRule* loop) |
314 | { |
315 | append_indentation(); |
316 | append_token(text: "@each" , node: loop); |
317 | append_mandatory_space(); |
318 | append_string(text: loop->variables()[0]); |
319 | for (size_t i = 1, L = loop->variables().size(); i < L; ++i) { |
320 | append_comma_separator(); |
321 | append_string(text: loop->variables()[i]); |
322 | } |
323 | append_string(text: " in " ); |
324 | loop->list()->perform(op: this); |
325 | loop->block()->perform(op: this); |
326 | } |
327 | |
328 | void Inspect::operator()(WhileRule* loop) |
329 | { |
330 | append_indentation(); |
331 | append_token(text: "@while" , node: loop); |
332 | append_mandatory_space(); |
333 | loop->predicate()->perform(op: this); |
334 | loop->block()->perform(op: this); |
335 | } |
336 | |
337 | void Inspect::operator()(Return* ret) |
338 | { |
339 | append_indentation(); |
340 | append_token(text: "@return" , node: ret); |
341 | append_mandatory_space(); |
342 | ret->value()->perform(op: this); |
343 | append_delimiter(); |
344 | } |
345 | |
346 | void Inspect::operator()(ExtendRule* extend) |
347 | { |
348 | append_indentation(); |
349 | append_token(text: "@extend" , node: extend); |
350 | append_mandatory_space(); |
351 | extend->selector()->perform(op: this); |
352 | append_delimiter(); |
353 | } |
354 | |
355 | void Inspect::operator()(Definition* def) |
356 | { |
357 | append_indentation(); |
358 | if (def->type() == Definition::MIXIN) { |
359 | append_token(text: "@mixin" , node: def); |
360 | append_mandatory_space(); |
361 | } else { |
362 | append_token(text: "@function" , node: def); |
363 | append_mandatory_space(); |
364 | } |
365 | append_string(text: def->name()); |
366 | def->parameters()->perform(op: this); |
367 | def->block()->perform(op: this); |
368 | } |
369 | |
370 | void Inspect::operator()(Mixin_Call* call) |
371 | { |
372 | append_indentation(); |
373 | append_token(text: "@include" , node: call); |
374 | append_mandatory_space(); |
375 | append_string(text: call->name()); |
376 | if (call->arguments()) { |
377 | call->arguments()->perform(op: this); |
378 | } |
379 | if (call->block()) { |
380 | append_optional_space(); |
381 | call->block()->perform(op: this); |
382 | } |
383 | if (!call->block()) append_delimiter(); |
384 | } |
385 | |
386 | void Inspect::operator()(Content* content) |
387 | { |
388 | append_indentation(); |
389 | append_token(text: "@content" , node: content); |
390 | append_delimiter(); |
391 | } |
392 | |
393 | void Inspect::operator()(Map* map) |
394 | { |
395 | if (output_style() == TO_SASS && map->empty()) { |
396 | append_string(text: "()" ); |
397 | return; |
398 | } |
399 | if (map->empty()) return; |
400 | if (map->is_invisible()) return; |
401 | bool items_output = false; |
402 | append_string(text: "(" ); |
403 | for (auto key : map->keys()) { |
404 | if (items_output) append_comma_separator(); |
405 | key->perform(op: this); |
406 | append_colon_separator(); |
407 | LOCAL_FLAG(in_space_array, true); |
408 | LOCAL_FLAG(in_comma_array, true); |
409 | map->at(k: key)->perform(op: this); |
410 | items_output = true; |
411 | } |
412 | append_string(text: ")" ); |
413 | } |
414 | |
415 | sass::string Inspect::lbracket(List* list) { |
416 | return list->is_bracketed() ? "[" : "(" ; |
417 | } |
418 | |
419 | sass::string Inspect::rbracket(List* list) { |
420 | return list->is_bracketed() ? "]" : ")" ; |
421 | } |
422 | |
423 | void Inspect::operator()(List* list) |
424 | { |
425 | if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) { |
426 | append_string(text: lbracket(list)); |
427 | append_string(text: rbracket(list)); |
428 | return; |
429 | } |
430 | sass::string sep(list->separator() == SASS_SPACE ? " " : "," ); |
431 | if ((output_style() != COMPRESSED) && sep == "," ) sep += " " ; |
432 | else if (in_media_block && sep != " " ) sep += " " ; // verified |
433 | if (list->empty()) return; |
434 | bool items_output = false; |
435 | |
436 | bool was_space_array = in_space_array; |
437 | bool was_comma_array = in_comma_array; |
438 | // if the list is bracketed, always include the left bracket |
439 | if (list->is_bracketed()) { |
440 | append_string(text: lbracket(list)); |
441 | } |
442 | // probably ruby sass eqivalent of element_needs_parens |
443 | else if (output_style() == TO_SASS && |
444 | list->length() == 1 && |
445 | !list->from_selector() && |
446 | !Cast<List>(ptr: list->at(i: 0)) && |
447 | !Cast<SelectorList>(ptr: list->at(i: 0)) |
448 | ) { |
449 | append_string(text: lbracket(list)); |
450 | } |
451 | else if (!in_declaration && (list->separator() == SASS_HASH || |
452 | (list->separator() == SASS_SPACE && in_space_array) || |
453 | (list->separator() == SASS_COMMA && in_comma_array) |
454 | )) { |
455 | append_string(text: lbracket(list)); |
456 | } |
457 | |
458 | if (list->separator() == SASS_SPACE) in_space_array = true; |
459 | else if (list->separator() == SASS_COMMA) in_comma_array = true; |
460 | |
461 | for (size_t i = 0, L = list->size(); i < L; ++i) { |
462 | if (list->separator() == SASS_HASH) |
463 | { sep[0] = i % 2 ? ':' : ','; } |
464 | ExpressionObj list_item = list->at(i); |
465 | if (output_style() != TO_SASS) { |
466 | if (list_item->is_invisible()) { |
467 | // this fixes an issue with "" in a list |
468 | if (!Cast<String_Constant>(ptr: list_item)) { |
469 | continue; |
470 | } |
471 | } |
472 | } |
473 | if (items_output) { |
474 | append_string(text: sep); |
475 | } |
476 | if (items_output && sep != " " ) |
477 | append_optional_space(); |
478 | list_item->perform(op: this); |
479 | items_output = true; |
480 | } |
481 | |
482 | in_comma_array = was_comma_array; |
483 | in_space_array = was_space_array; |
484 | |
485 | // if the list is bracketed, always include the right bracket |
486 | if (list->is_bracketed()) { |
487 | if (list->separator() == SASS_COMMA && list->size() == 1) { |
488 | append_string(text: "," ); |
489 | } |
490 | append_string(text: rbracket(list)); |
491 | } |
492 | // probably ruby sass eqivalent of element_needs_parens |
493 | else if (output_style() == TO_SASS && |
494 | list->length() == 1 && |
495 | !list->from_selector() && |
496 | !Cast<List>(ptr: list->at(i: 0)) && |
497 | !Cast<SelectorList>(ptr: list->at(i: 0)) |
498 | ) { |
499 | append_string(text: "," ); |
500 | append_string(text: rbracket(list)); |
501 | } |
502 | else if (!in_declaration && (list->separator() == SASS_HASH || |
503 | (list->separator() == SASS_SPACE && in_space_array) || |
504 | (list->separator() == SASS_COMMA && in_comma_array) |
505 | )) { |
506 | append_string(text: rbracket(list)); |
507 | } |
508 | |
509 | } |
510 | |
511 | void Inspect::operator()(Binary_Expression* expr) |
512 | { |
513 | expr->left()->perform(op: this); |
514 | if ( in_media_block || |
515 | (output_style() == INSPECT) || ( |
516 | expr->op().ws_before |
517 | && (!expr->is_interpolant()) |
518 | && (expr->is_left_interpolant() || |
519 | expr->is_right_interpolant()) |
520 | |
521 | )) append_string(text: " " ); |
522 | switch (expr->optype()) { |
523 | case Sass_OP::AND: append_string(text: "&&" ); break; |
524 | case Sass_OP::OR: append_string(text: "||" ); break; |
525 | case Sass_OP::EQ: append_string(text: "==" ); break; |
526 | case Sass_OP::NEQ: append_string(text: "!=" ); break; |
527 | case Sass_OP::GT: append_string(text: ">" ); break; |
528 | case Sass_OP::GTE: append_string(text: ">=" ); break; |
529 | case Sass_OP::LT: append_string(text: "<" ); break; |
530 | case Sass_OP::LTE: append_string(text: "<=" ); break; |
531 | case Sass_OP::ADD: append_string(text: "+" ); break; |
532 | case Sass_OP::SUB: append_string(text: "-" ); break; |
533 | case Sass_OP::MUL: append_string(text: "*" ); break; |
534 | case Sass_OP::DIV: append_string(text: "/" ); break; |
535 | case Sass_OP::MOD: append_string(text: "%" ); break; |
536 | default: break; // shouldn't get here |
537 | } |
538 | if ( in_media_block || |
539 | (output_style() == INSPECT) || ( |
540 | expr->op().ws_after |
541 | && (!expr->is_interpolant()) |
542 | && (expr->is_left_interpolant() || |
543 | expr->is_right_interpolant()) |
544 | )) append_string(text: " " ); |
545 | expr->right()->perform(op: this); |
546 | } |
547 | |
548 | void Inspect::operator()(Unary_Expression* expr) |
549 | { |
550 | if (expr->optype() == Unary_Expression::PLUS) append_string(text: "+" ); |
551 | else if (expr->optype() == Unary_Expression::SLASH) append_string(text: "/" ); |
552 | else append_string(text: "-" ); |
553 | expr->operand()->perform(op: this); |
554 | } |
555 | |
556 | void Inspect::operator()(Function_Call* call) |
557 | { |
558 | append_token(text: call->name(), node: call); |
559 | call->arguments()->perform(op: this); |
560 | } |
561 | |
562 | void Inspect::operator()(Variable* var) |
563 | { |
564 | append_token(text: var->name(), node: var); |
565 | } |
566 | |
567 | void Inspect::operator()(Number* n) |
568 | { |
569 | |
570 | // reduce units |
571 | n->reduce(); |
572 | |
573 | sass::ostream ss; |
574 | ss.precision(prec: opt.precision); |
575 | ss << std::fixed << n->value(); |
576 | |
577 | sass::string res = ss.str(); |
578 | size_t s = res.length(); |
579 | |
580 | // delete trailing zeros |
581 | for(s = s - 1; s > 0; --s) |
582 | { |
583 | if(res[s] == '0') { |
584 | res.erase(pos: s, n: 1); |
585 | } |
586 | else break; |
587 | } |
588 | |
589 | // delete trailing decimal separator |
590 | if(res[s] == '.') res.erase(pos: s, n: 1); |
591 | |
592 | // some final cosmetics |
593 | if (res == "0.0" ) res = "0" ; |
594 | else if (res == "" ) res = "0" ; |
595 | else if (res == "-0" ) res = "0" ; |
596 | else if (res == "-0.0" ) res = "0" ; |
597 | else if (opt.output_style == COMPRESSED) |
598 | { |
599 | if (n->zero()) { |
600 | // check if handling negative nr |
601 | size_t off = res[0] == '-' ? 1 : 0; |
602 | // remove leading zero from floating point in compressed mode |
603 | if (res[off] == '0' && res[off+1] == '.') res.erase(pos: off, n: 1); |
604 | } |
605 | } |
606 | |
607 | // add unit now |
608 | res += n->unit(); |
609 | |
610 | if (opt.output_style == TO_CSS && !n->is_valid_css_unit()) { |
611 | // traces.push_back(Backtrace(nr->pstate())); |
612 | throw Exception::InvalidValue({}, *n); |
613 | } |
614 | |
615 | // output the final token |
616 | append_token(text: res, node: n); |
617 | } |
618 | |
619 | // helper function for serializing colors |
620 | template <size_t range> |
621 | static double cap_channel(double c) { |
622 | if (c > range) return range; |
623 | else if (c < 0) return 0; |
624 | else return c; |
625 | } |
626 | |
627 | void Inspect::operator()(Color_RGBA* c) |
628 | { |
629 | // output the final token |
630 | sass::ostream ss; |
631 | |
632 | // original color name |
633 | // maybe an unknown token |
634 | sass::string name = c->disp(); |
635 | |
636 | // resolved color |
637 | sass::string res_name = name; |
638 | |
639 | double r = Sass::round(val: cap_channel<0xff>(c: c->r()), precision: opt.precision); |
640 | double g = Sass::round(val: cap_channel<0xff>(c: c->g()), precision: opt.precision); |
641 | double b = Sass::round(val: cap_channel<0xff>(c: c->b()), precision: opt.precision); |
642 | double a = cap_channel<1> (c: c->a()); |
643 | |
644 | // get color from given name (if one was given at all) |
645 | if (name != "" && name_to_color(name)) { |
646 | const Color_RGBA* n = name_to_color(name); |
647 | r = Sass::round(val: cap_channel<0xff>(c: n->r()), precision: opt.precision); |
648 | g = Sass::round(val: cap_channel<0xff>(c: n->g()), precision: opt.precision); |
649 | b = Sass::round(val: cap_channel<0xff>(c: n->b()), precision: opt.precision); |
650 | a = cap_channel<1> (c: n->a()); |
651 | } |
652 | // otherwise get the possible resolved color name |
653 | else { |
654 | double numval = r * 0x10000 + g * 0x100 + b; |
655 | if (color_to_name(numval)) |
656 | res_name = color_to_name(numval); |
657 | } |
658 | |
659 | sass::ostream hexlet; |
660 | // dart sass compressed all colors in regular css always |
661 | // ruby sass and libsass does it only when not delayed |
662 | // since color math is going to be removed, this can go too |
663 | bool compressed = opt.output_style == COMPRESSED; |
664 | hexlet << '#' << std::setw(1) << std::setfill('0'); |
665 | // create a short color hexlet if there is any need for it |
666 | if (compressed && is_color_doublet(r, g, b) && a == 1) { |
667 | hexlet << std::hex << std::setw(1) << (static_cast<unsigned long>(r) >> 4); |
668 | hexlet << std::hex << std::setw(1) << (static_cast<unsigned long>(g) >> 4); |
669 | hexlet << std::hex << std::setw(1) << (static_cast<unsigned long>(b) >> 4); |
670 | } else { |
671 | hexlet << std::hex << std::setw(2) << static_cast<unsigned long>(r); |
672 | hexlet << std::hex << std::setw(2) << static_cast<unsigned long>(g); |
673 | hexlet << std::hex << std::setw(2) << static_cast<unsigned long>(b); |
674 | } |
675 | |
676 | if (compressed && !c->is_delayed()) name = "" ; |
677 | if (opt.output_style == INSPECT && a >= 1) { |
678 | append_token(text: hexlet.str(), node: c); |
679 | return; |
680 | } |
681 | |
682 | // retain the originally specified color definition if unchanged |
683 | if (name != "" ) { |
684 | ss << name; |
685 | } |
686 | else if (a >= 1) { |
687 | if (res_name != "" ) { |
688 | if (compressed && hexlet.str().size() < res_name.size()) { |
689 | ss << hexlet.str(); |
690 | } else { |
691 | ss << res_name; |
692 | } |
693 | } |
694 | else { |
695 | ss << hexlet.str(); |
696 | } |
697 | } |
698 | else { |
699 | ss << "rgba(" ; |
700 | ss << static_cast<unsigned long>(r) << "," ; |
701 | if (!compressed) ss << " " ; |
702 | ss << static_cast<unsigned long>(g) << "," ; |
703 | if (!compressed) ss << " " ; |
704 | ss << static_cast<unsigned long>(b) << "," ; |
705 | if (!compressed) ss << " " ; |
706 | ss << a << ')'; |
707 | } |
708 | |
709 | append_token(text: ss.str(), node: c); |
710 | |
711 | } |
712 | |
713 | void Inspect::operator()(Color_HSLA* c) |
714 | { |
715 | Color_RGBA_Obj rgba = c->toRGBA(); |
716 | operator()(c: rgba); |
717 | } |
718 | |
719 | void Inspect::operator()(Boolean* b) |
720 | { |
721 | // output the final token |
722 | append_token(text: b->value() ? "true" : "false" , node: b); |
723 | } |
724 | |
725 | void Inspect::operator()(String_Schema* ss) |
726 | { |
727 | // Evaluation should turn these into String_Constants, |
728 | // so this method is only for inspection purposes. |
729 | for (size_t i = 0, L = ss->length(); i < L; ++i) { |
730 | if ((*ss)[i]->is_interpolant()) append_string(text: "#{" ); |
731 | (*ss)[i]->perform(op: this); |
732 | if ((*ss)[i]->is_interpolant()) append_string(text: "}" ); |
733 | } |
734 | } |
735 | |
736 | void Inspect::operator()(String_Constant* s) |
737 | { |
738 | append_token(text: s->value(), node: s); |
739 | } |
740 | |
741 | void Inspect::operator()(String_Quoted* s) |
742 | { |
743 | if (const char q = s->quote_mark()) { |
744 | append_token(text: quote(s->value(), q), node: s); |
745 | } else { |
746 | append_token(text: s->value(), node: s); |
747 | } |
748 | } |
749 | |
750 | void Inspect::operator()(Custom_Error* e) |
751 | { |
752 | append_token(text: e->message(), node: e); |
753 | } |
754 | |
755 | void Inspect::operator()(Custom_Warning* w) |
756 | { |
757 | append_token(text: w->message(), node: w); |
758 | } |
759 | |
760 | void Inspect::operator()(SupportsOperation* so) |
761 | { |
762 | |
763 | if (so->needs_parens(cond: so->left())) append_string(text: "(" ); |
764 | so->left()->perform(op: this); |
765 | if (so->needs_parens(cond: so->left())) append_string(text: ")" ); |
766 | |
767 | if (so->operand() == SupportsOperation::AND) { |
768 | append_mandatory_space(); |
769 | append_token(text: "and" , node: so); |
770 | append_mandatory_space(); |
771 | } else if (so->operand() == SupportsOperation::OR) { |
772 | append_mandatory_space(); |
773 | append_token(text: "or" , node: so); |
774 | append_mandatory_space(); |
775 | } |
776 | |
777 | if (so->needs_parens(cond: so->right())) append_string(text: "(" ); |
778 | so->right()->perform(op: this); |
779 | if (so->needs_parens(cond: so->right())) append_string(text: ")" ); |
780 | } |
781 | |
782 | void Inspect::operator()(SupportsNegation* sn) |
783 | { |
784 | append_token(text: "not" , node: sn); |
785 | append_mandatory_space(); |
786 | if (sn->needs_parens(cond: sn->condition())) append_string(text: "(" ); |
787 | sn->condition()->perform(op: this); |
788 | if (sn->needs_parens(cond: sn->condition())) append_string(text: ")" ); |
789 | } |
790 | |
791 | void Inspect::operator()(SupportsDeclaration* sd) |
792 | { |
793 | append_string(text: "(" ); |
794 | sd->feature()->perform(op: this); |
795 | append_string(text: ": " ); |
796 | sd->value()->perform(op: this); |
797 | append_string(text: ")" ); |
798 | } |
799 | |
800 | void Inspect::operator()(Supports_Interpolation* sd) |
801 | { |
802 | sd->value()->perform(op: this); |
803 | } |
804 | |
805 | void Inspect::operator()(Media_Query* mq) |
806 | { |
807 | size_t i = 0; |
808 | if (mq->media_type()) { |
809 | if (mq->is_negated()) append_string(text: "not " ); |
810 | else if (mq->is_restricted()) append_string(text: "only " ); |
811 | mq->media_type()->perform(op: this); |
812 | } |
813 | else { |
814 | (*mq)[i++]->perform(op: this); |
815 | } |
816 | for (size_t L = mq->length(); i < L; ++i) { |
817 | append_string(text: " and " ); |
818 | (*mq)[i]->perform(op: this); |
819 | } |
820 | } |
821 | |
822 | void Inspect::operator()(Media_Query_Expression* mqe) |
823 | { |
824 | if (mqe->is_interpolated()) { |
825 | mqe->feature()->perform(op: this); |
826 | } |
827 | else { |
828 | append_string(text: "(" ); |
829 | mqe->feature()->perform(op: this); |
830 | if (mqe->value()) { |
831 | append_string(text: ": " ); // verified |
832 | mqe->value()->perform(op: this); |
833 | } |
834 | append_string(text: ")" ); |
835 | } |
836 | } |
837 | |
838 | void Inspect::operator()(At_Root_Query* ae) |
839 | { |
840 | if (ae->feature()) { |
841 | append_string(text: "(" ); |
842 | ae->feature()->perform(op: this); |
843 | if (ae->value()) { |
844 | append_colon_separator(); |
845 | ae->value()->perform(op: this); |
846 | } |
847 | append_string(text: ")" ); |
848 | } |
849 | } |
850 | |
851 | void Inspect::operator()(Function* f) |
852 | { |
853 | append_token(text: "get-function" , node: f); |
854 | append_string(text: "(" ); |
855 | append_string(text: quote(f->name())); |
856 | append_string(text: ")" ); |
857 | } |
858 | |
859 | void Inspect::operator()(Null* n) |
860 | { |
861 | // output the final token |
862 | append_token(text: "null" , node: n); |
863 | } |
864 | |
865 | // parameters and arguments |
866 | void Inspect::operator()(Parameter* p) |
867 | { |
868 | append_token(text: p->name(), node: p); |
869 | if (p->default_value()) { |
870 | append_colon_separator(); |
871 | p->default_value()->perform(op: this); |
872 | } |
873 | else if (p->is_rest_parameter()) { |
874 | append_string(text: "..." ); |
875 | } |
876 | } |
877 | |
878 | void Inspect::operator()(Parameters* p) |
879 | { |
880 | append_string(text: "(" ); |
881 | if (!p->empty()) { |
882 | (*p)[0]->perform(op: this); |
883 | for (size_t i = 1, L = p->length(); i < L; ++i) { |
884 | append_comma_separator(); |
885 | (*p)[i]->perform(op: this); |
886 | } |
887 | } |
888 | append_string(text: ")" ); |
889 | } |
890 | |
891 | void Inspect::operator()(Argument* a) |
892 | { |
893 | if (!a->name().empty()) { |
894 | append_token(text: a->name(), node: a); |
895 | append_colon_separator(); |
896 | } |
897 | if (!a->value()) return; |
898 | // Special case: argument nulls can be ignored |
899 | if (a->value()->concrete_type() == Expression::NULL_VAL) { |
900 | return; |
901 | } |
902 | if (a->value()->concrete_type() == Expression::STRING) { |
903 | String_Constant* s = Cast<String_Constant>(ptr: a->value()); |
904 | if (s) s->perform(op: this); |
905 | } else { |
906 | a->value()->perform(op: this); |
907 | } |
908 | if (a->is_rest_argument()) { |
909 | append_string(text: "..." ); |
910 | } |
911 | } |
912 | |
913 | void Inspect::operator()(Arguments* a) |
914 | { |
915 | append_string(text: "(" ); |
916 | if (!a->empty()) { |
917 | (*a)[0]->perform(op: this); |
918 | for (size_t i = 1, L = a->length(); i < L; ++i) { |
919 | append_string(text: ", " ); // verified |
920 | // Sass Bug? append_comma_separator(); |
921 | (*a)[i]->perform(op: this); |
922 | } |
923 | } |
924 | append_string(text: ")" ); |
925 | } |
926 | |
927 | void Inspect::operator()(Selector_Schema* s) |
928 | { |
929 | s->contents()->perform(op: this); |
930 | } |
931 | |
932 | void Inspect::operator()(Parent_Reference* p) |
933 | { |
934 | append_string(text: "&" ); |
935 | } |
936 | |
937 | void Inspect::operator()(PlaceholderSelector* s) |
938 | { |
939 | append_token(text: s->name(), node: s); |
940 | |
941 | } |
942 | |
943 | void Inspect::operator()(TypeSelector* s) |
944 | { |
945 | append_token(text: s->ns_name(), node: s); |
946 | } |
947 | |
948 | void Inspect::operator()(ClassSelector* s) |
949 | { |
950 | append_token(text: s->ns_name(), node: s); |
951 | } |
952 | |
953 | void Inspect::operator()(IDSelector* s) |
954 | { |
955 | append_token(text: s->ns_name(), node: s); |
956 | } |
957 | |
958 | void Inspect::operator()(AttributeSelector* s) |
959 | { |
960 | append_string(text: "[" ); |
961 | add_open_mapping(node: s); |
962 | append_token(text: s->ns_name(), node: s); |
963 | if (!s->matcher().empty()) { |
964 | append_string(text: s->matcher()); |
965 | if (s->value() && *s->value()) { |
966 | s->value()->perform(op: this); |
967 | } |
968 | } |
969 | add_close_mapping(node: s); |
970 | if (s->modifier() != 0) { |
971 | append_mandatory_space(); |
972 | append_char(chr: s->modifier()); |
973 | } |
974 | append_string(text: "]" ); |
975 | } |
976 | |
977 | void Inspect::operator()(PseudoSelector* s) |
978 | { |
979 | |
980 | if (s->name() != "" ) { |
981 | append_string(text: ":" ); |
982 | if (s->isSyntacticElement()) { |
983 | append_string(text: ":" ); |
984 | } |
985 | append_token(text: s->ns_name(), node: s); |
986 | if (s->selector() || s->argument()) { |
987 | bool was = in_wrapped; |
988 | in_wrapped = true; |
989 | append_string(text: "(" ); |
990 | if (s->argument()) { |
991 | s->argument()->perform(op: this); |
992 | } |
993 | if (s->selector() && s->argument()) { |
994 | append_mandatory_space(); |
995 | } |
996 | bool was_comma_array = in_comma_array; |
997 | in_comma_array = false; |
998 | if (s->selector()) { |
999 | s->selector()->perform(op: this); |
1000 | } |
1001 | in_comma_array = was_comma_array; |
1002 | append_string(text: ")" ); |
1003 | in_wrapped = was; |
1004 | } |
1005 | } |
1006 | } |
1007 | |
1008 | void Inspect::operator()(SelectorList* g) |
1009 | { |
1010 | |
1011 | if (g->empty()) { |
1012 | if (output_style() == TO_SASS) { |
1013 | append_token(text: "()" , node: g); |
1014 | } |
1015 | return; |
1016 | } |
1017 | |
1018 | |
1019 | bool was_comma_array = in_comma_array; |
1020 | // probably ruby sass eqivalent of element_needs_parens |
1021 | if (output_style() == TO_SASS && g->length() == 1 && |
1022 | (!Cast<List>(ptr: (*g)[0]) && |
1023 | !Cast<SelectorList>(ptr: (*g)[0]))) { |
1024 | append_string(text: "(" ); |
1025 | } |
1026 | else if (!in_declaration && in_comma_array) { |
1027 | append_string(text: "(" ); |
1028 | } |
1029 | |
1030 | if (in_declaration) in_comma_array = true; |
1031 | |
1032 | for (size_t i = 0, L = g->length(); i < L; ++i) { |
1033 | |
1034 | if (!in_wrapped && i == 0) append_indentation(); |
1035 | if ((*g)[i] == nullptr) continue; |
1036 | if (g->at(i)->length() == 0) continue; |
1037 | schedule_mapping(node: g->at(i)->last()); |
1038 | // add_open_mapping((*g)[i]->last()); |
1039 | (*g)[i]->perform(op: this); |
1040 | // add_close_mapping((*g)[i]->last()); |
1041 | if (i < L - 1) { |
1042 | scheduled_space = 0; |
1043 | append_comma_separator(); |
1044 | } |
1045 | } |
1046 | |
1047 | in_comma_array = was_comma_array; |
1048 | // probably ruby sass eqivalent of element_needs_parens |
1049 | if (output_style() == TO_SASS && g->length() == 1 && |
1050 | (!Cast<List>(ptr: (*g)[0]) && |
1051 | !Cast<SelectorList>(ptr: (*g)[0]))) { |
1052 | append_string(text: ",)" ); |
1053 | } |
1054 | else if (!in_declaration && in_comma_array) { |
1055 | append_string(text: ")" ); |
1056 | } |
1057 | |
1058 | } |
1059 | void Inspect::operator()(ComplexSelector* sel) |
1060 | { |
1061 | if (sel->hasPreLineFeed()) { |
1062 | append_optional_linefeed(); |
1063 | if (!in_wrapped && output_style() == NESTED) { |
1064 | append_indentation(); |
1065 | } |
1066 | } |
1067 | const SelectorComponent* prev = nullptr; |
1068 | for (auto& item : sel->elements()) { |
1069 | if (prev != nullptr) { |
1070 | if (item->getCombinator() || prev->getCombinator()) { |
1071 | append_optional_space(); |
1072 | } else { |
1073 | append_mandatory_space(); |
1074 | } |
1075 | } |
1076 | item->perform(op: this); |
1077 | prev = item.ptr(); |
1078 | } |
1079 | } |
1080 | |
1081 | void Inspect::operator()(SelectorComponent* sel) |
1082 | { |
1083 | // You should probably never call this method directly |
1084 | // But in case anyone does, we will do the upcasting |
1085 | if (auto comp = Cast<CompoundSelector>(ptr: sel)) operator()(comp); |
1086 | if (auto comb = Cast<SelectorCombinator>(ptr: sel)) operator()(comb); |
1087 | } |
1088 | |
1089 | void Inspect::operator()(CompoundSelector* sel) |
1090 | { |
1091 | if (sel->hasRealParent()) { |
1092 | append_string(text: "&" ); |
1093 | } |
1094 | sel->sortChildren(); |
1095 | for (auto& item : sel->elements()) { |
1096 | item->perform(op: this); |
1097 | } |
1098 | // Add the post line break (from ruby sass) |
1099 | // Dart sass uses another logic for newlines |
1100 | if (sel->hasPostLineBreak()) { |
1101 | if (output_style() != COMPACT) { |
1102 | append_optional_linefeed(); |
1103 | } |
1104 | } |
1105 | } |
1106 | |
1107 | void Inspect::operator()(SelectorCombinator* sel) |
1108 | { |
1109 | append_optional_space(); |
1110 | switch (sel->combinator()) { |
1111 | case SelectorCombinator::Combinator::CHILD: append_string(text: ">" ); break; |
1112 | case SelectorCombinator::Combinator::GENERAL: append_string(text: "~" ); break; |
1113 | case SelectorCombinator::Combinator::ADJACENT: append_string(text: "+" ); break; |
1114 | } |
1115 | append_optional_space(); |
1116 | // Add the post line break (from ruby sass) |
1117 | // Dart sass uses another logic for newlines |
1118 | if (sel->hasPostLineBreak()) { |
1119 | if (output_style() != COMPACT) { |
1120 | // append_optional_linefeed(); |
1121 | } |
1122 | } |
1123 | } |
1124 | |
1125 | } |
1126 | |