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 
10 namespace Sass {
11 
12   // ##########################################################################
13   // Constructor without default [mode].
14   // [traces] are needed to throw errors.
15   // ##########################################################################
Extender(Backtraces & traces)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   // ##########################################################################
Extender(ExtendMode mode,Backtraces & traces)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   // ##########################################################################
extend(SelectorListObj & selector,const SelectorListObj & source,const SelectorListObj & targets,Backtraces & traces)48   SelectorListObj Extender::extend(
49     SelectorListObj& selector,
50     const SelectorListObj& source,
51     const SelectorListObj& targets,
52     Backtraces& traces)
53   {
54     return extendOrReplace(selector, source, targets, ExtendMode::TARGETS, traces);
55   }
56   // EO Extender::extend
57 
58   // ##########################################################################
59   // Returns a copy of [selector] with [targets] replaced by [source].
60   // ##########################################################################
replace(SelectorListObj & selector,const SelectorListObj & source,const SelectorListObj & targets,Backtraces & traces)61   SelectorListObj Extender::replace(
62     SelectorListObj& selector,
63     const SelectorListObj& source,
64     const SelectorListObj& targets,
65     Backtraces& traces)
66   {
67     return extendOrReplace(selector, source, targets, ExtendMode::REPLACE, traces);
68   }
69   // EO Extender::replace
70 
71   // ##########################################################################
72   // A helper function for [extend] and [replace].
73   // ##########################################################################
extendOrReplace(SelectorListObj & selector,const SelectorListObj & source,const SelectorListObj & targets,const ExtendMode mode,Backtraces & traces)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(complex, 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(std::make_pair(simple, extenders));
101         }
102 
103         Extender extender(mode, traces);
104 
105         if (!selector->is_invisible()) {
106           for (auto sel : selector->elements()) {
107             extender.originals.insert(sel);
108           }
109         }
110 
111         selector = extender.extendList(selector, extensions, {});
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   // ##########################################################################
getSimpleSelectors() const127   ExtSmplSelSet Extender::getSimpleSelectors() const
128   {
129     ExtSmplSelSet set;
130     for (auto& entry : selectors) {
131       set.insert(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   // ##########################################################################
checkForUnsatisfiedExtends(Extension & unsatisfied) const143   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(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   // ##########################################################################
addSelector(const SelectorListObj & selector,const CssMediaRuleObj & mediaContext)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(complex);
183       }
184     }
185 
186     if (!extensions.empty()) {
187 
188       SelectorListObj res = extendList(selector, extensions, mediaContext);
189 
190       selector->elements(res->elements());
191 
192     }
193 
194     if (!mediaContext.isNull()) {
195       mediaContexts.insert(selector, mediaContext);
196     }
197 
198     registerSelector(selector, selector);
199 
200   }
201   // EO addSelector
202 
203   // ##########################################################################
204   // Registers the [SimpleSelector]s in [list]
205   // to point to [rule] in [selectors].
206   // ##########################################################################
registerSelector(const SelectorListObj & list,const SelectorListObj & rule)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(rule);
217             if (auto pseudo = simple->getPseudoSelector()) {
218               if (pseudo->selector()) {
219                 auto sel = pseudo->selector();
220                 registerSelector(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   // ##########################################################################
mergeExtension(const Extension & lhs,const Extension & rhs)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   // ##########################################################################
addExtension(const SelectorListObj & extender,const SimpleSelectorObj & target,const CssMediaRuleObj & mediaQueryContext,bool is_optional)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(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(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(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(complex, 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(state);
334             if (sourceSpecificity.find(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(complex, state);
345       }
346 
347     }
348     // EO foreach complex
349 
350     if (newExtensions.empty()) {
351       return;
352     }
353 
354     ExtSelExtMap newExtensionsByTarget;
355     newExtensionsByTarget.insert(std::make_pair(target, newExtensions));
356     // ToDo: do we really need to fetch again (see top off fn)
357     auto existingExtensions = extensionsByExtender.find(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(existingExtensions->second, 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(selectors[target], 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   // ##########################################################################
extendExistingStyleRules(const ExtListSelSet & rules,const ExtSelExtMap & newExtensions)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(rule)) mediaContext = mediaContexts.get(rule);
391       SelectorListObj ext = extendList(rule, 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(oldValue, ext)) continue;
395       rule->elements(ext->elements());
396       registerSelector(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   // ##########################################################################
extendExistingExtensions(const sass::vector<Extension> & oldExtensions,const ExtSelExtMap & newExtensions)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         extension.extender,
436         newExtensions,
437         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(selectors.front(), 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(complex);
458         if (sources.hasKey(complex)) {
459           sources.insert(complex, mergeExtension(
460             sources.get(complex), withExtender));
461         }
462         else {
463           sources.insert(complex, 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   // ##########################################################################
extendList(const SelectorListObj & list,const ExtSelExtMap & extensions,const CssMediaRuleObj & mediaQueryContext)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(complex, extensions, mediaQueryContext);
513       if (result.empty()) {
514         if (!extended.empty()) {
515           extended.push_back(complex);
516         }
517       }
518       else {
519         if (extended.empty()) {
520           for (size_t n = 0; n < i; n += 1) {
521             extended.push_back(list->get(n));
522           }
523         }
524         for (auto sel : result) {
525           extended.push_back(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(trim(extended, 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   // ##########################################################################
extendComplex(const ComplexSelectorObj & complex,const ExtSelExtMap & extensions,const CssMediaRuleObj & mediaQueryContext)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(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>(component)) {
576         sass::vector<ComplexSelectorObj> extended = extendCompound(
577           compound, extensions, mediaQueryContext, isOriginal);
578         if (extended.empty()) {
579           if (!extendedNotExpanded.empty()) {
580             extendedNotExpanded.push_back({
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({
590                   complex->at(n)->wrapInComplex()
591                 });
592             }
593           }
594           extendedNotExpanded.push_back(extended);
595         }
596       }
597       else {
598         // Note: dart-sass checks for null!?
599         if (!extendedNotExpanded.empty()) {
600           extendedNotExpanded.push_back({
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(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(_paths.end(), sel->elements());
623       }
624 
625       sass::vector<sass::vector<SelectorComponentObj>> weaved = weave(_paths);
626 
627       for (sass::vector<SelectorComponentObj>& components : weaved) {
628 
629         ComplexSelectorObj cplx = SASS_MEMORY_NEW(ComplexSelector, complex->pstate());
630         cplx->hasPreLineFeed(complex->hasPreLineFeed());
631         for (auto& pp : path) {
632           if (pp->hasPreLineFeed()) {
633             cplx->hasPreLineFeed(true);
634           }
635         }
636         cplx->elements(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(complex) != originals.end()) {
642           originals.insert(cplx);
643         }
644         first = false;
645 
646         auto it = result.begin();
647         while (it != result.end()) {
648           if (ObjEqualityFn(*it, cplx)) break;
649           it += 1;
650         }
651         if (it == result.end()) {
652           result.push_back(cplx);
653         }
654 
655         if (result.size() > 500) {
656           throw Exception::EndlessExtendError(traces, complex);
657         }
658 
659       }
660 
661     }
662 
663     return result;
664   }
665   // EO extendComplex
666 
667   // ##########################################################################
668   // Returns a one-off [Extension] whose
669   // extender is composed solely of [simple].
670   // ##########################################################################
extensionForSimple(const SimpleSelectorObj & simple) const671   Extension Extender::extensionForSimple(
672     const SimpleSelectorObj& simple) const
673   {
674     Extension extension(simple->wrapInComplex());
675     extension.specificity = maxSourceSpecificity(simple);
676     extension.isOriginal = true;
677     return extension;
678   }
679   // Extender::extensionForSimple
680 
681   // ##########################################################################
682   // Returns a one-off [Extension] whose extender is composed
683   // solely of a compound selector containing [simples].
684   // ##########################################################################
extensionForCompound(const sass::vector<SimpleSelectorObj> & simples) const685   Extension Extender::extensionForCompound(
686     // Taking in a reference here makes MSVC debug stuck!?
687     const sass::vector<SimpleSelectorObj>& simples) const
688   {
689     CompoundSelectorObj compound = SASS_MEMORY_NEW(CompoundSelector, SourceSpan("[ext]"));
690     compound->concat(simples);
691     Extension extension(compound->wrapInComplex());
692     // extension.specificity = sourceSpecificity[simple];
693     extension.isOriginal = true;
694     return extension;
695   }
696   // EO extensionForCompound
697 
698   // ##########################################################################
699   // Extends [compound] using [extensions], and returns the
700   // contents of a [SelectorList]. The [inOriginal] parameter
701   // indicates whether this is in an original complex selector,
702   // meaning that [compound] should not be trimmed out.
703   // ##########################################################################
extendCompound(const CompoundSelectorObj & compound,const ExtSelExtMap & extensions,const CssMediaRuleObj & mediaQueryContext,bool inOriginal)704   sass::vector<ComplexSelectorObj> Extender::extendCompound(
705     const CompoundSelectorObj& compound,
706     const ExtSelExtMap& extensions,
707     const CssMediaRuleObj& mediaQueryContext,
708     bool inOriginal)
709   {
710 
711     // If there's more than one target and they all need to
712     // match, we track which targets are actually extended.
713     ExtSmplSelSet targetsUsed2;
714 
715     ExtSmplSelSet* targetsUsed = nullptr;
716 
717     if (mode != ExtendMode::NORMAL && extensions.size() > 1) {
718       targetsUsed = &targetsUsed2;
719     }
720 
721     sass::vector<ComplexSelectorObj> result;
722     // The complex selectors produced from each component of [compound].
723     sass::vector<sass::vector<Extension>> options;
724 
725     for (size_t i = 0; i < compound->length(); i++) {
726       const SimpleSelectorObj& simple = compound->get(i);
727       auto extended = extendSimple(simple, extensions, mediaQueryContext, targetsUsed);
728       if (extended.empty()) {
729         if (!options.empty()) {
730           options.push_back({ extensionForSimple(simple) });
731         }
732       }
733       else {
734         if (options.empty()) {
735           if (i != 0) {
736             sass::vector<SimpleSelectorObj> in;
737             for (size_t n = 0; n < i; n += 1) {
738               in.push_back(compound->get(n));
739             }
740             options.push_back({ extensionForCompound(in) });
741           }
742         }
743         options.insert(options.end(),
744           extended.begin(), extended.end());
745       }
746     }
747 
748     if (options.empty()) {
749       return {};
750     }
751 
752     // If [_mode] isn't [ExtendMode.normal] and we didn't use all
753     // the targets in [extensions], extension fails for [compound].
754     if (targetsUsed != nullptr) {
755 
756       if (targetsUsed->size() != extensions.size()) {
757         if (!targetsUsed->empty()) {
758           return {};
759         }
760       }
761     }
762 
763     // Optimize for the simple case of a single simple
764     // selector that doesn't need any unification.
765     if (options.size() == 1) {
766       sass::vector<Extension> exts = options[0];
767       for (size_t n = 0; n < exts.size(); n += 1) {
768         exts[n].assertCompatibleMediaContext(mediaQueryContext, traces);
769         // To fix invalid css we need to re-order some
770         // Therefore we need to make copies for them
771         if (exts[n].extender->isInvalidCss()) {
772           exts[n].extender = SASS_MEMORY_COPY(exts[n].extender);
773           for (SelectorComponentObj& component : exts[n].extender->elements()) {
774             if (CompoundSelector* compound = component->getCompound()) {
775               if (compound->isInvalidCss()) {
776                 CompoundSelector* copy = SASS_MEMORY_COPY(compound);
777                 copy->sortChildren();
778                 component = copy;
779               }
780             }
781           }
782         }
783         result.push_back(exts[n].extender);
784       }
785       return result;
786     }
787 
788     // Find all paths through [options]. In this case, each path represents a
789     // different unification of the base selector. For example, if we have:
790     //
791     //     .a.b {...}
792     //     .w .x {@extend .a}
793     //     .y .z {@extend .b}
794     //
795     // then [options] is `[[.a, .w .x], [.b, .y .z]]` and `paths(options)` is
796     //
797     //     [
798     //       [.a, .b],
799     //       [.a, .y .z],
800     //       [.w .x, .b],
801     //       [.w .x, .y .z]
802     //     ]
803     //
804     // We then unify each path to get a list of complex selectors:
805     //
806     //     [
807     //       [.a.b],
808     //       [.y .a.z],
809     //       [.w .x.b],
810     //       [.w .y .x.z, .y .w .x.z]
811     //     ]
812 
813     bool first = mode != ExtendMode::REPLACE;
814     sass::vector<ComplexSelectorObj> unifiedPaths;
815     sass::vector<sass::vector<Extension>> prePaths = permutate(options);
816 
817     for (size_t i = 0; i < prePaths.size(); i += 1) {
818       sass::vector<sass::vector<SelectorComponentObj>> complexes;
819       const sass::vector<Extension>& path = prePaths[i];
820       if (first) {
821         // The first path is always the original selector. We can't just
822         // return [compound] directly because pseudo selectors may be
823         // modified, but we don't have to do any unification.
824         first = false;
825         CompoundSelectorObj mergedSelector =
826           SASS_MEMORY_NEW(CompoundSelector, "[ext]");
827         for (size_t n = 0; n < path.size(); n += 1) {
828           const ComplexSelectorObj& sel = path[n].extender;
829           if (CompoundSelectorObj compound = Cast<CompoundSelector>(sel->last())) {
830             mergedSelector->concat(compound->elements());
831           }
832         }
833         complexes.push_back({ mergedSelector });
834       }
835       else {
836         sass::vector<SimpleSelectorObj> originals;
837         sass::vector<sass::vector<SelectorComponentObj>> toUnify;
838 
839         for (auto& state : path) {
840           if (state.isOriginal) {
841             const ComplexSelectorObj& sel = state.extender;
842             if (const CompoundSelector* compound = Cast<CompoundSelector>(sel->last())) {
843               originals.insert(originals.end(), compound->last());
844             }
845           }
846           else {
847             toUnify.push_back(state.extender->elements());
848           }
849         }
850         if (!originals.empty()) {
851           CompoundSelectorObj merged =
852             SASS_MEMORY_NEW(CompoundSelector, "[compound]");
853           merged->concat(originals);
854           toUnify.insert(toUnify.begin(), { merged });
855         }
856         complexes = unifyComplex(toUnify);
857         if (complexes.empty()) {
858           return {};
859         }
860 
861       }
862 
863       bool lineBreak = false;
864       // var specificity = _sourceSpecificityFor(compound);
865       for (const Extension& state : path) {
866         state.assertCompatibleMediaContext(mediaQueryContext, traces);
867         lineBreak = lineBreak || state.extender->hasPreLineFeed();
868         // specificity = math.max(specificity, state.specificity);
869       }
870 
871       for (sass::vector<SelectorComponentObj>& components : complexes) {
872         auto sel = SASS_MEMORY_NEW(ComplexSelector, "[unified]");
873         sel->hasPreLineFeed(lineBreak);
874         sel->elements(components);
875 
876         /* This seems to do too much in regard of previous behavior
877         for (SelectorComponentObj& component : sel->elements()) {
878           if (CompoundSelector* compound = component->getCompound()) {
879             if (compound->isInvalidCss()) {
880               CompoundSelector* copy = SASS_MEMORY_COPY(compound);
881               copy->sortChildren();
882               component = copy;
883             }
884           }
885         }*/
886 
887         unifiedPaths.push_back(sel);
888 
889       }
890 
891     }
892 
893     return unifiedPaths;
894   }
895   // EO extendCompound
896 
897   // ##########################################################################
898   // Extends [simple] without extending the
899   // contents of any selector pseudos it contains.
900   // ##########################################################################
extendWithoutPseudo(const SimpleSelectorObj & simple,const ExtSelExtMap & extensions,ExtSmplSelSet * targetsUsed) const901   sass::vector<Extension> Extender::extendWithoutPseudo(
902     const SimpleSelectorObj& simple,
903     const ExtSelExtMap& extensions,
904     ExtSmplSelSet* targetsUsed) const
905   {
906 
907     auto extension = extensions.find(simple);
908     if (extension == extensions.end()) return {};
909     const ExtSelExtMapEntry& extenders = extension->second;
910 
911     if (targetsUsed != nullptr) {
912       targetsUsed->insert(simple);
913     }
914     if (mode == ExtendMode::REPLACE) {
915       return extenders.values();
916     }
917 
918     const sass::vector<Extension>&
919       values = extenders.values();
920     sass::vector<Extension> result;
921     result.reserve(values.size() + 1);
922     result.push_back(extensionForSimple(simple));
923     result.insert(result.end(), values.begin(), values.end());
924     return result;
925   }
926   // EO extendWithoutPseudo
927 
928   // ##########################################################################
929   // Extends [simple] and also extending the
930   // contents of any selector pseudos it contains.
931   // ##########################################################################
extendSimple(const SimpleSelectorObj & simple,const ExtSelExtMap & extensions,const CssMediaRuleObj & mediaQueryContext,ExtSmplSelSet * targetsUsed)932   sass::vector<sass::vector<Extension>> Extender::extendSimple(
933     const SimpleSelectorObj& simple,
934     const ExtSelExtMap& extensions,
935     const CssMediaRuleObj& mediaQueryContext,
936     ExtSmplSelSet* targetsUsed)
937   {
938     if (PseudoSelector* pseudo = Cast<PseudoSelector>(simple)) {
939       if (pseudo->selector()) {
940         sass::vector<sass::vector<Extension>> merged;
941         sass::vector<PseudoSelectorObj> extended =
942           extendPseudo(pseudo, extensions, mediaQueryContext);
943         for (PseudoSelectorObj& extend : extended) {
944           SimpleSelectorObj simple = extend;
945           sass::vector<Extension> result =
946             extendWithoutPseudo(simple, extensions, targetsUsed);
947           if (result.empty()) result = { extensionForSimple(extend) };
948           merged.push_back(result);
949         }
950         if (!extended.empty()) {
951           return merged;
952         }
953       }
954     }
955     sass::vector<Extension> result =
956       extendWithoutPseudo(simple, extensions, targetsUsed);
957     if (result.empty()) return {};
958     return { result };
959   }
960   // extendSimple
961 
962   // ##########################################################################
963   // Inner loop helper for [extendPseudo] function
964   // ##########################################################################
extendPseudoComplex(const ComplexSelectorObj & complex,const PseudoSelectorObj & pseudo,const CssMediaRuleObj & mediaQueryContext)965   sass::vector<ComplexSelectorObj> Extender::extendPseudoComplex(
966     const ComplexSelectorObj& complex,
967     const PseudoSelectorObj& pseudo,
968     const CssMediaRuleObj& mediaQueryContext)
969   {
970 
971     if (complex->length() != 1) { return { complex }; }
972     auto compound = Cast<CompoundSelector>(complex->get(0));
973     if (compound == nullptr) { return { complex }; }
974     if (compound->length() != 1) { return { complex }; }
975     auto innerPseudo = Cast<PseudoSelector>(compound->get(0));
976     if (innerPseudo == nullptr) { return { complex }; }
977     if (!innerPseudo->selector()) { return { complex }; }
978 
979     sass::string name(pseudo->normalized());
980 
981     if (name == "not") {
982       // In theory, if there's a `:not` nested within another `:not`, the
983       // inner `:not`'s contents should be unified with the return value.
984       // For example, if `:not(.foo)` extends `.bar`, `:not(.bar)` should
985       // become `.foo:not(.bar)`. However, this is a narrow edge case and
986       // supporting it properly would make this code and the code calling it
987       // a lot more complicated, so it's not supported for now.
988       if (innerPseudo->normalized() != "matches") return {};
989       return innerPseudo->selector()->elements();
990     }
991     else if (name == "matches" || name == "any" || name == "current" || name == "nth-child" || name == "nth-last-child") {
992       // As above, we could theoretically support :not within :matches, but
993       // doing so would require this method and its callers to handle much
994       // more complex cases that likely aren't worth the pain.
995       if (innerPseudo->name() != pseudo->name()) return {};
996       if (!ObjEquality()(innerPseudo->argument(), pseudo->argument())) return {};
997       return innerPseudo->selector()->elements();
998     }
999     else if (name == "has" || name == "host" || name == "host-context" || name == "slotted") {
1000       // We can't expand nested selectors here, because each layer adds an
1001       // additional layer of semantics. For example, `:has(:has(img))`
1002       // doesn't match `<div><img></div>` but `:has(img)` does.
1003       return { complex };
1004     }
1005 
1006     return {};
1007 
1008   }
1009   // EO extendPseudoComplex
1010 
1011   // ##########################################################################
1012   // Extends [pseudo] using [extensions], and returns
1013   // a list of resulting pseudo selectors.
1014   // ##########################################################################
extendPseudo(const PseudoSelectorObj & pseudo,const ExtSelExtMap & extensions,const CssMediaRuleObj & mediaQueryContext)1015   sass::vector<PseudoSelectorObj> Extender::extendPseudo(
1016     const PseudoSelectorObj& pseudo,
1017     const ExtSelExtMap& extensions,
1018     const CssMediaRuleObj& mediaQueryContext)
1019   {
1020     auto selector = pseudo->selector();
1021     SelectorListObj extended = extendList(
1022       selector, extensions, mediaQueryContext);
1023     if (!extended || !pseudo || !pseudo->selector()) { return {}; }
1024     if (ObjEqualityFn(pseudo->selector(), extended)) { return {}; }
1025 
1026     // For `:not()`, we usually want to get rid of any complex selectors because
1027     // that will cause the selector to fail to parse on all browsers at time of
1028     // writing. We can keep them if either the original selector had a complex
1029     // selector, or the result of extending has only complex selectors, because
1030     // either way we aren't breaking anything that isn't already broken.
1031     sass::vector<ComplexSelectorObj> complexes = extended->elements();
1032 
1033     if (pseudo->normalized() == "not") {
1034       if (!hasAny(pseudo->selector()->elements(), hasMoreThanOne)) {
1035         if (hasAny(extended->elements(), hasExactlyOne)) {
1036           complexes.clear();
1037           for (auto& complex : extended->elements()) {
1038             if (complex->length() <= 1) {
1039               complexes.push_back(complex);
1040             }
1041           }
1042         }
1043       }
1044     }
1045 
1046     sass::vector<ComplexSelectorObj> expanded = expand(
1047       complexes, extendPseudoComplex, pseudo, mediaQueryContext);
1048 
1049     // Older browsers support `:not`, but only with a single complex selector.
1050     // In order to support those browsers, we break up the contents of a `:not`
1051     // unless it originally contained a selector list.
1052     if (pseudo->normalized() == "not") {
1053       if (pseudo->selector()->length() == 1) {
1054         sass::vector<PseudoSelectorObj> pseudos;
1055         for (size_t i = 0; i < expanded.size(); i += 1) {
1056           pseudos.push_back(pseudo->withSelector(
1057             expanded[i]->wrapInList()
1058           ));
1059         }
1060         return pseudos;
1061       }
1062     }
1063 
1064     SelectorListObj list = SASS_MEMORY_NEW(SelectorList, "[pseudo]");
1065     list->concat(expanded);
1066     return { pseudo->withSelector(list) };
1067 
1068   }
1069   // EO extendPseudo
1070 
1071   // ##########################################################################
1072   // Rotates the element in list from [start] (inclusive) to [end] (exclusive)
1073   // one index higher, looping the final element back to [start].
1074   // ##########################################################################
rotateSlice(sass::vector<ComplexSelectorObj> & list,size_t start,size_t end)1075   void Extender::rotateSlice(
1076     sass::vector<ComplexSelectorObj>& list,
1077     size_t start, size_t end)
1078   {
1079     auto element = list[end - 1];
1080     for (size_t i = start; i < end; i++) {
1081       auto next = list[i];
1082       list[i] = element;
1083       element = next;
1084     }
1085   }
1086   // EO rotateSlice
1087 
1088   // ##########################################################################
1089   // Removes elements from [selectors] if they're subselectors of other
1090   // elements. The [isOriginal] callback indicates which selectors are
1091   // original to the document, and thus should never be trimmed.
1092   // ##########################################################################
1093   // Note: for adaption I pass in the set directly, there is some
1094   // code path in selector-trim that might need this special callback
1095   // ##########################################################################
trim(const sass::vector<ComplexSelectorObj> & selectors,const ExtCplxSelSet & existing) const1096   sass::vector<ComplexSelectorObj> Extender::trim(
1097     const sass::vector<ComplexSelectorObj>& selectors,
1098     const ExtCplxSelSet& existing) const
1099   {
1100 
1101     // Avoid truly horrific quadratic behavior.
1102     // TODO(nweiz): I think there may be a way to get perfect trimming
1103     // without going quadratic by building some sort of trie-like
1104     // data structure that can be used to look up superselectors.
1105     // TODO(mgreter): Check how this performs in C++ (up the limit)
1106     if (selectors.size() > 100) return selectors;
1107 
1108     // This is n² on the sequences, but only comparing between separate sequences
1109     // should limit the quadratic behavior. We iterate from last to first and reverse
1110     // the result so that, if two selectors are identical, we keep the first one.
1111     sass::vector<ComplexSelectorObj> result; size_t numOriginals = 0;
1112 
1113     size_t i = selectors.size();
1114   outer: // Use label to continue loop
1115     while (--i != sass::string::npos) {
1116 
1117       const ComplexSelectorObj& complex1 = selectors[i];
1118       // Check if selector in known in existing "originals"
1119       // For custom behavior dart-sass had `isOriginal(complex1)`
1120       if (existing.find(complex1) != existing.end()) {
1121         // Make sure we don't include duplicate originals, which could
1122         // happen if a style rule extends a component of its own selector.
1123         for (size_t j = 0; j < numOriginals; j++) {
1124           if (ObjEqualityFn(result[j], complex1)) {
1125             rotateSlice(result, 0, j + 1);
1126             goto outer;
1127           }
1128         }
1129         result.insert(result.begin(), complex1);
1130         numOriginals++;
1131         continue;
1132       }
1133 
1134       // The maximum specificity of the sources that caused [complex1]
1135       // to be generated. In order for [complex1] to be removed, there
1136       // must be another selector that's a superselector of it *and*
1137       // that has specificity greater or equal to this.
1138       size_t maxSpecificity = 0;
1139       for (const SelectorComponentObj& component : complex1->elements()) {
1140         if (const CompoundSelectorObj compound = Cast<CompoundSelector>(component)) {
1141           maxSpecificity = std::max(maxSpecificity, maxSourceSpecificity(compound));
1142         }
1143       }
1144 
1145 
1146       // Look in [result] rather than [selectors] for selectors after [i]. This
1147       // ensures we aren't comparing against a selector that's already been trimmed,
1148       // and thus that if there are two identical selectors only one is trimmed.
1149       if (hasAny(result, dontTrimComplex, complex1, maxSpecificity)) {
1150         continue;
1151       }
1152 
1153       // Check if any element (up to [i]) from [selector] returns true
1154       // when passed to [dontTrimComplex]. The arguments [complex1] and
1155       // [maxSepcificity] will be passed to the invoked function.
1156       if (hasSubAny(selectors, i, dontTrimComplex, complex1, maxSpecificity)) {
1157         continue;
1158       }
1159 
1160       // ToDo: Maybe use deque for front insert?
1161       result.insert(result.begin(), complex1);
1162 
1163     }
1164 
1165     return result;
1166 
1167   }
1168   // EO trim
1169 
1170   // ##########################################################################
1171   // Returns the maximum specificity of the given [simple] source selector.
1172   // ##########################################################################
maxSourceSpecificity(const SimpleSelectorObj & simple) const1173   size_t Extender::maxSourceSpecificity(const SimpleSelectorObj& simple) const
1174   {
1175     auto it = sourceSpecificity.find(simple);
1176     if (it == sourceSpecificity.end()) return 0;
1177     return it->second;
1178   }
1179   // EO maxSourceSpecificity(SimpleSelectorObj)
1180 
1181   // ##########################################################################
1182   // Returns the maximum specificity for sources that went into producing [compound].
1183   // ##########################################################################
maxSourceSpecificity(const CompoundSelectorObj & compound) const1184   size_t Extender::maxSourceSpecificity(const CompoundSelectorObj& compound) const
1185   {
1186     size_t specificity = 0;
1187     for (auto simple : compound->elements()) {
1188       size_t src = maxSourceSpecificity(simple);
1189       specificity = std::max(specificity, src);
1190     }
1191     return specificity;
1192   }
1193   // EO maxSourceSpecificity(CompoundSelectorObj)
1194 
1195   // ##########################################################################
1196   // Helper function used as callbacks on lists
1197   // ##########################################################################
dontTrimComplex(const ComplexSelector * complex2,const ComplexSelector * complex1,const size_t maxSpecificity)1198   bool Extender::dontTrimComplex(
1199     const ComplexSelector* complex2,
1200     const ComplexSelector* complex1,
1201     const size_t maxSpecificity)
1202   {
1203     if (complex2->minSpecificity() < maxSpecificity) return false;
1204     return complex2->isSuperselectorOf(complex1);
1205   }
1206   // EO dontTrimComplex
1207 
1208   // ##########################################################################
1209   // Helper function used as callbacks on lists
1210   // ##########################################################################
hasExactlyOne(const ComplexSelectorObj & vec)1211   bool Extender::hasExactlyOne(const ComplexSelectorObj& vec)
1212   {
1213     return vec->length() == 1;
1214   }
1215   // EO hasExactlyOne
1216 
1217   // ##########################################################################
1218   // Helper function used as callbacks on lists
1219   // ##########################################################################
hasMoreThanOne(const ComplexSelectorObj & vec)1220   bool Extender::hasMoreThanOne(const ComplexSelectorObj& vec)
1221   {
1222     return vec->length() > 1;
1223   }
1224   // hasMoreThanOne
1225 
1226 }
1227