/
pseudo.dart
206 lines (171 loc) · 6.58 KB
/
pseudo.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:math' as math;
import 'package:charcode/charcode.dart';
import '../../utils.dart';
import '../../visitor/interface/selector.dart';
import '../selector.dart';
/// A pseudo-class or pseudo-element selector.
///
/// The semantics of a specific pseudo selector depends on its name. Some
/// selectors take arguments, including other selectors. Sass manually encodes
/// logic for each pseudo selector that takes a selector as an argument, to
/// ensure that extension and other selector operations work properly.
class PseudoSelector extends SimpleSelector {
/// The name of this selector.
final String name;
/// Like [name], but without any vendor prefixes.
final String normalizedName;
/// Whether this is a pseudo-class selector.
///
/// This is `true` if and only if [isElement] is `false`.
final bool isClass;
/// Whether this is a pseudo-element selector.
///
/// This is `true` if and only if [isClass] is `false`.
bool get isElement => !isClass;
/// Whether this is syntactically a pseudo-class selector.
///
/// This is the same as [isClass] unless this selector is a pseudo-element
/// that was written syntactically as a pseudo-class (`:before`, `:after`,
/// `:first-line`, or `:first-letter`).
///
/// This is `true` if and only if [isSyntacticElement] is `false`.
final bool isSyntacticClass;
/// Whether this is syntactically a pseudo-element selector.
///
/// This is `true` if and only if [isSyntacticClass] is `false`.
bool get isSyntacticElement => !isSyntacticClass;
/// The non-selector argument passed to this selector.
///
/// This is `null` if there's no argument. If [argument] and [selector] are
/// both non-`null`, the selector follows the argument.
final String? argument;
/// The selector argument passed to this selector.
///
/// This is `null` if there's no selector. If [argument] and [selector] are
/// both non-`null`, the selector follows the argument.
final SelectorList? selector;
int get minSpecificity {
if (_minSpecificity == null) _computeSpecificity();
return _minSpecificity!;
}
int? _minSpecificity;
int get maxSpecificity {
if (_maxSpecificity == null) _computeSpecificity();
return _maxSpecificity!;
}
int? _maxSpecificity;
bool get isInvisible {
var selector = this.selector;
if (selector == null) return false;
// We don't consider `:not(%foo)` to be invisible because, semantically, it
// means "doesn't match this selector that matches nothing", so it's
// equivalent to *. If the entire compound selector is composed of `:not`s
// with invisible lists, the serializer emits it as `*`.
return name != 'not' && selector.isInvisible;
}
PseudoSelector(String name,
{bool element = false, this.argument, this.selector})
: isClass = !element && !_isFakePseudoElement(name),
isSyntacticClass = !element,
name = name,
normalizedName = unvendor(name);
/// Returns whether [name] is the name of a pseudo-element that can be written
/// with pseudo-class syntax (`:before`, `:after`, `:first-line`, or
/// `:first-letter`)
static bool _isFakePseudoElement(String name) {
switch (name.codeUnitAt(0)) {
case $a:
case $A:
return equalsIgnoreCase(name, "after");
case $b:
case $B:
return equalsIgnoreCase(name, "before");
case $f:
case $F:
return equalsIgnoreCase(name, "first-line") ||
equalsIgnoreCase(name, "first-letter");
default:
return false;
}
}
/// Returns a new [PseudoSelector] based on this, but with the selector
/// replaced with [selector].
PseudoSelector withSelector(SelectorList selector) => PseudoSelector(name,
element: isElement, argument: argument, selector: selector);
PseudoSelector addSuffix(String suffix) {
if (argument != null || selector != null) super.addSuffix(suffix);
return PseudoSelector(name + suffix, element: isElement);
}
List<SimpleSelector>? unify(List<SimpleSelector> compound) {
if (compound.length == 1 && compound.first is UniversalSelector) {
return compound.first.unify([this]);
}
if (compound.contains(this)) return compound;
var result = <SimpleSelector>[];
var addedThis = false;
for (var simple in compound) {
if (simple is PseudoSelector && simple.isElement) {
// A given compound selector may only contain one pseudo element. If
// [compound] has a different one than [this], unification fails.
if (isElement) return null;
// Otherwise, this is a pseudo selector and should come before pseudo
// elements.
result.add(this);
addedThis = true;
}
result.add(simple);
}
if (!addedThis) result.add(this);
return result;
}
/// Computes [_minSpecificity] and [_maxSpecificity].
void _computeSpecificity() {
if (isElement) {
_minSpecificity = 1;
_maxSpecificity = 1;
return;
}
var selector = this.selector;
if (selector == null) {
_minSpecificity = super.minSpecificity;
_maxSpecificity = super.maxSpecificity;
return;
}
if (name == 'not') {
var minSpecificity = 0;
var maxSpecificity = 0;
for (var complex in selector.components) {
minSpecificity = math.max(minSpecificity, complex.minSpecificity);
maxSpecificity = math.max(maxSpecificity, complex.maxSpecificity);
}
_minSpecificity = minSpecificity;
_maxSpecificity = maxSpecificity;
} else {
// This is higher than any selector's specificity can actually be.
var minSpecificity = math.pow(super.minSpecificity, 3) as int;
var maxSpecificity = 0;
for (var complex in selector.components) {
minSpecificity = math.min(minSpecificity, complex.minSpecificity);
maxSpecificity = math.max(maxSpecificity, complex.maxSpecificity);
}
_minSpecificity = minSpecificity;
_maxSpecificity = maxSpecificity;
}
}
T accept<T>(SelectorVisitor<T> visitor) => visitor.visitPseudoSelector(this);
// This intentionally uses identity for the selector list, if one is available.
bool operator ==(Object other) =>
other is PseudoSelector &&
other.name == name &&
other.isClass == isClass &&
other.argument == argument &&
other.selector == selector;
int get hashCode =>
name.hashCode ^
isElement.hashCode ^
argument.hashCode ^
selector.hashCode;
}