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