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