-
Notifications
You must be signed in to change notification settings - Fork 354
/
complex.dart
187 lines (171 loc) · 6.73 KB
/
complex.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
// 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 'package:meta/meta.dart';
import 'package:source_span/source_span.dart';
import '../../extend/functions.dart';
import '../../logger.dart';
import '../../parse/selector.dart';
import '../../utils.dart';
import '../../visitor/interface/selector.dart';
import '../css/value.dart';
import '../selector.dart';
/// A complex selector.
///
/// A complex selector is composed of [CompoundSelector]s separated by
/// [Combinator]s. It selects elements based on their parent selectors.
///
/// {@category AST}
/// {@category Parsing}
final class ComplexSelector extends Selector {
/// This selector's leading combinators.
///
/// If this is empty, that indicates that it has no leading combinator. If
/// it's more than one element, that means it's invalid CSS; however, we still
/// support this for backwards-compatibility purposes.
final List<CssValue<Combinator>> leadingCombinators;
/// The components of this selector.
///
/// This is only empty if [leadingCombinators] is not empty.
///
/// Descendant combinators aren't explicitly represented here. If two
/// [CompoundSelector]s are adjacent to one another, there's an implicit
/// descendant combinator between them.
///
/// It's possible for multiple [Combinator]s to be adjacent to one another.
/// This isn't valid CSS, but Sass supports it for CSS hack purposes.
final List<ComplexSelectorComponent> components;
/// Whether a line break should be emitted *before* this selector.
///
/// @nodoc
@internal
final bool lineBreak;
/// This selector's specificity.
///
/// Specificity is represented in base 1000. The spec says this should be
/// "sufficiently high"; it's extremely unlikely that any single selector
/// sequence will contain 1000 simple selectors.
late final int specificity = components.fold(
0, (sum, component) => sum + component.selector.specificity);
/// If this compound selector is composed of a single compound selector with
/// no combinators, returns it.
///
/// Otherwise, returns null.
///
/// @nodoc
@internal
CompoundSelector? get singleCompound {
if (leadingCombinators.isNotEmpty) return null;
return switch (components) {
[ComplexSelectorComponent(:var selector, combinators: [])] => selector,
_ => null
};
}
ComplexSelector(Iterable<CssValue<Combinator>> leadingCombinators,
Iterable<ComplexSelectorComponent> components, super.span,
{this.lineBreak = false})
: leadingCombinators = List.unmodifiable(leadingCombinators),
components = List.unmodifiable(components) {
if (this.leadingCombinators.isEmpty && this.components.isEmpty) {
throw ArgumentError(
"leadingCombinators and components may not both be empty.");
}
}
/// Parses a complex selector from [contents].
///
/// If passed, [url] is the name of the file from which [contents] comes.
/// [allowParent] controls whether a [ParentSelector] is allowed in this
/// selector.
///
/// Throws a [SassFormatException] if parsing fails.
factory ComplexSelector.parse(String contents,
{Object? url, Logger? logger, bool allowParent = true}) =>
SelectorParser(contents,
url: url, logger: logger, allowParent: allowParent)
.parseComplexSelector();
T accept<T>(SelectorVisitor<T> visitor) => visitor.visitComplexSelector(this);
/// Whether this is a superselector of [other].
///
/// That is, whether this matches every element that [other] matches, as well
/// as possibly matching more.
bool isSuperselector(ComplexSelector other) =>
leadingCombinators.isEmpty &&
other.leadingCombinators.isEmpty &&
complexIsSuperselector(components, other.components);
/// Returns a copy of `this` with [combinators] added to the end of the final
/// component in [components].
///
/// If [forceLineBreak] is `true`, this will mark the new complex selector as
/// having a line break.
///
/// @nodoc
@internal
ComplexSelector withAdditionalCombinators(
List<CssValue<Combinator>> combinators,
{bool forceLineBreak = false}) {
if (combinators.isEmpty) return this;
return switch (components) {
[...var initial, var last] => ComplexSelector(leadingCombinators,
[...initial, last.withAdditionalCombinators(combinators)], span,
lineBreak: lineBreak || forceLineBreak),
[] => ComplexSelector(
[...leadingCombinators, ...combinators], const [], span,
lineBreak: lineBreak || forceLineBreak)
};
}
/// Returns a copy of `this` with an additional [component] added to the end.
///
/// If [forceLineBreak] is `true`, this will mark the new complex selector as
/// having a line break.
///
/// The [span] is used for the new selector.
///
/// @nodoc
@internal
ComplexSelector withAdditionalComponent(
ComplexSelectorComponent component, FileSpan span,
{bool forceLineBreak = false}) =>
ComplexSelector(leadingCombinators, [...components, component], span,
lineBreak: lineBreak || forceLineBreak);
/// Returns a copy of `this` with [child]'s combinators added to the end.
///
/// If [child] has [leadingCombinators], they're appended to `this`'s last
/// combinator. This does _not_ resolve parent selectors.
///
/// The [span] is used for the new selector.
///
/// If [forceLineBreak] is `true`, this will mark the new complex selector as
/// having a line break.
///
/// @nodoc
@internal
ComplexSelector concatenate(ComplexSelector child, FileSpan span,
{bool forceLineBreak = false}) {
if (child.leadingCombinators.isEmpty) {
return ComplexSelector(
leadingCombinators, [...components, ...child.components], span,
lineBreak: lineBreak || child.lineBreak || forceLineBreak);
} else if (components case [...var initial, var last]) {
return ComplexSelector(
leadingCombinators,
[
...initial,
last.withAdditionalCombinators(child.leadingCombinators),
...child.components
],
span,
lineBreak: lineBreak || child.lineBreak || forceLineBreak);
} else {
return ComplexSelector(
[...leadingCombinators, ...child.leadingCombinators],
child.components,
span,
lineBreak: lineBreak || child.lineBreak || forceLineBreak);
}
}
int get hashCode => listHash(leadingCombinators) ^ listHash(components);
bool operator ==(Object other) =>
other is ComplexSelector &&
listEquals(leadingCombinators, other.leadingCombinators) &&
listEquals(components, other.components);
}