mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-23 06:12:00 +01:00
Split out an Extender class from Extension
This gets rid of the weird subset of "one-off" extensions which didn't have target information available. Now instead, each method explicitly declares whether it takes/returns extensions (which do have target info) or extenders (which do not).
This commit is contained in:
parent
8fd3c1ba03
commit
3ead2e2bb5
@ -14,34 +14,19 @@ import '../utils.dart';
|
||||
/// The target of the extension is represented externally, in the map that
|
||||
/// contains this extender.
|
||||
class Extension {
|
||||
/// The selector in which the `@extend` appeared.
|
||||
final ComplexSelector extender;
|
||||
/// The extender (such as `A` in `A {@extend B}`).
|
||||
final Extender extender;
|
||||
|
||||
/// The selector that's being extended.
|
||||
///
|
||||
/// `null` for one-off extensions.
|
||||
final SimpleSelector? target;
|
||||
final SimpleSelector target;
|
||||
|
||||
/// The minimum specificity required for any selector generated from this
|
||||
/// extender.
|
||||
final int specificity;
|
||||
/// The media query context to which this extension is restricted, or `null`
|
||||
/// if it can apply within any context.
|
||||
final List<CssMediaQuery>? mediaContext;
|
||||
|
||||
/// Whether this extension is optional.
|
||||
final bool isOptional;
|
||||
|
||||
/// Whether this is a one-off extender representing a selector that was
|
||||
/// originally in the document, rather than one defined with `@extend`.
|
||||
final bool isOriginal;
|
||||
|
||||
/// The media query context to which this extend is restricted, or `null` if
|
||||
/// it can apply within any context.
|
||||
final List<CssMediaQuery>? mediaContext;
|
||||
|
||||
/// The span in which [extender] was defined.
|
||||
///
|
||||
/// `null` for one-off extensions.
|
||||
final FileSpan? extenderSpan;
|
||||
|
||||
/// The span for an `@extend` rule that defined this extension.
|
||||
///
|
||||
/// If any extend rule for this is extension is mandatory, this is guaranteed
|
||||
@ -51,43 +36,68 @@ class Extension {
|
||||
/// Creates a new extension.
|
||||
///
|
||||
/// If [specificity] isn't passed, it defaults to `extender.maxSpecificity`.
|
||||
Extension(ComplexSelector extender, this.target, this.extenderSpan, this.span,
|
||||
this.mediaContext,
|
||||
{int? specificity, bool optional = false})
|
||||
: extender = extender,
|
||||
specificity = specificity ?? extender.maxSpecificity,
|
||||
isOptional = optional,
|
||||
isOriginal = false;
|
||||
|
||||
/// Creates a one-off extension that's not intended to be modified over time.
|
||||
///
|
||||
/// If [specificity] isn't passed, it defaults to `extender.maxSpecificity`.
|
||||
Extension.oneOff(ComplexSelector extender,
|
||||
{int? specificity, this.isOriginal = false})
|
||||
: extender = extender,
|
||||
target = null,
|
||||
extenderSpan = null,
|
||||
specificity = specificity ?? extender.maxSpecificity,
|
||||
isOptional = true,
|
||||
mediaContext = null,
|
||||
span = null;
|
||||
|
||||
/// Asserts that the [mediaContext] for a selector is compatible with the
|
||||
/// query context for this extender.
|
||||
void assertCompatibleMediaContext(List<CssMediaQuery>? mediaContext) {
|
||||
if (this.mediaContext == null) return;
|
||||
if (mediaContext != null && listEquals(this.mediaContext, mediaContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw SassException(
|
||||
"You may not @extend selectors across media queries.", span);
|
||||
Extension(
|
||||
ComplexSelector extender, FileSpan? extenderSpan, this.target, this.span,
|
||||
{this.mediaContext, bool optional = false})
|
||||
: extender = Extender(extender, extenderSpan),
|
||||
isOptional = optional {
|
||||
this.extender._extension = this;
|
||||
}
|
||||
|
||||
Extension withExtender(ComplexSelector newExtender) =>
|
||||
Extension(newExtender, target, extenderSpan, span, mediaContext,
|
||||
specificity: specificity, optional: isOptional);
|
||||
Extension(newExtender, extender.span, target, span,
|
||||
mediaContext: mediaContext, optional: isOptional);
|
||||
|
||||
String toString() =>
|
||||
"$extender {@extend $target${isOptional ? ' !optional' : ''}}";
|
||||
}
|
||||
|
||||
/// A selector that's extending another selector, such as `A` in `A {@extend
|
||||
/// B}`.
|
||||
class Extender {
|
||||
/// The selector in which the `@extend` appeared.
|
||||
final ComplexSelector selector;
|
||||
|
||||
/// The minimum specificity required for any selector generated from this
|
||||
/// extender.
|
||||
final int specificity;
|
||||
|
||||
/// Whether this extender represents a selector that was originally in the
|
||||
/// document, rather than one defined with `@extend`.
|
||||
final bool isOriginal;
|
||||
|
||||
/// The extension that created this [Extender].
|
||||
///
|
||||
/// Not all [Extender]s are created by extensions. Some simply represent the
|
||||
/// original selectors that exist in the document.
|
||||
Extension? _extension;
|
||||
|
||||
/// The span in which this selector was defined.
|
||||
final FileSpan? span;
|
||||
|
||||
/// Creates a new extender.
|
||||
///
|
||||
/// If [specificity] isn't passed, it defaults to `extender.maxSpecificity`.
|
||||
Extender(this.selector, this.span, {int? specificity, bool original = false})
|
||||
: specificity = specificity ?? selector.maxSpecificity,
|
||||
isOriginal = original;
|
||||
|
||||
/// Asserts that the [mediaContext] for a selector is compatible with the
|
||||
/// query context for this extender.
|
||||
void assertCompatibleMediaContext(List<CssMediaQuery>? mediaContext) {
|
||||
var extension = _extension;
|
||||
if (extension == null) return;
|
||||
|
||||
var expectedMediaContext = extension.mediaContext;
|
||||
if (expectedMediaContext == null) return;
|
||||
if (mediaContext != null &&
|
||||
listEquals(expectedMediaContext, mediaContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw SassException(
|
||||
"You may not @extend selectors across media queries.", extension.span);
|
||||
}
|
||||
|
||||
String toString() => selector.toString();
|
||||
}
|
||||
|
@ -91,10 +91,6 @@ class ExtensionStore {
|
||||
/// A helper function for [extend] and [replace].
|
||||
static SelectorList _extendOrReplace(SelectorList selector,
|
||||
SelectorList source, SelectorList targets, ExtendMode mode) {
|
||||
var extenders = {
|
||||
for (var complex in source.components) complex: Extension.oneOff(complex)
|
||||
};
|
||||
|
||||
var compoundTargets = [
|
||||
for (var complex in targets.components)
|
||||
if (complex.components.length != 1)
|
||||
@ -105,14 +101,18 @@ class ExtensionStore {
|
||||
|
||||
var extensions = {
|
||||
for (var compound in compoundTargets)
|
||||
for (var simple in compound.components) simple: extenders
|
||||
for (var simple in compound.components)
|
||||
simple: {
|
||||
for (var complex in source.components)
|
||||
complex: Extension(complex, null, simple, null, optional: true)
|
||||
}
|
||||
};
|
||||
|
||||
var extender = ExtensionStore._mode(mode);
|
||||
if (!selector.isInvisible) {
|
||||
extender._originals.addAll(selector.components);
|
||||
}
|
||||
selector = extender._extendList(selector, extensions, null);
|
||||
selector = extender._extendList(selector, null /* listSpan */, extensions);
|
||||
|
||||
return selector;
|
||||
}
|
||||
@ -173,7 +173,7 @@ class ExtensionStore {
|
||||
/// The [mediaContext] is the media query context in which the selector was
|
||||
/// defined, or `null` if it was defined at the top level of the document.
|
||||
ModifiableCssValue<SelectorList> addSelector(
|
||||
SelectorList selector, FileSpan? span,
|
||||
SelectorList selector, FileSpan? selectorSpan,
|
||||
[List<CssMediaQuery>? mediaContext]) {
|
||||
var originalSelector = selector;
|
||||
if (!originalSelector.isInvisible) {
|
||||
@ -184,7 +184,8 @@ class ExtensionStore {
|
||||
|
||||
if (_extensions.isNotEmpty) {
|
||||
try {
|
||||
selector = _extendList(originalSelector, _extensions, mediaContext);
|
||||
selector = _extendList(
|
||||
originalSelector, selectorSpan, _extensions, mediaContext);
|
||||
} on SassException catch (error) {
|
||||
var span = error.span;
|
||||
if (span == null) rethrow;
|
||||
@ -196,7 +197,7 @@ class ExtensionStore {
|
||||
}
|
||||
}
|
||||
|
||||
var modifiableSelector = ModifiableCssValue(selector, span);
|
||||
var modifiableSelector = ModifiableCssValue(selector, selectorSpan);
|
||||
if (mediaContext != null) _mediaContexts[modifiableSelector] = mediaContext;
|
||||
_registerSelector(selector, modifiableSelector);
|
||||
|
||||
@ -242,25 +243,24 @@ class ExtensionStore {
|
||||
Map<ComplexSelector, Extension>? newExtensions;
|
||||
var sources = _extensions.putIfAbsent(target, () => {});
|
||||
for (var complex in extender.value.components) {
|
||||
var state = Extension(
|
||||
complex, target, extender.span, extend.span, mediaContext,
|
||||
optional: extend.isOptional);
|
||||
var extension = Extension(complex, extender.span, target, extend.span,
|
||||
mediaContext: mediaContext, optional: extend.isOptional);
|
||||
|
||||
var existingState = sources[complex];
|
||||
if (existingState != null) {
|
||||
var existingExtension = sources[complex];
|
||||
if (existingExtension != null) {
|
||||
// If there's already an extend from [extender] to [target], we don't need
|
||||
// to re-run the extension. We may need to mark the extension as
|
||||
// mandatory, though.
|
||||
sources[complex] = MergedExtension.merge(existingState, state);
|
||||
sources[complex] = MergedExtension.merge(existingExtension, extension);
|
||||
continue;
|
||||
}
|
||||
|
||||
sources[complex] = state;
|
||||
sources[complex] = extension;
|
||||
|
||||
for (var component in complex.components) {
|
||||
if (component is CompoundSelector) {
|
||||
for (var simple in component.components) {
|
||||
_extensionsByExtender.putIfAbsent(simple, () => []).add(state);
|
||||
_extensionsByExtender.putIfAbsent(simple, () => []).add(extension);
|
||||
// Only source specificity for the original selector is relevant.
|
||||
// Selectors generated by `@extend` don't get new specificity.
|
||||
_sourceSpecificity.putIfAbsent(
|
||||
@ -271,7 +271,7 @@ class ExtensionStore {
|
||||
|
||||
if (selectors != null || existingExtensions != null) {
|
||||
newExtensions ??= {};
|
||||
newExtensions[complex] = state;
|
||||
newExtensions[complex] = extension;
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,16 +312,15 @@ class ExtensionStore {
|
||||
Map<SimpleSelector, Map<ComplexSelector, Extension>>? additionalExtensions;
|
||||
|
||||
for (var extension in extensions.toList()) {
|
||||
var sources = _extensions[extension.target!]!;
|
||||
var sources = _extensions[extension.target]!;
|
||||
|
||||
// [_extendExistingSelectors] would have thrown already.
|
||||
List<ComplexSelector>? selectors;
|
||||
try {
|
||||
selectors = _extendComplex(
|
||||
extension.extender, newExtensions, extension.mediaContext);
|
||||
selectors = _extendComplex(extension.extender.selector,
|
||||
extension.extender.span, newExtensions, extension.mediaContext);
|
||||
if (selectors == null) continue;
|
||||
} on SassException catch (error) {
|
||||
var extenderSpan = extension.extenderSpan;
|
||||
var extenderSpan = extension.extender.span;
|
||||
if (extenderSpan == null) rethrow;
|
||||
|
||||
throw SassException(
|
||||
@ -330,7 +329,7 @@ class ExtensionStore {
|
||||
error.span);
|
||||
}
|
||||
|
||||
var containsExtension = selectors.first == extension.extender;
|
||||
var containsExtension = selectors.first == extension.extender.selector;
|
||||
var first = false;
|
||||
for (var complex in selectors) {
|
||||
// If the output contains the original complex selector, there's no
|
||||
@ -361,7 +360,7 @@ class ExtensionStore {
|
||||
if (newExtensions!.containsKey(extension.target)) {
|
||||
additionalExtensions ??= {};
|
||||
var additionalSources =
|
||||
additionalExtensions.putIfAbsent(extension.target!, () => {});
|
||||
additionalExtensions.putIfAbsent(extension.target, () => {});
|
||||
additionalSources[complex] = withExtender;
|
||||
}
|
||||
}
|
||||
@ -382,8 +381,8 @@ class ExtensionStore {
|
||||
for (var selector in selectors) {
|
||||
var oldValue = selector.value;
|
||||
try {
|
||||
selector.value = _extendList(
|
||||
selector.value, newExtensions, _mediaContexts[selector]);
|
||||
selector.value = _extendList(selector.value, selector.span,
|
||||
newExtensions, _mediaContexts[selector]);
|
||||
} on SassException catch (error) {
|
||||
if (selector.span == null) rethrow;
|
||||
|
||||
@ -482,16 +481,16 @@ class ExtensionStore {
|
||||
}
|
||||
|
||||
/// Extends [list] using [extensions].
|
||||
SelectorList _extendList(
|
||||
SelectorList list,
|
||||
SelectorList _extendList(SelectorList list, FileSpan? listSpan,
|
||||
Map<SimpleSelector, Map<ComplexSelector, Extension>?>? extensions,
|
||||
List<CssMediaQuery>? mediaQueryContext) {
|
||||
[List<CssMediaQuery>? mediaQueryContext]) {
|
||||
// This could be written more simply using [List.map], but we want to avoid
|
||||
// any allocations in the common case where no extends apply.
|
||||
List<ComplexSelector>? extended;
|
||||
for (var i = 0; i < list.components.length; i++) {
|
||||
var complex = list.components[i];
|
||||
var result = _extendComplex(complex, extensions, mediaQueryContext);
|
||||
var result =
|
||||
_extendComplex(complex, listSpan, extensions, mediaQueryContext);
|
||||
if (result == null) {
|
||||
if (extended != null) extended.add(complex);
|
||||
} else {
|
||||
@ -508,6 +507,7 @@ class ExtensionStore {
|
||||
/// [SelectorList].
|
||||
List<ComplexSelector>? _extendComplex(
|
||||
ComplexSelector complex,
|
||||
FileSpan? complexSpan,
|
||||
Map<SimpleSelector, Map<ComplexSelector, Extension>?>? extensions,
|
||||
List<CssMediaQuery>? mediaQueryContext) {
|
||||
// The complex selectors that each compound selector in [complex.components]
|
||||
@ -532,7 +532,8 @@ class ExtensionStore {
|
||||
for (var i = 0; i < complex.components.length; i++) {
|
||||
var component = complex.components[i];
|
||||
if (component is CompoundSelector) {
|
||||
var extended = _extendCompound(component, extensions, mediaQueryContext,
|
||||
var extended = _extendCompound(
|
||||
component, complexSpan, extensions, mediaQueryContext,
|
||||
inOriginal: isOriginal);
|
||||
if (extended == null) {
|
||||
extendedNotExpanded?.add([
|
||||
@ -583,6 +584,7 @@ class ExtensionStore {
|
||||
/// complex selector, meaning that [compound] should not be trimmed out.
|
||||
List<ComplexSelector>? _extendCompound(
|
||||
CompoundSelector compound,
|
||||
FileSpan? compoundSpan,
|
||||
Map<SimpleSelector, Map<ComplexSelector, Extension>?>? extensions,
|
||||
List<CssMediaQuery>? mediaQueryContext,
|
||||
{bool? inOriginal}) {
|
||||
@ -593,18 +595,20 @@ class ExtensionStore {
|
||||
: <SimpleSelector>{};
|
||||
|
||||
// The complex selectors produced from each component of [compound].
|
||||
List<List<Extension>>? options;
|
||||
List<List<Extender>>? options;
|
||||
for (var i = 0; i < compound.components.length; i++) {
|
||||
var simple = compound.components[i];
|
||||
var extended =
|
||||
_extendSimple(simple, extensions, mediaQueryContext, targetsUsed);
|
||||
var extended = _extendSimple(
|
||||
simple, compoundSpan, extensions, mediaQueryContext, targetsUsed);
|
||||
if (extended == null) {
|
||||
options?.add([_extensionForSimple(simple)]);
|
||||
options?.add([_extenderForSimple(simple, compoundSpan)]);
|
||||
} else {
|
||||
if (options == null) {
|
||||
options = [];
|
||||
if (i != 0) {
|
||||
options.add([_extensionForCompound(compound.components.take(i))]);
|
||||
options.add([
|
||||
_extenderForCompound(compound.components.take(i), compoundSpan)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -622,9 +626,9 @@ class ExtensionStore {
|
||||
// Optimize for the simple case of a single simple selector that doesn't
|
||||
// need any unification.
|
||||
if (options.length == 1) {
|
||||
return options.first.map((state) {
|
||||
state.assertCompatibleMediaContext(mediaQueryContext);
|
||||
return state.extender;
|
||||
return options.first.map((extender) {
|
||||
extender.assertCompatibleMediaContext(mediaQueryContext);
|
||||
return extender.selector;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@ -662,9 +666,9 @@ class ExtensionStore {
|
||||
first = false;
|
||||
complexes = [
|
||||
[
|
||||
CompoundSelector(path.expand((state) {
|
||||
assert(state.extender.components.length == 1);
|
||||
return (state.extender.components.last as CompoundSelector)
|
||||
CompoundSelector(path.expand((extender) {
|
||||
assert(extender.selector.components.length == 1);
|
||||
return (extender.selector.components.last as CompoundSelector)
|
||||
.components;
|
||||
}))
|
||||
]
|
||||
@ -672,14 +676,14 @@ class ExtensionStore {
|
||||
} else {
|
||||
var toUnify = QueueList<List<ComplexSelectorComponent>>();
|
||||
List<SimpleSelector>? originals;
|
||||
for (var state in path) {
|
||||
if (state.isOriginal) {
|
||||
for (var extender in path) {
|
||||
if (extender.isOriginal) {
|
||||
originals ??= [];
|
||||
originals.addAll(
|
||||
(state.extender.components.last as CompoundSelector)
|
||||
(extender.selector.components.last as CompoundSelector)
|
||||
.components);
|
||||
} else {
|
||||
toUnify.add(state.extender.components);
|
||||
toUnify.add(extender.selector.components);
|
||||
}
|
||||
}
|
||||
|
||||
@ -692,9 +696,9 @@ class ExtensionStore {
|
||||
}
|
||||
|
||||
var lineBreak = false;
|
||||
for (var state in path) {
|
||||
state.assertCompatibleMediaContext(mediaQueryContext);
|
||||
lineBreak = lineBreak || state.extender.lineBreak;
|
||||
for (var extender in path) {
|
||||
extender.assertCompatibleMediaContext(mediaQueryContext);
|
||||
lineBreak = lineBreak || extender.selector.lineBreak;
|
||||
}
|
||||
|
||||
return complexes
|
||||
@ -719,57 +723,65 @@ class ExtensionStore {
|
||||
isOriginal);
|
||||
}
|
||||
|
||||
Iterable<List<Extension>>? _extendSimple(
|
||||
Iterable<List<Extender>>? _extendSimple(
|
||||
SimpleSelector simple,
|
||||
FileSpan? simpleSpan,
|
||||
Map<SimpleSelector, Map<ComplexSelector, Extension>?>? extensions,
|
||||
List<CssMediaQuery>? mediaQueryContext,
|
||||
Set<SimpleSelector>? targetsUsed) {
|
||||
// Extends [simple] without extending the contents of any selector pseudos
|
||||
// it contains.
|
||||
List<Extension>? withoutPseudo(SimpleSelector simple) {
|
||||
var extenders = extensions![simple];
|
||||
if (extenders == null) return null;
|
||||
List<Extender>? withoutPseudo(SimpleSelector simple) {
|
||||
var extensionsForSimple = extensions![simple];
|
||||
if (extensionsForSimple == null) return null;
|
||||
targetsUsed?.add(simple);
|
||||
if (_mode == ExtendMode.replace) return extenders.values.toList();
|
||||
|
||||
return [_extensionForSimple(simple), ...extenders.values];
|
||||
return [
|
||||
if (_mode != ExtendMode.replace) _extenderForSimple(simple, simpleSpan),
|
||||
for (var extension in extensionsForSimple.values) extension.extender
|
||||
];
|
||||
}
|
||||
|
||||
if (simple is PseudoSelector && simple.selector != null) {
|
||||
var extended = _extendPseudo(simple, extensions, mediaQueryContext);
|
||||
var extended =
|
||||
_extendPseudo(simple, simpleSpan, extensions, mediaQueryContext);
|
||||
if (extended != null) {
|
||||
return extended.map(
|
||||
(pseudo) => withoutPseudo(pseudo) ?? [_extensionForSimple(pseudo)]);
|
||||
return extended.map((pseudo) =>
|
||||
withoutPseudo(pseudo) ?? [_extenderForSimple(pseudo, simpleSpan)]);
|
||||
}
|
||||
}
|
||||
|
||||
return withoutPseudo(simple).andThen((result) => [result]);
|
||||
}
|
||||
|
||||
/// Returns a one-off [Extension] whose extender is composed solely of a
|
||||
/// compound selector containing [simples].
|
||||
Extension _extensionForCompound(Iterable<SimpleSelector> simples) {
|
||||
/// Returns an [Extender] composed solely of a compound selector containing
|
||||
/// [simples].
|
||||
Extender _extenderForCompound(
|
||||
Iterable<SimpleSelector> simples, FileSpan? span) {
|
||||
var compound = CompoundSelector(simples);
|
||||
return Extension.oneOff(ComplexSelector([compound]),
|
||||
specificity: _sourceSpecificityFor(compound), isOriginal: true);
|
||||
return Extender(ComplexSelector([compound]), span,
|
||||
specificity: _sourceSpecificityFor(compound), original: true);
|
||||
}
|
||||
|
||||
/// Returns a one-off [Extension] whose extender is composed solely of
|
||||
/// [simple].
|
||||
Extension _extensionForSimple(SimpleSelector simple) => Extension.oneOff(
|
||||
ComplexSelector([
|
||||
CompoundSelector([simple])
|
||||
]),
|
||||
specificity: _sourceSpecificity[simple] ?? 0,
|
||||
isOriginal: true);
|
||||
/// Returns an [Extender] composed solely of [simple].
|
||||
Extender _extenderForSimple(SimpleSelector simple, FileSpan? span) =>
|
||||
Extender(
|
||||
ComplexSelector([
|
||||
CompoundSelector([simple])
|
||||
]),
|
||||
span,
|
||||
specificity: _sourceSpecificity[simple] ?? 0,
|
||||
original: true);
|
||||
|
||||
/// Extends [pseudo] using [extensions], and returns a list of resulting
|
||||
/// pseudo selectors.
|
||||
List<PseudoSelector>? _extendPseudo(
|
||||
PseudoSelector pseudo,
|
||||
FileSpan? pseudoSpan,
|
||||
Map<SimpleSelector, Map<ComplexSelector, Extension>?>? extensions,
|
||||
List<CssMediaQuery>? mediaQueryContext) {
|
||||
var extended = _extendList(pseudo.selector!, extensions, mediaQueryContext);
|
||||
var extended = _extendList(
|
||||
pseudo.selector!, pseudoSpan, extensions, mediaQueryContext);
|
||||
if (identical(extended, pseudo.selector)) return null;
|
||||
|
||||
// For `:not()`, we usually want to get rid of any complex selectors because
|
||||
|
@ -26,7 +26,8 @@ class MergedExtension extends Extension {
|
||||
/// Throws an [ArgumentError] if [left] and [right] don't have the same
|
||||
/// extender and target.
|
||||
static Extension merge(Extension left, Extension right) {
|
||||
if (left.extender != right.extender || left.target != right.target) {
|
||||
if (left.extender.selector != right.extender.selector ||
|
||||
left.target != right.target) {
|
||||
throw ArgumentError("$left and $right aren't the same extension.");
|
||||
}
|
||||
|
||||
@ -49,20 +50,23 @@ class MergedExtension extends Extension {
|
||||
}
|
||||
|
||||
MergedExtension._(this.left, this.right)
|
||||
: super(left.extender, left.target, left.extenderSpan, left.span,
|
||||
left.mediaContext ?? right.mediaContext,
|
||||
specificity: left.specificity, optional: true);
|
||||
: super(
|
||||
left.extender.selector, left.extender.span, left.target, left.span,
|
||||
mediaContext: left.mediaContext ?? right.mediaContext,
|
||||
optional: true);
|
||||
|
||||
/// Returns all leaf-node [Extension]s in the tree or [MergedExtension]s.
|
||||
/// Returns all leaf-node [Extension]s in the tree of [MergedExtension]s.
|
||||
Iterable<Extension> unmerge() sync* {
|
||||
var left = this.left;
|
||||
if (left is MergedExtension) {
|
||||
yield* (left as MergedExtension).unmerge();
|
||||
yield* left.unmerge();
|
||||
} else {
|
||||
yield left;
|
||||
}
|
||||
|
||||
var right = this.right;
|
||||
if (right is MergedExtension) {
|
||||
yield* (right as MergedExtension).unmerge();
|
||||
yield* right.unmerge();
|
||||
} else {
|
||||
yield right;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user