Skip to content

Commit 42d6fbb

Browse files
authored
Ensure that selectors like :root always unify to the beginning (#1759)
Closes #1811
1 parent bc8df44 commit 42d6fbb

File tree

5 files changed

+44
-28
lines changed

5 files changed

+44
-28
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 1.54.1
2+
3+
* When unifying selectors for `@extend` and `selector.unify()`, ensure that
4+
`:root`, `:scope`, `:host`, and `:host-context` only appear at the beginning
5+
of complex selectors.
6+
17
## 1.54.0
28

39
* Deprecate selectors with leading or trailing combinators, or with multiple

lib/src/extend/functions.dart

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import 'package:tuple/tuple.dart';
1818
import '../ast/selector.dart';
1919
import '../utils.dart';
2020

21+
/// Pseudo-selectors that can only meaningfully appear in the first component of
22+
/// a complex selector.
23+
final _rootishPseudoClasses = {'root', 'scope', 'host', 'host-context'};
24+
2125
/// Returns the contents of a [SelectorList] that matches only elements that are
2226
/// matched by every complex selector in [complexes].
2327
///
@@ -231,19 +235,22 @@ Iterable<ComplexSelector>? _weaveParents(
231235
var trailingCombinators = _mergeTrailingCombinators(queue1, queue2);
232236
if (trailingCombinators == null) return null;
233237

234-
// Make sure there's at most one `:root` in the output.
235-
var root1 = _firstIfRoot(queue1);
236-
var root2 = _firstIfRoot(queue2);
237-
if (root1 != null && root2 != null) {
238-
var root =
239-
unifyCompound(root1.selector.components, root2.selector.components);
240-
if (root == null) return null;
241-
queue1.addFirst(ComplexSelectorComponent(root, root1.combinators));
242-
queue2.addFirst(ComplexSelectorComponent(root, root2.combinators));
243-
} else if (root1 != null) {
244-
queue2.addFirst(root1);
245-
} else if (root2 != null) {
246-
queue1.addFirst(root2);
238+
// Make sure all selectors that are required to be at the root
239+
var rootish1 = _firstIfRootish(queue1);
240+
var rootish2 = _firstIfRootish(queue2);
241+
if (rootish1 != null && rootish2 != null) {
242+
var rootish =
243+
unifyCompound(rootish1.selector.components, rootish2.selector.components);
244+
if (rootish == null) return null;
245+
queue1.addFirst(ComplexSelectorComponent(rootish, rootish1.combinators));
246+
queue2.addFirst(ComplexSelectorComponent(rootish, rootish2.combinators));
247+
} else if (rootish1 != null || rootish2 != null) {
248+
// If there's only one rootish selector, it should only appear in the first
249+
// position of the resulting selector. We can ensure that happens by adding
250+
// it to the beginning of _both_ queues.
251+
var rootish = (rootish1 ?? rootish2)!;
252+
queue1.addFirst(rootish);
253+
queue2.addFirst(rootish);
247254
}
248255

249256
var groups1 = _groupSelectors(queue1);
@@ -289,14 +296,19 @@ Iterable<ComplexSelector>? _weaveParents(
289296
];
290297
}
291298

292-
/// If the first element of [queue] has a `:root` selector, removes and returns
293-
/// that element.
294-
ComplexSelectorComponent? _firstIfRoot(Queue<ComplexSelectorComponent> queue) {
299+
/// If the first element of [queue] has a selector like `:root` that can only
300+
/// appear in a complex selector's first component, removes and returns that
301+
/// element.
302+
ComplexSelectorComponent? _firstIfRootish(Queue<ComplexSelectorComponent> queue) {
295303
if (queue.isEmpty) return null;
296304
var first = queue.first;
297-
if (!_hasRoot(first.selector)) return null;
298-
queue.removeFirst();
299-
return first;
305+
for (var simple in first.selector.components) {
306+
if (simple is PseudoSelector && simple.isClass && _rootishPseudoClasses.contains(simple.normalizedName)) {
307+
queue.removeFirst();
308+
return first;
309+
}
310+
}
311+
return null;
300312
}
301313

302314
/// Returns a leading combinator list that's compatible with both [combinators1]
@@ -543,12 +555,6 @@ QueueList<List<ComplexSelectorComponent>> _groupSelectors(
543555
return groups;
544556
}
545557

546-
/// Returns whether or not [compound] contains a `::root` selector.
547-
bool _hasRoot(CompoundSelector compound) => compound.components.any((simple) =>
548-
simple is PseudoSelector &&
549-
simple.isClass &&
550-
simple.normalizedName == 'root');
551-
552558
/// Returns whether [list1] is a superselector of [list2].
553559
///
554560
/// That is, whether [list1] matches every element that [list2] matches, as well

pkg/sass_api/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.0.1
2+
3+
* No user-visible changes.
4+
15
## 2.0.0
26

37
* Refactor the `CssMediaQuery` API to support new logical operators:

pkg/sass_api/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ name: sass_api
22
# Note: Every time we add a new Sass AST node, we need to bump the *major*
33
# version because it's a breaking change for anyone who's implementing the
44
# visitor interface(s).
5-
version: 2.0.0
5+
version: 2.0.1
66
description: Additional APIs for Dart Sass.
77
homepage: https://github.com/sass/dart-sass
88

99
environment:
1010
sdk: ">=2.12.0 <3.0.0"
1111

1212
dependencies:
13-
sass: 1.54.0
13+
sass: 1.54.1
1414

1515
dev_dependencies:
1616
dartdoc: ^5.0.0

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: sass
2-
version: 1.54.0
2+
version: 1.54.1
33
description: A Sass implementation in Dart.
44
homepage: https://github.com/sass/dart-sass
55

0 commit comments

Comments
 (0)