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