Skip to content

Commit

Permalink
Avoid extra computations in superselector checks where possible
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed May 29, 2024
1 parent e61d6aa commit 3a683c2
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 14 deletions.
12 changes: 12 additions & 0 deletions lib/src/ast/selector/compound.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ final class CompoundSelector extends Selector {
SimpleSelector? get singleSimple =>
components.length == 1 ? components.first : null;

/// Whether any simple selector in this contains a selector that requires
/// complex non-local reasoning to determine whether it's a super- or
/// sub-selector.
///
/// This includes both pseudo-elements and pseudo-selectors that take
/// selectors as arguments.
///
/// #nodoc
@internal
late final bool hasComplicatedSuperselectorSemantics = components
.any((component) => component.hasComplicatedSuperselectorSemantics);

CompoundSelector(Iterable<SimpleSelector> components, super.span)
: components = List.unmodifiable(components) {
if (this.components.isEmpty) {
Expand Down
4 changes: 4 additions & 0 deletions lib/src/ast/selector/pseudo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ final class PseudoSelector extends SimpleSelector {
bool get isHostContext =>
isClass && name == 'host-context' && selector != null;

@internal
bool get hasComplicatedSuperselectorSemantics =>
isElement || selector != null;

/// The non-selector argument passed to this selector.
///
/// This is `null` if there's no argument. If [argument] and [selector] are
Expand Down
10 changes: 10 additions & 0 deletions lib/src/ast/selector/simple.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ abstract base class SimpleSelector extends Selector {
/// sequence will contain 1000 simple selectors.
int get specificity => 1000;

/// Whether this requires complex non-local reasoning to determine whether
/// it's a super- or sub-selector.
///
/// This includes both pseudo-elements and pseudo-selectors that take
/// selectors as arguments.
///
/// #nodoc
@internal
bool get hasComplicatedSuperselectorSemantics => false;

SimpleSelector(super.span);

/// Parses a simple selector from [contents].
Expand Down
38 changes: 24 additions & 14 deletions lib/src/extend/functions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -646,24 +646,28 @@ bool complexIsSuperselector(List<ComplexSelectorComponent> complex1,
var component1 = complex1[i1];
if (component1.combinators.length > 1) return false;
if (remaining1 == 1) {
var parents = complex2.sublist(i2, complex2.length - 1);
if (parents.any((parent) => parent.combinators.length > 1)) return false;

return compoundIsSuperselector(
component1.selector, complex2.last.selector,
parents: parents);
if (complex2.any((parent) => parent.combinators.length > 1)) {
return false;
} else {
return compoundIsSuperselector(
component1.selector, complex2.last.selector,
parents: component1.selector.hasComplicatedSuperselectorSemantics
? complex2.sublist(i2, complex2.length - 1)
: null);
}
}

// Find the first index [endOfSubselector] in [complex2] such that
// `complex2.sublist(i2, endOfSubselector + 1)` is a subselector of
// [component1.selector].
var endOfSubselector = i2;
List<ComplexSelectorComponent>? parents;
while (true) {
var component2 = complex2[endOfSubselector];
if (component2.combinators.length > 1) return false;
if (compoundIsSuperselector(component1.selector, component2.selector,
parents: parents)) {
parents: component1.selector.hasComplicatedSuperselectorSemantics
? complex2.sublist(i2, endOfSubselector)
: null)) {
break;
}

Expand All @@ -675,13 +679,10 @@ bool complexIsSuperselector(List<ComplexSelectorComponent> complex1,
// to match.
return false;
}

parents ??= [];
parents.add(component2);
}

if (!_compatibleWithPreviousCombinator(
previousCombinator, parents ?? const [])) {
previousCombinator, complex2.take(endOfSubselector).skip(i2))) {
return false;
}

Expand Down Expand Up @@ -717,8 +718,8 @@ bool complexIsSuperselector(List<ComplexSelectorComponent> complex1,
/// Returns whether [parents] are valid intersitial components between one
/// complex superselector and another, given that the earlier complex
/// superselector had the combinator [previous].
bool _compatibleWithPreviousCombinator(
CssValue<Combinator>? previous, List<ComplexSelectorComponent> parents) {
bool _compatibleWithPreviousCombinator(CssValue<Combinator>? previous,
Iterable<ComplexSelectorComponent> parents) {
if (parents.isEmpty) return true;
if (previous == null) return true;

Expand Down Expand Up @@ -754,6 +755,15 @@ bool _isSupercombinator(
bool compoundIsSuperselector(
CompoundSelector compound1, CompoundSelector compound2,
{Iterable<ComplexSelectorComponent>? parents}) {
if (!compound1.hasComplicatedSuperselectorSemantics &&
!compound2.hasComplicatedSuperselectorSemantics) {
if (compound1.components.length > compound2.components.length) return false;
return compound1.components
.every((simple1) => compound2.components.any(simple1.isSuperselector));
}

//print("compoundIsSuperselector($compound1, $compound2, parents: $parents)");

// Pseudo elements effectively change the target of a compound selector rather
// than narrowing the set of elements to which it applies like other
// selectors. As such, if either selector has a pseudo element, they both must
Expand Down

0 comments on commit 3a683c2

Please sign in to comment.