44
55import 'package:meta/meta.dart';
66
7+ import '../evaluation_context.dart';
8+ import '../exception.dart';
9+ import '../visitor/any_selector.dart';
710import '../visitor/interface/selector.dart';
811import '../visitor/serialize.dart';
12+ import 'selector/complex.dart';
13+ import 'selector/list.dart';
14+ import 'selector/placeholder.dart';
15+ import 'selector/pseudo.dart';
916
1017export 'selector/attribute.dart';
1118export 'selector/class.dart';
19+ export 'selector/combinator.dart';
1220export 'selector/complex.dart';
21+ export 'selector/complex_component.dart';
1322export 'selector/compound.dart';
1423export 'selector/id.dart';
1524export 'selector/list.dart';
@@ -32,11 +41,131 @@ export 'selector/universal.dart';
3241abstract class Selector {
3342 /// Whether this selector, and complex selectors containing it, should not be
3443 /// emitted.
44+ ///
45+ /// @nodoc
3546 @internal
36- bool get isInvisible => false;
47+ bool get isInvisible => accept(const _IsInvisibleVisitor(includeBogus: true));
48+
49+ // Whether this selector would be invisible even if it didn't have bogus
50+ // combinators.
51+ ///
52+ /// @nodoc
53+ @internal
54+ bool get isInvisibleOtherThanBogusCombinators =>
55+ accept(const _IsInvisibleVisitor(includeBogus: false));
56+
57+ /// Whether this selector is not valid CSS.
58+ ///
59+ /// This includes both selectors that are useful exclusively for build-time
60+ /// nesting (`> .foo)` and selectors with invalid combiantors that are still
61+ /// supported for backwards-compatibility reasons (`.foo + ~ .bar`).
62+ bool get isBogus =>
63+ accept(const _IsBogusVisitor(includeLeadingCombinator: true));
64+
65+ /// Whether this selector is bogus other than having a leading combinator.
66+ ///
67+ /// @nodoc
68+ @internal
69+ bool get isBogusOtherThanLeadingCombinator =>
70+ accept(const _IsBogusVisitor(includeLeadingCombinator: false));
71+
72+ /// Whether this is a useless selector (that is, it's bogus _and_ it can't be
73+ /// transformed into valid CSS by `@extend` or nesting).
74+ ///
75+ /// @nodoc
76+ @internal
77+ bool get isUseless => accept(const _IsUselessVisitor());
78+
79+ /// Prints a warning if [this] is a bogus selector.
80+ ///
81+ /// This may only be called from within a custom Sass function. This will
82+ /// throw a [SassScriptException] in Dart Sass 2.0.0.
83+ void assertNotBogus({String? name}) {
84+ if (!isBogus) return;
85+ warn(
86+ (name == null ? '' : '\$$name: ') +
87+ '$this is not valid CSS.\n'
88+ 'This will be an error in Dart Sass 2.0.0.\n'
89+ '\n'
90+ 'More info: https://sass-lang.com/d/bogus-combinators',
91+ deprecation: true);
92+ }
3793
3894 /// Calls the appropriate visit method on [visitor].
3995 T accept<T>(SelectorVisitor<T> visitor);
4096
4197 String toString() => serializeSelector(this, inspect: true);
4298}
99+
100+ /// The visitor used to implement [Selector.isInvisible].
101+ class _IsInvisibleVisitor extends AnySelectorVisitor {
102+ /// Whether to consider selectors with bogus combinators invisible.
103+ final bool includeBogus;
104+
105+ const _IsInvisibleVisitor({required this.includeBogus});
106+
107+ bool visitSelectorList(SelectorList list) =>
108+ list.components.every(visitComplexSelector);
109+
110+ bool visitComplexSelector(ComplexSelector complex) =>
111+ super.visitComplexSelector(complex) ||
112+ (includeBogus && complex.isBogusOtherThanLeadingCombinator);
113+
114+ bool visitPlaceholderSelector(PlaceholderSelector placeholder) => true;
115+
116+ bool visitPseudoSelector(PseudoSelector pseudo) {
117+ var selector = pseudo.selector;
118+ if (selector == null) return false;
119+
120+ // We don't consider `:not(%foo)` to be invisible because, semantically, it
121+ // means "doesn't match this selector that matches nothing", so it's
122+ // equivalent to *. If the entire compound selector is composed of `:not`s
123+ // with invisible lists, the serializer emits it as `*`.
124+ return pseudo.name == 'not'
125+ ? (includeBogus && selector.isBogus)
126+ : selector.accept(this);
127+ }
128+ }
129+
130+ /// The visitor used to implement [Selector.isBogus].
131+ class _IsBogusVisitor extends AnySelectorVisitor {
132+ /// Whether to consider selectors with leading combinators as bogus.
133+ final bool includeLeadingCombinator;
134+
135+ const _IsBogusVisitor({required this.includeLeadingCombinator});
136+
137+ bool visitComplexSelector(ComplexSelector complex) {
138+ if (complex.components.isEmpty) {
139+ return complex.leadingCombinators.isNotEmpty;
140+ } else {
141+ return complex.leadingCombinators.length >
142+ (includeLeadingCombinator ? 0 : 1) ||
143+ complex.components.last.combinators.isNotEmpty ||
144+ complex.components.any((component) =>
145+ component.combinators.length > 1 ||
146+ component.selector.accept(this));
147+ }
148+ }
149+
150+ bool visitPseudoSelector(PseudoSelector pseudo) {
151+ var selector = pseudo.selector;
152+ if (selector == null) return false;
153+
154+ // The CSS spec specifically allows leading combinators in `:has()`.
155+ return pseudo.name == 'has'
156+ ? selector.isBogusOtherThanLeadingCombinator
157+ : selector.isBogus;
158+ }
159+ }
160+
161+ /// The visitor used to implement [Selector.isUseless]
162+ class _IsUselessVisitor extends AnySelectorVisitor {
163+ const _IsUselessVisitor();
164+
165+ bool visitComplexSelector(ComplexSelector complex) =>
166+ complex.leadingCombinators.length > 1 ||
167+ complex.components.any((component) =>
168+ component.combinators.length > 1 || component.selector.accept(this));
169+
170+ bool visitPseudoSelector(PseudoSelector pseudo) => pseudo.isBogus;
171+ }
0 commit comments