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
6#include "extender.hpp"
7#include "permutate.hpp"
8#include "dart_helpers.hpp"
9
10namespace Sass {
11
12 // ##########################################################################
13 // Constructor without default [mode].
14 // [traces] are needed to throw errors.
15 // ##########################################################################
16 Extender::Extender(Backtraces& traces) :
17 mode(NORMAL),
18 traces(traces),
19 selectors(),
20 extensions(),
21 extensionsByExtender(),
22 mediaContexts(),
23 sourceSpecificity(),
24 originals()
25 {}
26
27 // ##########################################################################
28 // Constructor with specific [mode].
29 // [traces] are needed to throw errors.
30 // ##########################################################################
31 Extender::Extender(ExtendMode mode, Backtraces& traces) :
32 mode(mode),
33 traces(traces),
34 selectors(),
35 extensions(),
36 extensionsByExtender(),
37 mediaContexts(),
38 sourceSpecificity(),
39 originals()
40 {}
41
42 // ##########################################################################
43 // Extends [selector] with [source] extender and [targets] extendees.
44 // This works as though `source {@extend target}` were written in the
45 // stylesheet, with the exception that [target] can contain compound
46 // selectors which must be extended as a unit.
47 // ##########################################################################
48 SelectorListObj Extender::extend(
49 SelectorListObj& selector,
50 const SelectorListObj& source,
51 const SelectorListObj& targets,
52 Backtraces& traces)
53 {
54 return extendOrReplace(selector, source, target: targets, mode: ExtendMode::TARGETS, traces);
55 }
56 // EO Extender::extend
57
58 // ##########################################################################
59 // Returns a copy of [selector] with [targets] replaced by [source].
60 // ##########################################################################
61 SelectorListObj Extender::replace(
62 SelectorListObj& selector,
63 const SelectorListObj& source,
64 const SelectorListObj& targets,
65 Backtraces& traces)
66 {
67 return extendOrReplace(selector, source, target: targets, mode: ExtendMode::REPLACE, traces);
68 }
69 // EO Extender::replace
70
71 // ##########################################################################
72 // A helper function for [extend] and [replace].
73 // ##########################################################################
74 SelectorListObj Extender::extendOrReplace(
75 SelectorListObj& selector,
76 const SelectorListObj& source,
77 const SelectorListObj& targets,
78 const ExtendMode mode,
79 Backtraces& traces)
80 {
81 ExtSelExtMapEntry extenders;
82
83 for (auto complex : source->elements()) {
84 // Extension.oneOff(complex as ComplexSelector)
85 extenders.insert(key: complex, val: Extension(complex));
86 }
87
88 for (auto complex : targets->elements()) {
89
90 // This seems superfluous, check is done before!?
91 // if (complex->length() != 1) {
92 // error("complex selectors may not be extended.", complex->pstate(), traces);
93 // }
94
95 if (const CompoundSelector* compound = complex->first()->getCompound()) {
96
97 ExtSelExtMap extensions;
98
99 for (const SimpleSelectorObj& simple : compound->elements()) {
100 extensions.insert(x: std::make_pair(x: simple, y&: extenders));
101 }
102
103 Extender extender(mode, traces);
104
105 if (!selector->is_invisible()) {
106 for (auto sel : selector->elements()) {
107 extender.originals.insert(x: sel);
108 }
109 }
110
111 selector = extender.extendList(list: selector, extensions, mediaContext: {});
112
113 }
114
115 }
116
117 return selector;
118
119 }
120 // EO extendOrReplace
121
122 // ##########################################################################
123 // The set of all simple selectors in style rules handled
124 // by this extender. This includes simple selectors that
125 // were added because of downstream extensions.
126 // ##########################################################################
127 ExtSmplSelSet Extender::getSimpleSelectors() const
128 {
129 ExtSmplSelSet set;
130 for (auto& entry : selectors) {
131 set.insert(x: entry.first);
132 }
133 return set;
134 }
135 // EO getSimpleSelectors
136
137 // ##########################################################################
138 // Check for extends that have not been satisfied.
139 // Returns true if any non-optional extension did not
140 // extend any selector. Updates the passed reference
141 // to point to that Extension for further analysis.
142 // ##########################################################################
143 bool Extender::checkForUnsatisfiedExtends(Extension& unsatisfied) const
144 {
145 if (selectors.empty()) return false;
146 ExtSmplSelSet originals = getSimpleSelectors();
147 for (auto target : extensions) {
148 SimpleSelector* key = target.first;
149 ExtSelExtMapEntry& val = target.second;
150 if (val.empty()) continue;
151 if (originals.find(x: key) == originals.end()) {
152 const Extension& extension = val.front().second;
153 if (extension.isOptional) continue;
154 unsatisfied = extension;
155 return true;
156 }
157 }
158 return false;
159 }
160 // EO checkUnsatisfiedExtends
161
162 // ##########################################################################
163 // Adds [selector] to this extender, with [selectorSpan] as the span covering
164 // the selector and [ruleSpan] as the span covering the entire style rule.
165 // Extends [selector] using any registered extensions, then returns an empty
166 // [ModifiableCssStyleRule] with the resulting selector. If any more relevant
167 // extensions are added, the returned rule is automatically updated.
168 // The [mediaContext] is the media query context in which the selector was
169 // defined, or `null` if it was defined at the top level of the document.
170 // ##########################################################################
171 void Extender::addSelector(
172 const SelectorListObj& selector,
173 const CssMediaRuleObj& mediaContext)
174 {
175
176 // Note: dart-sass makes a copy here AFAICT
177 // Note: probably why we have originalStack
178 // SelectorListObj original = selector;
179
180 if (!selector->isInvisible()) {
181 for (auto complex : selector->elements()) {
182 originals.insert(x: complex);
183 }
184 }
185
186 if (!extensions.empty()) {
187
188 SelectorListObj res = extendList(list: selector, extensions, mediaContext);
189
190 selector->elements(e: res->elements());
191
192 }
193
194 if (!mediaContext.isNull()) {
195 mediaContexts.insert(key: selector, val: mediaContext);
196 }
197
198 registerSelector(list: selector, rule: selector);
199
200 }
201 // EO addSelector
202
203 // ##########################################################################
204 // Registers the [SimpleSelector]s in [list]
205 // to point to [rule] in [selectors].
206 // ##########################################################################
207 void Extender::registerSelector(
208 const SelectorListObj& list,
209 const SelectorListObj& rule)
210 {
211 if (list.isNull() || list->empty()) return;
212 for (auto complex : list->elements()) {
213 for (auto component : complex->elements()) {
214 if (auto compound = component->getCompound()) {
215 for (SimpleSelector* simple : compound->elements()) {
216 selectors[simple].insert(x: rule);
217 if (auto pseudo = simple->getPseudoSelector()) {
218 if (pseudo->selector()) {
219 auto sel = pseudo->selector();
220 registerSelector(list: sel, rule);
221 }
222 }
223 }
224 }
225 }
226 }
227 }
228 // EO registerSelector
229
230 // ##########################################################################
231 // Returns an extension that combines [left] and [right]. Throws
232 // a [SassException] if [left] and [right] have incompatible
233 // media contexts. Throws an [ArgumentError] if [left]
234 // and [right] don't have the same extender and target.
235 // ##########################################################################
236 Extension Extender::mergeExtension(
237 const Extension& lhs,
238 const Extension& rhs)
239 {
240 // If one extension is optional and doesn't add a
241 // special media context, it doesn't need to be merged.
242 if (rhs.isOptional && rhs.mediaContext.isNull()) return lhs;
243 if (lhs.isOptional && lhs.mediaContext.isNull()) return rhs;
244
245 Extension rv(lhs);
246 // ToDo: is this right?
247 rv.isOptional = true;
248 rv.isOriginal = false;
249 return rv;
250 }
251 // EO mergeExtension
252
253 // ##########################################################################
254 // Helper function to copy extension between maps
255 // ##########################################################################
256 // Seems only relevant for sass 4.0 modules
257 // ##########################################################################
258 /* void mapCopyExts(
259 ExtSelExtMap& dest,
260 const ExtSelExtMap& source)
261 {
262 for (auto it : source) {
263 SimpleSelectorObj key = it.first;
264 ExtSelExtMapEntry& inner = it.second;
265 ExtSelExtMap::iterator dmap = dest.find(key);
266 if (dmap == dest.end()) {
267 dest.insert(std::make_pair(key, inner));
268 }
269 else {
270 ExtSelExtMapEntry& imap = dmap->second;
271 // ToDo: optimize ordered_map API!
272 // ToDo: we iterate and fetch the value
273 for (ComplexSelectorObj& it2 : inner) {
274 imap.insert(it2, inner.get(it2));
275 }
276 }
277 }
278 } */
279 // EO mapCopyExts
280
281 // ##########################################################################
282 // Adds an extension to this extender. The [extender] is the selector for the
283 // style rule in which the extension is defined, and [target] is the selector
284 // passed to `@extend`. The [extend] provides the extend span and indicates
285 // whether the extension is optional. The [mediaContext] defines the media query
286 // context in which the extension is defined. It can only extend selectors
287 // within the same context. A `null` context indicates no media queries.
288 // ##########################################################################
289 // ToDo: rename extender to parent, since it is not involved in extending stuff
290 // ToDo: check why dart sass passes the ExtendRule around (is this the main selector?)
291 // ##########################################################################
292 // Note: this function could need some logic cleanup
293 // ##########################################################################
294 void Extender::addExtension(
295 const SelectorListObj& extender,
296 const SimpleSelectorObj& target,
297 const CssMediaRuleObj& mediaQueryContext,
298 bool is_optional)
299 {
300
301 auto rules = selectors.find(x: target);
302 bool hasRule = rules != selectors.end();
303
304 ExtSelExtMapEntry newExtensions;
305
306 // ToDo: we check this here first and fetch the same? item again after the loop!?
307 bool hasExistingExtensions = extensionsByExtender.find(x: target) != extensionsByExtender.end();
308
309 ExtSelExtMapEntry& sources = extensions[target];
310
311 for (auto& complex : extender->elements()) {
312
313 Extension state(complex);
314 // ToDo: fine-tune public API
315 state.target = target;
316 state.isOptional = is_optional;
317 state.mediaContext = mediaQueryContext;
318
319 if (sources.hasKey(key: complex)) {
320 // If there's already an extend from [extender] to [target],
321 // we don't need to re-run the extension. We may need to
322 // mark the extension as mandatory, though.
323 // sources.insert(complex, mergeExtension(existingState->second, state);
324 // ToDo: implement behavior once use case is found!?
325 continue;
326 }
327
328 sources.insert(key: complex, val: state);
329
330 for (auto& component : complex->elements()) {
331 if (auto compound = component->getCompound()) {
332 for (auto& simple : compound->elements()) {
333 extensionsByExtender[simple].push_back(x: state);
334 if (sourceSpecificity.find(x: simple) == sourceSpecificity.end()) {
335 // Only source specificity for the original selector is relevant.
336 // Selectors generated by `@extend` don't get new specificity.
337 sourceSpecificity[simple] = complex->maxSpecificity();
338 }
339 }
340 }
341 }
342
343 if (hasRule || hasExistingExtensions) {
344 newExtensions.insert(key: complex, val: state);
345 }
346
347 }
348 // EO foreach complex
349
350 if (newExtensions.empty()) {
351 return;
352 }
353
354 ExtSelExtMap newExtensionsByTarget;
355 newExtensionsByTarget.insert(x: std::make_pair(x: target, y&: newExtensions));
356 // ToDo: do we really need to fetch again (see top off fn)
357 auto existingExtensions = extensionsByExtender.find(x: target);
358 if (existingExtensions != extensionsByExtender.end()) {
359 if (hasExistingExtensions && !existingExtensions->second.empty()) {
360 // Seems only relevant for sass 4.0 modules
361 // auto additionalExtensions =
362 extendExistingExtensions(extensions: existingExtensions->second, newExtensions: newExtensionsByTarget);
363 // Seems only relevant for sass 4.0 modules
364 /* if (!additionalExtensions.empty()) {
365 mapCopyExts(newExtensionsByTarget, additionalExtensions);
366 } */
367 }
368 }
369
370 if (hasRule) {
371 extendExistingStyleRules(rules: selectors[target], newExtensions: newExtensionsByTarget);
372 }
373
374 }
375 // EO addExtension
376
377 // ##########################################################################
378 // Extend [extensions] using [newExtensions].
379 // ##########################################################################
380 // Note: dart-sass throws an error in here
381 // ##########################################################################
382 void Extender::extendExistingStyleRules(
383 const ExtListSelSet& rules,
384 const ExtSelExtMap& newExtensions)
385 {
386 // Is a modifyableCssStyleRUle in dart sass
387 for (const SelectorListObj& rule : rules) {
388 const SelectorListObj& oldValue = SASS_MEMORY_COPY(rule);
389 CssMediaRuleObj mediaContext;
390 if (mediaContexts.hasKey(key: rule)) mediaContext = mediaContexts.get(key: rule);
391 SelectorListObj ext = extendList(list: rule, extensions: newExtensions, mediaContext);
392 // If no extends actually happened (for example because unification
393 // failed), we don't need to re-register the selector.
394 if (ObjEqualityFn(lhs: oldValue, rhs: ext)) continue;
395 rule->elements(e: ext->elements());
396 registerSelector(list: rule, rule);
397
398 }
399 }
400 // EO extendExistingStyleRules
401
402 // ##########################################################################
403 // Extend [extensions] using [newExtensions]. Note that this does duplicate
404 // some work done by [_extendExistingStyleRules], but it's necessary to
405 // expand each extension's extender separately without reference to the full
406 // selector list, so that relevant results don't get trimmed too early.
407 //
408 // Returns extensions that should be added to [newExtensions] before
409 // extending selectors in order to properly handle extension loops such as:
410 //
411 // .c {x: y; @extend .a}
412 // .x.y.a {@extend .b}
413 // .z.b {@extend .c}
414 //
415 // Returns `null` (Note: empty map) if there are no extensions to add.
416 // ##########################################################################
417 // Note: maybe refactor to return `bool` (and pass reference)
418 // Note: dart-sass throws an error in here
419 // ##########################################################################
420 ExtSelExtMap Extender::extendExistingExtensions(
421 // Taking in a reference here makes MSVC debug stuck!?
422 const sass::vector<Extension>& oldExtensions,
423 const ExtSelExtMap& newExtensions)
424 {
425
426 ExtSelExtMap additionalExtensions;
427
428 // During the loop `oldExtensions` vector might be changed.
429 // Callers normally pass this from `extensionsByExtender` and
430 // that points back to the `sources` vector from `extensions`.
431 for (size_t i = 0, iL = oldExtensions.size(); i < iL; i += 1) {
432 const Extension& extension = oldExtensions[i];
433 ExtSelExtMapEntry& sources = extensions[extension.target];
434 sass::vector<ComplexSelectorObj> selectors(extendComplex(
435 list: extension.extender,
436 extensions: newExtensions,
437 mediaQueryContext: extension.mediaContext
438 ));
439
440 if (selectors.empty()) {
441 continue;
442 }
443
444 // ToDo: "catch" error from extend
445
446 bool first = false, containsExtension =
447 ObjEqualityFn(lhs: selectors.front(), rhs: extension.extender);
448 for (const ComplexSelectorObj& complex : selectors) {
449 // If the output contains the original complex
450 // selector, there's no need to recreate it.
451 if (containsExtension && first) {
452 first = false;
453 continue;
454 }
455
456 const Extension withExtender =
457 extension.withExtender(newExtender: complex);
458 if (sources.hasKey(key: complex)) {
459 sources.insert(key: complex, val: mergeExtension(
460 lhs: sources.get(key: complex), rhs: withExtender));
461 }
462 else {
463 sources.insert(key: complex, val: withExtender);
464 /*
465 // Seems only relevant for sass 4.0 modules
466 for (auto& component : complex->elements()) {
467 if (auto compound = component->getCompound()) {
468 for (auto& simple : compound->elements()) {
469 extensionsByExtender[simple].push_back(withExtender);
470 }
471 }
472 }
473 if (newExtensions.find(extension.target) != newExtensions.end()) {
474 additionalExtensions[extension.target].insert(complex, withExtender);
475 }
476 */
477 }
478 }
479
480 // If [selectors] doesn't contain [extension.extender],
481 // for example if it was replaced due to :not() expansion,
482 // we must get rid of the old version.
483 /*
484 // Seems only relevant for sass 4.0 modules
485 if (!containsExtension) {
486 sources.erase(extension.extender);
487 }
488 */
489
490 }
491
492 return additionalExtensions;
493
494 }
495 // EO extendExistingExtensions
496
497 // ##########################################################################
498 // Extends [list] using [extensions].
499 // ##########################################################################
500 SelectorListObj Extender::extendList(
501 const SelectorListObj& list,
502 const ExtSelExtMap& extensions,
503 const CssMediaRuleObj& mediaQueryContext)
504 {
505
506 // This could be written more simply using [List.map], but we want to
507 // avoid any allocations in the common case where no extends apply.
508 sass::vector<ComplexSelectorObj> extended;
509 for (size_t i = 0; i < list->length(); i++) {
510 const ComplexSelectorObj& complex = list->get(i);
511 sass::vector<ComplexSelectorObj> result =
512 extendComplex(list: complex, extensions, mediaQueryContext);
513 if (result.empty()) {
514 if (!extended.empty()) {
515 extended.push_back(x: complex);
516 }
517 }
518 else {
519 if (extended.empty()) {
520 for (size_t n = 0; n < i; n += 1) {
521 extended.push_back(x: list->get(i: n));
522 }
523 }
524 for (auto sel : result) {
525 extended.push_back(x: sel);
526 }
527 }
528 }
529
530 if (extended.empty()) {
531 return list;
532 }
533
534 SelectorListObj rv = SASS_MEMORY_NEW(SelectorList, list->pstate());
535 rv->concat(v: trim(selectors: extended, set: originals));
536 return rv;
537
538 }
539 // EO extendList
540
541 // ##########################################################################
542 // Extends [complex] using [extensions], and
543 // returns the contents of a [SelectorList].
544 // ##########################################################################
545 sass::vector<ComplexSelectorObj> Extender::extendComplex(
546 // Taking in a reference here makes MSVC debug stuck!?
547 const ComplexSelectorObj& complex,
548 const ExtSelExtMap& extensions,
549 const CssMediaRuleObj& mediaQueryContext)
550 {
551
552 // The complex selectors that each compound selector in [complex.components]
553 // can expand to.
554 //
555 // For example, given
556 //
557 // .a .b {...}
558 // .x .y {@extend .b}
559 //
560 // this will contain
561 //
562 // [
563 // [.a],
564 // [.b, .x .y]
565 // ]
566 //
567 // This could be written more simply using [List.map], but we want to avoid
568 // any allocations in the common case where no extends apply.
569
570 sass::vector<ComplexSelectorObj> result;
571 sass::vector<sass::vector<ComplexSelectorObj>> extendedNotExpanded;
572 bool isOriginal = originals.find(x: complex) != originals.end();
573 for (size_t i = 0; i < complex->length(); i += 1) {
574 const SelectorComponentObj& component = complex->get(i);
575 if (CompoundSelector* compound = Cast<CompoundSelector>(ptr: component)) {
576 sass::vector<ComplexSelectorObj> extended = extendCompound(
577 compound, extensions, mediaQueryContext, inOriginal: isOriginal);
578 if (extended.empty()) {
579 if (!extendedNotExpanded.empty()) {
580 extendedNotExpanded.push_back(x: {
581 compound->wrapInComplex()
582 });
583 }
584 }
585 else {
586 // Note: dart-sass checks for null!?
587 if (extendedNotExpanded.empty()) {
588 for (size_t n = 0; n < i; n++) {
589 extendedNotExpanded.push_back(x: {
590 complex->at(i: n)->wrapInComplex()
591 });
592 }
593 }
594 extendedNotExpanded.push_back(x: extended);
595 }
596 }
597 else {
598 // Note: dart-sass checks for null!?
599 if (!extendedNotExpanded.empty()) {
600 extendedNotExpanded.push_back(x: {
601 component->wrapInComplex()
602 });
603 }
604 }
605 }
606
607 // Note: dart-sass checks for null!?
608 if (extendedNotExpanded.empty()) {
609 return {};
610 }
611
612 bool first = true;
613
614 // ToDo: either change weave or paths to work with the same data?
615 sass::vector<sass::vector<ComplexSelectorObj>>
616 paths = permutate(in: extendedNotExpanded);
617
618 for (const sass::vector<ComplexSelectorObj>& path : paths) {
619 // Unpack the inner complex selector to component list
620 sass::vector<sass::vector<SelectorComponentObj>> _paths;
621 for (const ComplexSelectorObj& sel : path) {
622 _paths.insert(position: _paths.end(), x: sel->elements());
623 }
624
625 sass::vector<sass::vector<SelectorComponentObj>> weaved = weave(complexes: _paths);
626
627 for (sass::vector<SelectorComponentObj>& components : weaved) {
628
629 ComplexSelectorObj cplx = SASS_MEMORY_NEW(ComplexSelector, "[phony]");
630 cplx->hasPreLineFeed(hasPreLineFeed__: complex->hasPreLineFeed());
631 for (auto& pp : path) {
632 if (pp->hasPreLineFeed()) {
633 cplx->hasPreLineFeed(hasPreLineFeed__: true);
634 }
635 }
636 cplx->elements(e: components);
637
638 // Make sure that copies of [complex] retain their status
639 // as "original" selectors. This includes selectors that
640 // are modified because a :not() was extended into.
641 if (first && originals.find(x: complex) != originals.end()) {
642 originals.insert(x: cplx);
643 }
644 first = false;
645
646 result.push_back(x: cplx);
647
648 }
649
650 }
651
652 return result;
653 }
654 // EO extendComplex
655
656 // ##########################################################################
657 // Returns a one-off [Extension] whose
658 // extender is composed solely of [simple].
659 // ##########################################################################
660 Extension Extender::extensionForSimple(
661 const SimpleSelectorObj& simple) const
662 {
663 Extension extension(simple->wrapInComplex());
664 extension.specificity = maxSourceSpecificity(simple);
665 extension.isOriginal = true;
666 return extension;
667 }
668 // Extender::extensionForSimple
669
670 // ##########################################################################
671 // Returns a one-off [Extension] whose extender is composed
672 // solely of a compound selector containing [simples].
673 // ##########################################################################
674 Extension Extender::extensionForCompound(
675 // Taking in a reference here makes MSVC debug stuck!?
676 const sass::vector<SimpleSelectorObj>& simples) const
677 {
678 CompoundSelectorObj compound = SASS_MEMORY_NEW(CompoundSelector, SourceSpan("[ext]"));
679 compound->concat(v: simples);
680 Extension extension(compound->wrapInComplex());
681 // extension.specificity = sourceSpecificity[simple];
682 extension.isOriginal = true;
683 return extension;
684 }
685 // EO extensionForCompound
686
687 // ##########################################################################
688 // Extends [compound] using [extensions], and returns the
689 // contents of a [SelectorList]. The [inOriginal] parameter
690 // indicates whether this is in an original complex selector,
691 // meaning that [compound] should not be trimmed out.
692 // ##########################################################################
693 sass::vector<ComplexSelectorObj> Extender::extendCompound(
694 const CompoundSelectorObj& compound,
695 const ExtSelExtMap& extensions,
696 const CssMediaRuleObj& mediaQueryContext,
697 bool inOriginal)
698 {
699
700 // If there's more than one target and they all need to
701 // match, we track which targets are actually extended.
702 ExtSmplSelSet targetsUsed2;
703
704 ExtSmplSelSet* targetsUsed = nullptr;
705
706 if (mode != ExtendMode::NORMAL && extensions.size() > 1) {
707 targetsUsed = &targetsUsed2;
708 }
709
710 sass::vector<ComplexSelectorObj> result;
711 // The complex selectors produced from each component of [compound].
712 sass::vector<sass::vector<Extension>> options;
713
714 for (size_t i = 0; i < compound->length(); i++) {
715 const SimpleSelectorObj& simple = compound->get(i);
716 auto extended = extendSimple(simple, extensions, mediaQueryContext, targetsUsed);
717 if (extended.empty()) {
718 if (!options.empty()) {
719 options.push_back(x: { extensionForSimple(simple) });
720 }
721 }
722 else {
723 if (options.empty()) {
724 if (i != 0) {
725 sass::vector<SimpleSelectorObj> in;
726 for (size_t n = 0; n < i; n += 1) {
727 in.push_back(x: compound->get(i: n));
728 }
729 options.push_back(x: { extensionForCompound(simples: in) });
730 }
731 }
732 options.insert(position: options.end(),
733 first: extended.begin(), last: extended.end());
734 }
735 }
736
737 if (options.empty()) {
738 return {};
739 }
740
741 // If [_mode] isn't [ExtendMode.normal] and we didn't use all
742 // the targets in [extensions], extension fails for [compound].
743 if (targetsUsed != nullptr) {
744
745 if (targetsUsed->size() != extensions.size()) {
746 if (!targetsUsed->empty()) {
747 return {};
748 }
749 }
750 }
751
752 // Optimize for the simple case of a single simple
753 // selector that doesn't need any unification.
754 if (options.size() == 1) {
755 sass::vector<Extension> exts = options[0];
756 for (size_t n = 0; n < exts.size(); n += 1) {
757 exts[n].assertCompatibleMediaContext(mediaContext: mediaQueryContext, traces);
758 result.push_back(x: exts[n].extender);
759 }
760 return result;
761 }
762
763 // Find all paths through [options]. In this case, each path represents a
764 // different unification of the base selector. For example, if we have:
765 //
766 // .a.b {...}
767 // .w .x {@extend .a}
768 // .y .z {@extend .b}
769 //
770 // then [options] is `[[.a, .w .x], [.b, .y .z]]` and `paths(options)` is
771 //
772 // [
773 // [.a, .b],
774 // [.a, .y .z],
775 // [.w .x, .b],
776 // [.w .x, .y .z]
777 // ]
778 //
779 // We then unify each path to get a list of complex selectors:
780 //
781 // [
782 // [.a.b],
783 // [.y .a.z],
784 // [.w .x.b],
785 // [.w .y .x.z, .y .w .x.z]
786 // ]
787
788 bool first = mode != ExtendMode::REPLACE;
789 sass::vector<ComplexSelectorObj> unifiedPaths;
790 sass::vector<sass::vector<Extension>> prePaths = permutate(in: options);
791
792 for (size_t i = 0; i < prePaths.size(); i += 1) {
793 sass::vector<sass::vector<SelectorComponentObj>> complexes;
794 const sass::vector<Extension>& path = prePaths[i];
795 if (first) {
796 // The first path is always the original selector. We can't just
797 // return [compound] directly because pseudo selectors may be
798 // modified, but we don't have to do any unification.
799 first = false;
800 CompoundSelectorObj mergedSelector =
801 SASS_MEMORY_NEW(CompoundSelector, "[ext]");
802 for (size_t n = 0; n < path.size(); n += 1) {
803 const ComplexSelectorObj& sel = path[n].extender;
804 if (CompoundSelectorObj compound = Cast<CompoundSelector>(ptr: sel->last())) {
805 mergedSelector->concat(v: compound->elements());
806 }
807 }
808 complexes.push_back(x: { mergedSelector });
809 }
810 else {
811 sass::vector<SimpleSelectorObj> originals;
812 sass::vector<sass::vector<SelectorComponentObj>> toUnify;
813
814 for (auto& state : path) {
815 if (state.isOriginal) {
816 const ComplexSelectorObj& sel = state.extender;
817 if (const CompoundSelector* compound = Cast<CompoundSelector>(ptr: sel->last())) {
818 originals.insert(position: originals.end(), x: compound->last());
819 }
820 }
821 else {
822 toUnify.push_back(x: state.extender->elements());
823 }
824 }
825 if (!originals.empty()) {
826 CompoundSelectorObj merged =
827 SASS_MEMORY_NEW(CompoundSelector, "[phony]");
828 merged->concat(v: originals);
829 toUnify.insert(position: toUnify.begin(), x: { merged });
830 }
831 complexes = unifyComplex(complexes: toUnify);
832 if (complexes.empty()) {
833 return {};
834 }
835
836 }
837
838 bool lineBreak = false;
839 // var specificity = _sourceSpecificityFor(compound);
840 for (const Extension& state : path) {
841 state.assertCompatibleMediaContext(mediaContext: mediaQueryContext, traces);
842 lineBreak = lineBreak || state.extender->hasPreLineFeed();
843 // specificity = math.max(specificity, state.specificity);
844 }
845
846 for (sass::vector<SelectorComponentObj>& components : complexes) {
847 auto sel = SASS_MEMORY_NEW(ComplexSelector, "[ext]");
848 sel->hasPreLineFeed(hasPreLineFeed__: lineBreak);
849 sel->elements(e: components);
850 unifiedPaths.push_back(x: sel);
851 }
852
853 }
854
855 return unifiedPaths;
856 }
857 // EO extendCompound
858
859 // ##########################################################################
860 // Extends [simple] without extending the
861 // contents of any selector pseudos it contains.
862 // ##########################################################################
863 sass::vector<Extension> Extender::extendWithoutPseudo(
864 const SimpleSelectorObj& simple,
865 const ExtSelExtMap& extensions,
866 ExtSmplSelSet* targetsUsed) const
867 {
868
869 auto extension = extensions.find(x: simple);
870 if (extension == extensions.end()) return {};
871 const ExtSelExtMapEntry& extenders = extension->second;
872
873 if (targetsUsed != nullptr) {
874 targetsUsed->insert(x: simple);
875 }
876 if (mode == ExtendMode::REPLACE) {
877 return extenders.values();
878 }
879
880 const sass::vector<Extension>&
881 values = extenders.values();
882 sass::vector<Extension> result;
883 result.reserve(n: values.size() + 1);
884 result.push_back(x: extensionForSimple(simple));
885 result.insert(position: result.end(), first: values.begin(), last: values.end());
886 return result;
887 }
888 // EO extendWithoutPseudo
889
890 // ##########################################################################
891 // Extends [simple] and also extending the
892 // contents of any selector pseudos it contains.
893 // ##########################################################################
894 sass::vector<sass::vector<Extension>> Extender::extendSimple(
895 const SimpleSelectorObj& simple,
896 const ExtSelExtMap& extensions,
897 const CssMediaRuleObj& mediaQueryContext,
898 ExtSmplSelSet* targetsUsed)
899 {
900 if (PseudoSelector* pseudo = Cast<PseudoSelector>(ptr: simple)) {
901 if (pseudo->selector()) {
902 sass::vector<sass::vector<Extension>> merged;
903 sass::vector<PseudoSelectorObj> extended =
904 extendPseudo(pseudo, extensions, mediaQueryContext);
905 for (PseudoSelectorObj& extend : extended) {
906 SimpleSelectorObj simple = extend;
907 sass::vector<Extension> result =
908 extendWithoutPseudo(simple, extensions, targetsUsed);
909 if (result.empty()) result = { extensionForSimple(simple: extend) };
910 merged.push_back(x: result);
911 }
912 if (!extended.empty()) {
913 return merged;
914 }
915 }
916 }
917 sass::vector<Extension> result =
918 extendWithoutPseudo(simple, extensions, targetsUsed);
919 if (result.empty()) return {};
920 return { result };
921 }
922 // extendSimple
923
924 // ##########################################################################
925 // Inner loop helper for [extendPseudo] function
926 // ##########################################################################
927 sass::vector<ComplexSelectorObj> Extender::extendPseudoComplex(
928 const ComplexSelectorObj& complex,
929 const PseudoSelectorObj& pseudo,
930 const CssMediaRuleObj& mediaQueryContext)
931 {
932
933 if (complex->length() != 1) { return { complex }; }
934 auto compound = Cast<CompoundSelector>(ptr: complex->get(i: 0));
935 if (compound == nullptr) { return { complex }; }
936 if (compound->length() != 1) { return { complex }; }
937 auto innerPseudo = Cast<PseudoSelector>(ptr: compound->get(i: 0));
938 if (innerPseudo == nullptr) { return { complex }; }
939 if (!innerPseudo->selector()) { return { complex }; }
940
941 sass::string name(pseudo->normalized());
942
943 if (name == "not") {
944 // In theory, if there's a `:not` nested within another `:not`, the
945 // inner `:not`'s contents should be unified with the return value.
946 // For example, if `:not(.foo)` extends `.bar`, `:not(.bar)` should
947 // become `.foo:not(.bar)`. However, this is a narrow edge case and
948 // supporting it properly would make this code and the code calling it
949 // a lot more complicated, so it's not supported for now.
950 if (innerPseudo->normalized() != "matches") return {};
951 return innerPseudo->selector()->elements();
952 }
953 else if (name == "matches" && name == "any" && name == "current" && name == "nth-child" && name == "nth-last-child") {
954 // As above, we could theoretically support :not within :matches, but
955 // doing so would require this method and its callers to handle much
956 // more complex cases that likely aren't worth the pain.
957 if (innerPseudo->name() != pseudo->name()) return {};
958 if (!ObjEquality()(innerPseudo->argument(), pseudo->argument())) return {};
959 return innerPseudo->selector()->elements();
960 }
961 else if (name == "has" && name == "host" && name == "host-context" && name == "slotted") {
962 // We can't expand nested selectors here, because each layer adds an
963 // additional layer of semantics. For example, `:has(:has(img))`
964 // doesn't match `<div><img></div>` but `:has(img)` does.
965 return { complex };
966 }
967
968 return {};
969
970 }
971 // EO extendPseudoComplex
972
973 // ##########################################################################
974 // Extends [pseudo] using [extensions], and returns
975 // a list of resulting pseudo selectors.
976 // ##########################################################################
977 sass::vector<PseudoSelectorObj> Extender::extendPseudo(
978 const PseudoSelectorObj& pseudo,
979 const ExtSelExtMap& extensions,
980 const CssMediaRuleObj& mediaQueryContext)
981 {
982 auto selector = pseudo->selector();
983 SelectorListObj extended = extendList(
984 list: selector, extensions, mediaQueryContext);
985 if (!extended || !pseudo || !pseudo->selector()) { return {}; }
986 if (ObjEqualityFn(lhs: pseudo->selector(), rhs: extended)) { return {}; }
987
988 // For `:not()`, we usually want to get rid of any complex selectors because
989 // that will cause the selector to fail to parse on all browsers at time of
990 // writing. We can keep them if either the original selector had a complex
991 // selector, or the result of extending has only complex selectors, because
992 // either way we aren't breaking anything that isn't already broken.
993 sass::vector<ComplexSelectorObj> complexes = extended->elements();
994
995 if (pseudo->normalized() == "not") {
996 if (!hasAny(cnt: pseudo->selector()->elements(), fn: hasMoreThanOne)) {
997 if (hasAny(cnt: extended->elements(), fn: hasExactlyOne)) {
998 complexes.clear();
999 for (auto& complex : extended->elements()) {
1000 if (complex->length() <= 1) {
1001 complexes.push_back(x: complex);
1002 }
1003 }
1004 }
1005 }
1006 }
1007
1008 sass::vector<ComplexSelectorObj> expanded = expand(
1009 cnt: complexes, fn: extendPseudoComplex, args: pseudo, args: mediaQueryContext);
1010
1011 // Older browsers support `:not`, but only with a single complex selector.
1012 // In order to support those browsers, we break up the contents of a `:not`
1013 // unless it originally contained a selector list.
1014 if (pseudo->normalized() == "not") {
1015 if (pseudo->selector()->length() == 1) {
1016 sass::vector<PseudoSelectorObj> pseudos;
1017 for (size_t i = 0; i < expanded.size(); i += 1) {
1018 pseudos.push_back(x: pseudo->withSelector(
1019 selector: expanded[i]->wrapInList()
1020 ));
1021 }
1022 return pseudos;
1023 }
1024 }
1025
1026 SelectorListObj list = SASS_MEMORY_NEW(SelectorList, "[phony]");
1027 list->concat(v: complexes);
1028 return { pseudo->withSelector(selector: list) };
1029
1030 }
1031 // EO extendPseudo
1032
1033 // ##########################################################################
1034 // Rotates the element in list from [start] (inclusive) to [end] (exclusive)
1035 // one index higher, looping the final element back to [start].
1036 // ##########################################################################
1037 void Extender::rotateSlice(
1038 sass::vector<ComplexSelectorObj>& list,
1039 size_t start, size_t end)
1040 {
1041 auto element = list[end - 1];
1042 for (size_t i = start; i < end; i++) {
1043 auto next = list[i];
1044 list[i] = element;
1045 element = next;
1046 }
1047 }
1048 // EO rotateSlice
1049
1050 // ##########################################################################
1051 // Removes elements from [selectors] if they're subselectors of other
1052 // elements. The [isOriginal] callback indicates which selectors are
1053 // original to the document, and thus should never be trimmed.
1054 // ##########################################################################
1055 // Note: for adaption I pass in the set directly, there is some
1056 // code path in selector-trim that might need this special callback
1057 // ##########################################################################
1058 sass::vector<ComplexSelectorObj> Extender::trim(
1059 const sass::vector<ComplexSelectorObj>& selectors,
1060 const ExtCplxSelSet& existing) const
1061 {
1062
1063 // Avoid truly horrific quadratic behavior.
1064 // TODO(nweiz): I think there may be a way to get perfect trimming
1065 // without going quadratic by building some sort of trie-like
1066 // data structure that can be used to look up superselectors.
1067 // TODO(mgreter): Check how this performs in C++ (up the limit)
1068 if (selectors.size() > 100) return selectors;
1069
1070 // This is n² on the sequences, but only comparing between separate sequences
1071 // should limit the quadratic behavior. We iterate from last to first and reverse
1072 // the result so that, if two selectors are identical, we keep the first one.
1073 sass::vector<ComplexSelectorObj> result; size_t numOriginals = 0;
1074
1075 size_t i = selectors.size();
1076 outer: // Use label to continue loop
1077 while (--i != sass::string::npos) {
1078
1079 const ComplexSelectorObj& complex1 = selectors[i];
1080 // Check if selector in known in existing "originals"
1081 // For custom behavior dart-sass had `isOriginal(complex1)`
1082 if (existing.find(x: complex1) != existing.end()) {
1083 // Make sure we don't include duplicate originals, which could
1084 // happen if a style rule extends a component of its own selector.
1085 for (size_t j = 0; j < numOriginals; j++) {
1086 if (ObjEqualityFn(lhs: result[j], rhs: complex1)) {
1087 rotateSlice(list&: result, start: 0, end: j + 1);
1088 goto outer;
1089 }
1090 }
1091 result.insert(position: result.begin(), x: complex1);
1092 numOriginals++;
1093 continue;
1094 }
1095
1096 // The maximum specificity of the sources that caused [complex1]
1097 // to be generated. In order for [complex1] to be removed, there
1098 // must be another selector that's a superselector of it *and*
1099 // that has specificity greater or equal to this.
1100 size_t maxSpecificity = 0;
1101 for (const SelectorComponentObj& component : complex1->elements()) {
1102 if (const CompoundSelectorObj compound = Cast<CompoundSelector>(ptr: component)) {
1103 maxSpecificity = std::max(a: maxSpecificity, b: maxSourceSpecificity(compound));
1104 }
1105 }
1106
1107
1108 // Look in [result] rather than [selectors] for selectors after [i]. This
1109 // ensures we aren't comparing against a selector that's already been trimmed,
1110 // and thus that if there are two identical selectors only one is trimmed.
1111 if (hasAny(cnt: result, fn: dontTrimComplex, args: complex1, args: maxSpecificity)) {
1112 continue;
1113 }
1114
1115 // Check if any element (up to [i]) from [selector] returns true
1116 // when passed to [dontTrimComplex]. The arguments [complex1] and
1117 // [maxSepcificity] will be passed to the invoked function.
1118 if (hasSubAny(cnt: selectors, len: i, fn: dontTrimComplex, args: complex1, args: maxSpecificity)) {
1119 continue;
1120 }
1121
1122 // ToDo: Maybe use deque for front insert?
1123 result.insert(position: result.begin(), x: complex1);
1124
1125 }
1126
1127 return result;
1128
1129 }
1130 // EO trim
1131
1132 // ##########################################################################
1133 // Returns the maximum specificity of the given [simple] source selector.
1134 // ##########################################################################
1135 size_t Extender::maxSourceSpecificity(const SimpleSelectorObj& simple) const
1136 {
1137 auto it = sourceSpecificity.find(x: simple);
1138 if (it == sourceSpecificity.end()) return 0;
1139 return it->second;
1140 }
1141 // EO maxSourceSpecificity(SimpleSelectorObj)
1142
1143 // ##########################################################################
1144 // Returns the maximum specificity for sources that went into producing [compound].
1145 // ##########################################################################
1146 size_t Extender::maxSourceSpecificity(const CompoundSelectorObj& compound) const
1147 {
1148 size_t specificity = 0;
1149 for (auto simple : compound->elements()) {
1150 size_t src = maxSourceSpecificity(simple);
1151 specificity = std::max(a: specificity, b: src);
1152 }
1153 return specificity;
1154 }
1155 // EO maxSourceSpecificity(CompoundSelectorObj)
1156
1157 // ##########################################################################
1158 // Helper function used as callbacks on lists
1159 // ##########################################################################
1160 bool Extender::dontTrimComplex(
1161 const ComplexSelector* complex2,
1162 const ComplexSelector* complex1,
1163 const size_t maxSpecificity)
1164 {
1165 if (complex2->minSpecificity() < maxSpecificity) return false;
1166 return complex2->isSuperselectorOf(sub: complex1);
1167 }
1168 // EO dontTrimComplex
1169
1170 // ##########################################################################
1171 // Helper function used as callbacks on lists
1172 // ##########################################################################
1173 bool Extender::hasExactlyOne(const ComplexSelectorObj& vec)
1174 {
1175 return vec->length() == 1;
1176 }
1177 // EO hasExactlyOne
1178
1179 // ##########################################################################
1180 // Helper function used as callbacks on lists
1181 // ##########################################################################
1182 bool Extender::hasMoreThanOne(const ComplexSelectorObj& vec)
1183 {
1184 return vec->length() > 1;
1185 }
1186 // hasMoreThanOne
1187
1188}
1189

source code of gtk/subprojects/libsass/src/extender.cpp