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