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, "[phony]");
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         result.push_back(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   // ##########################################################################
extensionForSimple(const SimpleSelectorObj & simple) const660   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   // ##########################################################################
extensionForCompound(const sass::vector<SimpleSelectorObj> & simples) const674   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(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   // ##########################################################################
extendCompound(const CompoundSelectorObj & compound,const ExtSelExtMap & extensions,const CssMediaRuleObj & mediaQueryContext,bool inOriginal)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({ 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(compound->get(n));
728             }
729             options.push_back({ extensionForCompound(in) });
730           }
731         }
732         options.insert(options.end(),
733           extended.begin(), 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(mediaQueryContext, traces);
758         result.push_back(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(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>(sel->last())) {
805             mergedSelector->concat(compound->elements());
806           }
807         }
808         complexes.push_back({ 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>(sel->last())) {
818               originals.insert(originals.end(), compound->last());
819             }
820           }
821           else {
822             toUnify.push_back(state.extender->elements());
823           }
824         }
825         if (!originals.empty()) {
826           CompoundSelectorObj merged =
827             SASS_MEMORY_NEW(CompoundSelector, "[phony]");
828           merged->concat(originals);
829           toUnify.insert(toUnify.begin(), { merged });
830         }
831         complexes = unifyComplex(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(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(lineBreak);
849         sel->elements(components);
850         unifiedPaths.push_back(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   // ##########################################################################
extendWithoutPseudo(const SimpleSelectorObj & simple,const ExtSelExtMap & extensions,ExtSmplSelSet * targetsUsed) const863   sass::vector<Extension> Extender::extendWithoutPseudo(
864     const SimpleSelectorObj& simple,
865     const ExtSelExtMap& extensions,
866     ExtSmplSelSet* targetsUsed) const
867   {
868 
869     auto extension = extensions.find(simple);
870     if (extension == extensions.end()) return {};
871     const ExtSelExtMapEntry& extenders = extension->second;
872 
873     if (targetsUsed != nullptr) {
874       targetsUsed->insert(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(values.size() + 1);
884     result.push_back(extensionForSimple(simple));
885     result.insert(result.end(), values.begin(), 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   // ##########################################################################
extendSimple(const SimpleSelectorObj & simple,const ExtSelExtMap & extensions,const CssMediaRuleObj & mediaQueryContext,ExtSmplSelSet * targetsUsed)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>(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(extend) };
910           merged.push_back(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   // ##########################################################################
extendPseudoComplex(const ComplexSelectorObj & complex,const PseudoSelectorObj & pseudo,const CssMediaRuleObj & mediaQueryContext)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>(complex->get(0));
935     if (compound == nullptr) { return { complex }; }
936     if (compound->length() != 1) { return { complex }; }
937     auto innerPseudo = Cast<PseudoSelector>(compound->get(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   // ##########################################################################
extendPseudo(const PseudoSelectorObj & pseudo,const ExtSelExtMap & extensions,const CssMediaRuleObj & mediaQueryContext)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       selector, extensions, mediaQueryContext);
985     if (!extended || !pseudo || !pseudo->selector()) { return {}; }
986     if (ObjEqualityFn(pseudo->selector(), 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(pseudo->selector()->elements(), hasMoreThanOne)) {
997         if (hasAny(extended->elements(), hasExactlyOne)) {
998           complexes.clear();
999           for (auto& complex : extended->elements()) {
1000             if (complex->length() <= 1) {
1001               complexes.push_back(complex);
1002             }
1003           }
1004         }
1005       }
1006     }
1007 
1008     sass::vector<ComplexSelectorObj> expanded = expand(
1009       complexes, extendPseudoComplex, pseudo, 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(pseudo->withSelector(
1019             expanded[i]->wrapInList()
1020           ));
1021         }
1022         return pseudos;
1023       }
1024     }
1025 
1026     SelectorListObj list = SASS_MEMORY_NEW(SelectorList, "[phony]");
1027     list->concat(complexes);
1028     return { pseudo->withSelector(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   // ##########################################################################
rotateSlice(sass::vector<ComplexSelectorObj> & list,size_t start,size_t end)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   // ##########################################################################
trim(const sass::vector<ComplexSelectorObj> & selectors,const ExtCplxSelSet & existing) const1058   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(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(result[j], complex1)) {
1087             rotateSlice(result, 0, j + 1);
1088             goto outer;
1089           }
1090         }
1091         result.insert(result.begin(), 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>(component)) {
1103           maxSpecificity = std::max(maxSpecificity, 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(result, dontTrimComplex, complex1, 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(selectors, i, dontTrimComplex, complex1, maxSpecificity)) {
1119         continue;
1120       }
1121 
1122       // ToDo: Maybe use deque for front insert?
1123       result.insert(result.begin(), 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   // ##########################################################################
maxSourceSpecificity(const SimpleSelectorObj & simple) const1135   size_t Extender::maxSourceSpecificity(const SimpleSelectorObj& simple) const
1136   {
1137     auto it = sourceSpecificity.find(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   // ##########################################################################
maxSourceSpecificity(const CompoundSelectorObj & compound) const1146   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(specificity, src);
1152     }
1153     return specificity;
1154   }
1155   // EO maxSourceSpecificity(CompoundSelectorObj)
1156 
1157   // ##########################################################################
1158   // Helper function used as callbacks on lists
1159   // ##########################################################################
dontTrimComplex(const ComplexSelector * complex2,const ComplexSelector * complex1,const size_t maxSpecificity)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(complex1);
1167   }
1168   // EO dontTrimComplex
1169 
1170   // ##########################################################################
1171   // Helper function used as callbacks on lists
1172   // ##########################################################################
hasExactlyOne(const ComplexSelectorObj & vec)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   // ##########################################################################
hasMoreThanOne(const ComplexSelectorObj & vec)1182   bool Extender::hasMoreThanOne(const ComplexSelectorObj& vec)
1183   {
1184     return vec->length() > 1;
1185   }
1186   // hasMoreThanOne
1187 
1188 }
1189