2016-07-16 02:27:14 +02:00
|
|
|
// 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.
|
|
|
|
|
2016-08-05 02:25:57 +02:00
|
|
|
import 'dart:collection';
|
2016-08-13 00:24:27 +02:00
|
|
|
import 'dart:math' as math;
|
2016-08-05 02:25:57 +02:00
|
|
|
|
2016-08-13 00:24:27 +02:00
|
|
|
import 'package:collection/collection.dart';
|
2016-08-05 02:25:57 +02:00
|
|
|
import 'package:source_span/source_span.dart';
|
|
|
|
|
2016-08-28 01:12:17 +02:00
|
|
|
import '../ast/css.dart';
|
2016-08-05 02:04:37 +02:00
|
|
|
import '../ast/selector.dart';
|
2016-09-03 16:09:15 +02:00
|
|
|
import '../ast/sass.dart';
|
2016-09-03 16:42:29 +02:00
|
|
|
import '../exception.dart';
|
2016-08-05 02:04:37 +02:00
|
|
|
import '../utils.dart';
|
2016-09-03 12:29:01 +02:00
|
|
|
import 'source.dart';
|
2016-08-05 02:04:37 +02:00
|
|
|
import 'functions.dart';
|
2016-07-16 02:27:14 +02:00
|
|
|
|
|
|
|
class Extender {
|
|
|
|
/// A map from all simple selectors in the stylesheet to the rules that
|
|
|
|
/// contain them.
|
|
|
|
///
|
|
|
|
/// This is used to find which rules an `@extend` applies to.
|
|
|
|
final _selectors = <SimpleSelector, Set<CssStyleRule>>{};
|
|
|
|
|
2016-09-03 12:29:01 +02:00
|
|
|
final _extensions = <SimpleSelector, Set<ExtendSource>>{};
|
2016-07-16 02:27:14 +02:00
|
|
|
|
2016-08-05 00:14:09 +02:00
|
|
|
final _sources = new Expando<ComplexSelector>();
|
2016-07-16 02:27:14 +02:00
|
|
|
|
2016-08-29 00:04:48 +02:00
|
|
|
CssStyleRule addSelector(
|
|
|
|
CssValue<SelectorList> selectorValue, FileSpan span) {
|
2016-08-13 00:24:27 +02:00
|
|
|
var selector = selectorValue.value;
|
2016-08-13 00:33:36 +02:00
|
|
|
for (var complex in selector.components) {
|
2016-08-05 00:39:53 +02:00
|
|
|
for (var component in complex.components) {
|
|
|
|
if (component is CompoundSelector) {
|
|
|
|
for (var simple in component.components) {
|
2016-08-05 02:25:57 +02:00
|
|
|
_sources[simple] = complex;
|
2016-08-05 00:39:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_extensions.isNotEmpty) selector = _extendList(selector, _extensions);
|
2016-08-28 23:53:20 +02:00
|
|
|
var rule = new CssStyleRule(selectorValue, span);
|
2016-07-16 02:27:14 +02:00
|
|
|
|
|
|
|
for (var complex in selector.components) {
|
2016-07-29 02:33:09 +02:00
|
|
|
for (var component in complex.components) {
|
|
|
|
if (component is CompoundSelector) {
|
|
|
|
for (var simple in component.components) {
|
2016-07-16 02:27:14 +02:00
|
|
|
_selectors.putIfAbsent(simple, () => new Set()).add(rule);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rule;
|
|
|
|
}
|
|
|
|
|
2016-09-03 12:29:01 +02:00
|
|
|
void addExtension(
|
2016-09-07 16:10:37 +02:00
|
|
|
SelectorList sourceList, SimpleSelector target, ExtendRule extend) {
|
2016-09-03 16:09:15 +02:00
|
|
|
var source = new ExtendSource(sourceList, extend.span);
|
|
|
|
source.isUsed = extend.isOptional;
|
2016-09-03 12:29:01 +02:00
|
|
|
_extensions.putIfAbsent(target, () => new Set()).add(source);
|
2016-08-05 00:39:53 +02:00
|
|
|
|
|
|
|
var rules = _selectors[target];
|
|
|
|
if (rules == null) return;
|
|
|
|
|
2016-09-03 12:29:01 +02:00
|
|
|
var extensions = {target: new Set()..add(source)};
|
2016-08-05 00:39:53 +02:00
|
|
|
for (var rule in rules) {
|
|
|
|
var list = rule.selector.value;
|
2016-08-05 02:25:57 +02:00
|
|
|
rule.selector.value = _extendList(list, extensions);
|
2016-08-05 00:39:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-03 12:29:01 +02:00
|
|
|
void finalize() {
|
|
|
|
for (var sources in _extensions.values) {
|
|
|
|
for (var source in sources) {
|
|
|
|
if (source.isUsed) continue;
|
2016-09-03 16:42:29 +02:00
|
|
|
throw new SassException(
|
|
|
|
'The target selector was not found.\n'
|
|
|
|
'Use "@extend %foo !optional" to avoid this error.',
|
|
|
|
source.span);
|
2016-09-03 12:29:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-29 00:04:48 +02:00
|
|
|
SelectorList _extendList(
|
2016-09-03 12:29:01 +02:00
|
|
|
SelectorList list, Map<SimpleSelector, Set<ExtendSource>> extensions) {
|
2016-07-16 02:27:14 +02:00
|
|
|
// This could be written more simply using [List.map], but we want to avoid
|
|
|
|
// any allocations in the common case where no extends apply.
|
|
|
|
var changed = false;
|
2016-07-29 02:33:09 +02:00
|
|
|
List<ComplexSelector> newList;
|
|
|
|
for (var i = 0; i < list.components.length; i++) {
|
|
|
|
var complex = list.components[i];
|
2016-08-05 00:39:53 +02:00
|
|
|
var extended = _extendComplex(complex, extensions);
|
2016-07-16 02:27:14 +02:00
|
|
|
if (extended == null) {
|
2016-07-29 02:33:09 +02:00
|
|
|
if (changed) newList.add(complex);
|
2016-07-16 02:27:14 +02:00
|
|
|
} else {
|
2016-07-29 02:33:09 +02:00
|
|
|
if (!changed) newList = list.components.take(i).toList();
|
2016-07-16 02:27:14 +02:00
|
|
|
changed = true;
|
2016-07-29 02:33:09 +02:00
|
|
|
newList.addAll(extended);
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|
|
|
|
}
|
2016-07-29 02:33:09 +02:00
|
|
|
if (!changed) return list;
|
2016-07-16 02:27:14 +02:00
|
|
|
|
|
|
|
// TODO: compute new line breaks
|
2016-07-29 02:33:09 +02:00
|
|
|
return new SelectorList(newList.where((complex) => complex != null));
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|
|
|
|
|
2016-08-13 02:51:17 +02:00
|
|
|
Iterable<ComplexSelector> _extendComplex(ComplexSelector complex,
|
2016-09-03 12:29:01 +02:00
|
|
|
Map<SimpleSelector, Set<ExtendSource>> extensions) {
|
2016-07-16 02:27:14 +02:00
|
|
|
// This could be written more simply using [List.map], but we want to avoid
|
|
|
|
// any allocations in the common case where no extends apply.
|
|
|
|
var changed = false;
|
|
|
|
List<List<List<ComplexSelectorComponent>>> extendedNotExpanded;
|
2016-07-29 02:33:09 +02:00
|
|
|
for (var i = 0; i < complex.components.length; i++) {
|
|
|
|
var component = complex.components[i];
|
2016-07-16 02:27:14 +02:00
|
|
|
if (component is CompoundSelector) {
|
2016-08-05 00:39:53 +02:00
|
|
|
var extended = _extendCompound(component, extensions);
|
2016-07-16 02:27:14 +02:00
|
|
|
// TODO: follow the first law of extend (https://github.com/sass/sass/blob/7774aa3/lib/sass/selector/sequence.rb#L114-L118)
|
|
|
|
if (extended == null) {
|
2016-08-29 00:04:48 +02:00
|
|
|
if (changed)
|
|
|
|
extendedNotExpanded.add([
|
|
|
|
[component]
|
|
|
|
]);
|
2016-07-16 02:27:14 +02:00
|
|
|
} else {
|
|
|
|
if (!changed) {
|
2016-08-13 02:51:17 +02:00
|
|
|
extendedNotExpanded = complex.components
|
|
|
|
.take(i)
|
2016-08-29 00:04:48 +02:00
|
|
|
.map((component) => [
|
|
|
|
[component]
|
|
|
|
])
|
2016-08-13 02:51:17 +02:00
|
|
|
.toList();
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|
|
|
|
changed = true;
|
|
|
|
extendedNotExpanded.add(extended);
|
|
|
|
}
|
|
|
|
} else {
|
2016-08-29 00:04:48 +02:00
|
|
|
if (changed)
|
|
|
|
extendedNotExpanded.add([
|
|
|
|
[component]
|
|
|
|
]);
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!changed) return null;
|
|
|
|
|
|
|
|
// TODO: preserve line breaks
|
2016-08-29 00:04:48 +02:00
|
|
|
var weaves =
|
|
|
|
_paths(extendedNotExpanded).map((path) => _weave(path)).toList();
|
2016-07-29 02:33:09 +02:00
|
|
|
return _trim(weaves).map((complex) => new ComplexSelector(complex));
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
List<List<ComplexSelectorComponent>> _extendCompound(
|
2016-08-05 00:39:53 +02:00
|
|
|
CompoundSelector compound,
|
2016-09-03 12:29:01 +02:00
|
|
|
Map<SimpleSelector, Set<ExtendSource>> extensions) {
|
2016-07-16 02:27:14 +02:00
|
|
|
var changed = false;
|
|
|
|
List<List<ComplexSelectorComponent>> extended;
|
2016-07-29 02:33:09 +02:00
|
|
|
for (var i = 0; i < compound.components.length; i++) {
|
|
|
|
var simple = compound.components[i];
|
2016-07-16 02:27:14 +02:00
|
|
|
|
2016-09-08 03:31:52 +02:00
|
|
|
// TODO: handle extending into pseudo selectors
|
2016-07-16 02:27:14 +02:00
|
|
|
|
2016-09-03 12:29:01 +02:00
|
|
|
var sources = extensions[simple];
|
|
|
|
if (sources == null) continue;
|
2016-07-16 02:27:14 +02:00
|
|
|
|
2016-08-29 00:04:48 +02:00
|
|
|
var compoundWithoutSimple = compound.components.toList()..removeAt(i);
|
2016-09-03 12:29:01 +02:00
|
|
|
for (var source in sources) {
|
|
|
|
for (var complex in source.extender.components) {
|
2016-08-05 02:25:57 +02:00
|
|
|
var extenderBase = complex.components.last as CompoundSelector;
|
2016-08-14 22:57:27 +02:00
|
|
|
var unified = compoundWithoutSimple.isEmpty
|
|
|
|
? extenderBase
|
|
|
|
: _unifyCompound(extenderBase.components, compoundWithoutSimple);
|
2016-07-16 02:27:14 +02:00
|
|
|
if (unified == null) continue;
|
|
|
|
|
2016-09-03 12:29:01 +02:00
|
|
|
if (!changed) {
|
2016-08-29 00:04:48 +02:00
|
|
|
extended = [
|
|
|
|
[compound]
|
|
|
|
];
|
2016-09-03 12:29:01 +02:00
|
|
|
}
|
2016-07-16 02:27:14 +02:00
|
|
|
changed = true;
|
2016-08-05 02:25:57 +02:00
|
|
|
extended.add(complex.components
|
|
|
|
.take(complex.components.length - 1)
|
2016-08-29 00:04:48 +02:00
|
|
|
.toList()..add(unified));
|
2016-09-03 12:29:01 +02:00
|
|
|
source.isUsed = true;
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return extended;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<List<ComplexSelectorComponent>> _weave(
|
2016-07-29 02:33:09 +02:00
|
|
|
List<List<ComplexSelectorComponent>> complexes) {
|
|
|
|
var prefixes = [complexes.first];
|
2016-07-16 02:27:14 +02:00
|
|
|
|
2016-07-29 02:33:09 +02:00
|
|
|
for (var complex in complexes.skip(1)) {
|
|
|
|
if (complex.isEmpty) continue;
|
2016-07-16 02:27:14 +02:00
|
|
|
|
2016-07-29 02:33:09 +02:00
|
|
|
var target = complex.last;
|
|
|
|
if (complex.length == 1) {
|
2016-07-16 02:27:14 +02:00
|
|
|
for (var prefix in prefixes) {
|
|
|
|
prefix.add(target);
|
|
|
|
}
|
2016-08-13 02:34:18 +02:00
|
|
|
continue;
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|
|
|
|
|
2016-07-29 02:33:09 +02:00
|
|
|
var parents = complex.take(complex.length - 1).toList();
|
2016-08-13 02:34:18 +02:00
|
|
|
var newPrefixes = <List<ComplexSelectorComponent>>[];
|
|
|
|
for (var prefix in prefixes) {
|
2016-07-16 02:27:14 +02:00
|
|
|
var parentPrefixes = _weaveParents(prefix, parents);
|
2016-08-13 02:34:18 +02:00
|
|
|
if (parentPrefixes == null) continue;
|
|
|
|
|
|
|
|
for (var parentPrefix in parentPrefixes) {
|
|
|
|
newPrefixes.add(parentPrefix..add(target));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
prefixes = newPrefixes;
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return prefixes;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<List<ComplexSelectorComponent>> _weaveParents(
|
|
|
|
List<ComplexSelectorComponent> parents1,
|
|
|
|
List<ComplexSelectorComponent> parents2) {
|
2016-08-13 02:34:18 +02:00
|
|
|
var queue1 = new Queue<ComplexSelectorComponent>.from(parents1);
|
|
|
|
var queue2 = new Queue<ComplexSelectorComponent>.from(parents2);
|
2016-07-16 02:27:14 +02:00
|
|
|
|
|
|
|
var initialCombinator = _mergeInitialCombinators(queue1, queue2);
|
|
|
|
if (initialCombinator == null) return null;
|
|
|
|
var finalCombinator = _mergeFinalCombinators(queue1, queue2);
|
|
|
|
if (finalCombinator == null) return null;
|
|
|
|
|
|
|
|
// Make sure there's at most one `:root` in the output.
|
2016-08-13 02:34:18 +02:00
|
|
|
var root1 = _firstIfRoot(queue1);
|
|
|
|
var root2 = _firstIfRoot(queue2);
|
2016-07-16 02:27:14 +02:00
|
|
|
if (root1 != null && root2 != null) {
|
2016-08-13 02:34:18 +02:00
|
|
|
var root = _unifyCompound(root1.components, root2.components);
|
2016-07-16 02:27:14 +02:00
|
|
|
if (root == null) return null;
|
|
|
|
queue1.addFirst(root);
|
|
|
|
queue2.addFirst(root);
|
|
|
|
} else if (root1 != null) {
|
|
|
|
queue2.addFirst(root1);
|
|
|
|
} else if (root2 != null) {
|
|
|
|
queue1.addFirst(root2);
|
|
|
|
}
|
|
|
|
|
|
|
|
var groups1 = _groupSelectors(queue1);
|
|
|
|
var groups2 = _groupSelectors(queue2);
|
2016-08-13 02:34:18 +02:00
|
|
|
var lcs = longestCommonSubsequence/*<List<ComplexSelectorComponent>>*/(
|
|
|
|
groups1, groups2, select: (group1, group2) {
|
2016-07-16 02:27:14 +02:00
|
|
|
if (listEquals(group1, group2)) return group1;
|
|
|
|
if (group1.first is! CompoundSelector ||
|
|
|
|
group2.first is! CompoundSelector) {
|
|
|
|
return null;
|
|
|
|
}
|
2016-08-13 02:03:09 +02:00
|
|
|
if (complexIsParentSuperselector(group1, group2)) return group2;
|
|
|
|
if (complexIsParentSuperselector(group2, group1)) return group1;
|
2016-07-16 02:27:14 +02:00
|
|
|
if (!_mustUnify(group1, group2)) return null;
|
|
|
|
|
|
|
|
var unified = _unifyComplex(group1, group2);
|
|
|
|
if (unified == null) return null;
|
2016-07-29 02:40:52 +02:00
|
|
|
if (unified.length > 1) return null;
|
|
|
|
return unified.first;
|
2016-07-16 02:27:14 +02:00
|
|
|
});
|
|
|
|
|
2016-08-29 00:04:48 +02:00
|
|
|
var choices = [
|
|
|
|
<List<ComplexSelectorComponent>>[initialCombinator]
|
|
|
|
];
|
2016-07-16 02:27:14 +02:00
|
|
|
for (var group in lcs) {
|
2016-08-13 02:34:18 +02:00
|
|
|
choices.add(_chunks/*<List<ComplexSelectorComponent>>*/(groups1, groups2,
|
2016-08-13 02:03:09 +02:00
|
|
|
(sequence) => complexIsParentSuperselector(sequence.first, group))
|
2016-08-13 00:24:27 +02:00
|
|
|
.map((chunk) => chunk.expand((group) => group)));
|
2016-08-13 02:34:18 +02:00
|
|
|
choices.add([group]);
|
2016-07-16 02:27:14 +02:00
|
|
|
groups1.removeFirst();
|
|
|
|
groups2.removeFirst();
|
|
|
|
}
|
2016-08-13 00:24:27 +02:00
|
|
|
choices.add(_chunks(groups1, groups2, (sequence) => sequence.isEmpty)
|
|
|
|
.map((chunk) => chunk.expand((group) => group)));
|
2016-07-16 02:27:14 +02:00
|
|
|
choices.addAll(finalCombinator);
|
|
|
|
|
2016-08-13 00:24:27 +02:00
|
|
|
return _paths(choices.where((choice) => choice.isNotEmpty))
|
2016-07-16 02:27:14 +02:00
|
|
|
.map((path) => path.expand((group) => group));
|
|
|
|
}
|
|
|
|
|
2016-08-13 02:34:18 +02:00
|
|
|
CompoundSelector _firstIfRoot(Queue<ComplexSelectorComponent> queue) {
|
|
|
|
var first = queue.first as CompoundSelector;
|
|
|
|
if (!_hasRoot(first)) return null;
|
|
|
|
|
|
|
|
queue.removeFirst();
|
|
|
|
return first;
|
|
|
|
}
|
|
|
|
|
2016-07-16 02:27:14 +02:00
|
|
|
List<Combinator> _mergeInitialCombinators(
|
|
|
|
Queue<ComplexSelectorComponent> components1,
|
|
|
|
Queue<ComplexSelectorComponent> components2) {
|
|
|
|
var combinators1 = <Combinator>[];
|
|
|
|
while (components1.first is Combinator) {
|
2016-08-13 00:24:27 +02:00
|
|
|
combinators1.add(components1.removeFirst() as Combinator);
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var combinators2 = <Combinator>[];
|
|
|
|
while (components2.first is Combinator) {
|
2016-08-13 00:24:27 +02:00
|
|
|
combinators2.add(components2.removeFirst() as Combinator);
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// If neither sequence of combinators is a subsequence of the other, they
|
|
|
|
// cannot be merged successfully.
|
2016-08-13 00:24:27 +02:00
|
|
|
var lcs = longestCommonSubsequence(combinators1, combinators2);
|
2016-07-16 02:27:14 +02:00
|
|
|
if (listEquals(lcs, combinators1)) return combinators2;
|
|
|
|
if (listEquals(lcs, combinators2)) return combinators1;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<List<List<ComplexSelectorComponent>>> _mergeFinalCombinators(
|
|
|
|
Queue<ComplexSelectorComponent> components1,
|
|
|
|
Queue<ComplexSelectorComponent> components2,
|
|
|
|
[QueueList<List<List<ComplexSelectorComponent>>> result]) {
|
|
|
|
result ??= new QueueList();
|
|
|
|
if ((components1.isEmpty || components1.last is! Combinator) &&
|
|
|
|
(components2.isEmpty || components2.last is! Combinator)) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
var combinators1 = <Combinator>[];
|
|
|
|
while (components1.last is Combinator) {
|
|
|
|
combinators1.add(components1.last as Combinator);
|
|
|
|
}
|
|
|
|
|
|
|
|
var combinators2 = <Combinator>[];
|
|
|
|
while (components2.last is Combinator) {
|
|
|
|
combinators2.add(components2.last as Combinator);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (combinators1.length > 1 || combinators2.length > 1) {
|
|
|
|
// If there are multiple combinators, something hacky's going on. If one
|
|
|
|
// is a supersequence of the other, use that, otherwise give up.
|
2016-08-13 00:24:27 +02:00
|
|
|
var lcs = longestCommonSubsequence(combinators1, combinators2);
|
2016-07-16 02:27:14 +02:00
|
|
|
if (listEquals(lcs, combinators1)) {
|
2016-08-13 00:24:27 +02:00
|
|
|
result.addAll([new List.from(combinators2.reversed)]);
|
2016-07-16 02:27:14 +02:00
|
|
|
} else if (listEquals(lcs, combinators2)) {
|
2016-08-13 00:24:27 +02:00
|
|
|
result.addAll([new List.from(combinators1.reversed)]);
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This code looks complicated, but it's actually just a bunch of special
|
|
|
|
// cases for interactions between different combinators.
|
|
|
|
var combinator1 = combinators1.isEmpty ? null : combinators1.first;
|
|
|
|
var combinator2 = combinators2.isEmpty ? null : combinators2.first;
|
|
|
|
if (combinator1 != null && combinator2 != null) {
|
|
|
|
var compound1 = components1.removeLast() as CompoundSelector;
|
|
|
|
var compound2 = components2.removeLast() as CompoundSelector;
|
|
|
|
|
|
|
|
if (combinator1 == Combinator.followingSibling &&
|
|
|
|
combinator2 == Combinator.followingSibling) {
|
2016-08-05 02:04:37 +02:00
|
|
|
if (compound1.isSuperselector(compound2)) {
|
2016-08-29 00:04:48 +02:00
|
|
|
result.addFirst([
|
|
|
|
[compound2, Combinator.followingSibling]
|
|
|
|
]);
|
2016-08-05 02:04:37 +02:00
|
|
|
} else if (compound2.isSuperselector(compound1)) {
|
2016-08-29 00:04:48 +02:00
|
|
|
result.addFirst([
|
|
|
|
[compound1, Combinator.followingSibling]
|
|
|
|
]);
|
2016-07-16 02:27:14 +02:00
|
|
|
} else {
|
|
|
|
var choices = [
|
|
|
|
[
|
2016-08-29 00:04:48 +02:00
|
|
|
compound1,
|
|
|
|
Combinator.followingSibling,
|
|
|
|
compound2,
|
|
|
|
Combinator.followingSibling
|
2016-07-16 02:27:14 +02:00
|
|
|
],
|
|
|
|
[
|
2016-08-29 00:04:48 +02:00
|
|
|
compound2,
|
|
|
|
Combinator.followingSibling,
|
|
|
|
compound1,
|
|
|
|
Combinator.followingSibling
|
2016-07-16 02:27:14 +02:00
|
|
|
]
|
|
|
|
];
|
|
|
|
|
2016-08-29 00:04:48 +02:00
|
|
|
var unified =
|
|
|
|
_unifyCompound(compound1.components, compound2.components);
|
2016-07-16 02:27:14 +02:00
|
|
|
if (unified != null) {
|
|
|
|
choices.add([unified, Combinator.followingSibling]);
|
|
|
|
}
|
|
|
|
|
|
|
|
result.addFirst(choices);
|
|
|
|
}
|
2016-08-29 00:04:48 +02:00
|
|
|
} else if ((combinator1 == Combinator.followingSibling &&
|
|
|
|
combinator2 == Combinator.nextSibling) ||
|
2016-07-16 02:27:14 +02:00
|
|
|
(combinator1 == Combinator.nextSibling &&
|
2016-08-29 00:04:48 +02:00
|
|
|
combinator2 == Combinator.followingSibling)) {
|
2016-07-16 02:27:14 +02:00
|
|
|
var followingSiblingSelector =
|
|
|
|
combinator1 == Combinator.followingSibling ? compound1 : compound2;
|
|
|
|
var nextSiblingSelector =
|
|
|
|
combinator1 == Combinator.followingSibling ? compound2 : compound1;
|
|
|
|
|
2016-08-05 02:04:37 +02:00
|
|
|
if (followingSiblingSelector.isSuperselector(nextSiblingSelector)) {
|
2016-08-29 00:04:48 +02:00
|
|
|
result.addFirst([
|
|
|
|
[nextSiblingSelector, Combinator.nextSibling]
|
|
|
|
]);
|
2016-07-16 02:27:14 +02:00
|
|
|
} else {
|
|
|
|
var choices = [
|
|
|
|
[
|
2016-08-29 00:04:48 +02:00
|
|
|
followingSiblingSelector,
|
|
|
|
Combinator.followingSibling,
|
|
|
|
nextSiblingSelector,
|
|
|
|
Combinator.nextSibling
|
2016-07-16 02:27:14 +02:00
|
|
|
]
|
|
|
|
];
|
|
|
|
|
2016-08-29 00:04:48 +02:00
|
|
|
var unified =
|
|
|
|
_unifyCompound(compound1.components, compound2.components);
|
2016-07-16 02:27:14 +02:00
|
|
|
if (unified != null) choices.add([unified, Combinator.nextSibling]);
|
|
|
|
result.addFirst(choices);
|
|
|
|
}
|
|
|
|
} else if (combinator1 == Combinator.child &&
|
|
|
|
(combinator2 == Combinator.nextSibling ||
|
2016-08-29 00:04:48 +02:00
|
|
|
combinator2 == Combinator.followingSibling)) {
|
|
|
|
result.addFirst([
|
|
|
|
[compound2, combinator2]
|
|
|
|
]);
|
2016-07-16 02:27:14 +02:00
|
|
|
components1..add(compound1)..add(Combinator.child);
|
|
|
|
} else if (combinator2 == Combinator.child &&
|
|
|
|
(combinator1 == Combinator.nextSibling ||
|
2016-08-29 00:04:48 +02:00
|
|
|
combinator1 == Combinator.followingSibling)) {
|
|
|
|
result.addFirst([
|
|
|
|
[compound2, combinator2]
|
|
|
|
]);
|
2016-07-16 02:27:14 +02:00
|
|
|
components1..add(compound1)..add(Combinator.child);
|
|
|
|
} else if (combinator1 == combinator2) {
|
2016-08-29 00:04:48 +02:00
|
|
|
var unified =
|
|
|
|
_unifyCompound(compound1.components, compound2.components);
|
2016-07-16 02:27:14 +02:00
|
|
|
if (unified == null) return null;
|
2016-08-29 00:04:48 +02:00
|
|
|
result.addFirst([
|
|
|
|
[unified, combinator1]
|
|
|
|
]);
|
2016-07-16 02:27:14 +02:00
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return _mergeFinalCombinators(components1, components2, result);
|
|
|
|
} else if (combinator1 != null) {
|
|
|
|
if (combinator1 == Combinator.child &&
|
|
|
|
components2.isNotEmpty &&
|
2016-08-29 00:04:48 +02:00
|
|
|
(components2.last as CompoundSelector)
|
|
|
|
.isSuperselector(components1.last)) {
|
2016-07-16 02:27:14 +02:00
|
|
|
components2.removeLast();
|
|
|
|
}
|
2016-08-29 00:04:48 +02:00
|
|
|
result.addFirst([
|
|
|
|
[components1.removeLast(), combinator1]
|
|
|
|
]);
|
2016-07-16 02:27:14 +02:00
|
|
|
return _mergeFinalCombinators(components1, components2, result);
|
|
|
|
} else {
|
|
|
|
assert(combinator1 != null);
|
|
|
|
if (combinator2 == Combinator.child &&
|
|
|
|
components1.isNotEmpty &&
|
2016-08-29 00:04:48 +02:00
|
|
|
(components1.last as CompoundSelector)
|
|
|
|
.isSuperselector(components2.last)) {
|
2016-07-16 02:27:14 +02:00
|
|
|
components1.removeLast();
|
|
|
|
}
|
2016-08-29 00:04:48 +02:00
|
|
|
result.addFirst([
|
|
|
|
[components2.removeLast(), combinator2]
|
|
|
|
]);
|
2016-07-16 02:27:14 +02:00
|
|
|
return _mergeFinalCombinators(components1, components2, result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-13 02:12:16 +02:00
|
|
|
bool _mustUnify(List<ComplexSelectorComponent> complex1,
|
|
|
|
List<ComplexSelectorComponent> complex2) {
|
|
|
|
var uniqueSelectors = new Set<SimpleSelector>();
|
|
|
|
for (var component in complex1) {
|
|
|
|
if (component is CompoundSelector) {
|
|
|
|
uniqueSelectors.addAll(component.components.where(_isUnique));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (uniqueSelectors.isEmpty) return false;
|
|
|
|
|
|
|
|
return complex2.any((component) =>
|
|
|
|
component is CompoundSelector &&
|
2016-08-29 00:04:48 +02:00
|
|
|
component.components.any(
|
|
|
|
(simple) => _isUnique(simple) && uniqueSelectors.contains(simple)));
|
2016-08-13 02:12:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool _isUnique(SimpleSelector simple) =>
|
|
|
|
simple is IDSelector ||
|
|
|
|
(simple is PseudoSelector && simple.type == PseudoType.element);
|
|
|
|
|
2016-08-29 00:04:48 +02:00
|
|
|
List<List/*<T>*/ > _chunks/*<T>*/(
|
|
|
|
Queue/*<T>*/ queue1, Queue/*<T>*/ queue2, bool done(Queue/*<T>*/ queue)) {
|
|
|
|
var chunk1 = /*<T>*/ [];
|
2016-07-29 00:45:29 +02:00
|
|
|
while (!done(queue1)) {
|
|
|
|
chunk1.add(queue1.removeFirst());
|
|
|
|
}
|
|
|
|
|
2016-08-29 00:04:48 +02:00
|
|
|
var chunk2 = /*<T>*/ [];
|
2016-07-29 00:45:29 +02:00
|
|
|
while (!done(queue2)) {
|
|
|
|
chunk2.add(queue2.removeFirst());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chunk1.isEmpty && chunk2.isEmpty) return [];
|
|
|
|
if (chunk1.isEmpty) return [chunk2];
|
|
|
|
if (chunk2.isEmpty) return [chunk1];
|
2016-08-13 00:24:27 +02:00
|
|
|
return [chunk1.toList()..addAll(chunk2), chunk2..addAll(chunk1)];
|
2016-07-29 00:45:29 +02:00
|
|
|
}
|
|
|
|
|
2016-08-29 00:04:48 +02:00
|
|
|
List<List/*<T>*/ > _paths/*<T>*/(Iterable<List/*<T>*/ > choices) =>
|
|
|
|
choices.fold(
|
|
|
|
[[]],
|
|
|
|
(paths, choice) => choice
|
|
|
|
.expand(
|
|
|
|
(option) => paths.map((path) => path.toList()..add(option)))
|
|
|
|
.toList());
|
2016-08-13 02:01:21 +02:00
|
|
|
|
2016-08-13 00:24:27 +02:00
|
|
|
QueueList<List<ComplexSelectorComponent>> _groupSelectors(
|
2016-07-29 02:33:09 +02:00
|
|
|
Iterable<ComplexSelectorComponent> complex) {
|
2016-08-13 00:24:27 +02:00
|
|
|
var groups = new QueueList<List<ComplexSelectorComponent>>();
|
2016-07-29 02:33:09 +02:00
|
|
|
var iterator = complex.iterator;
|
2016-07-29 00:45:29 +02:00
|
|
|
while (iterator.moveNext()) {
|
|
|
|
var group = <ComplexSelectorComponent>[];
|
|
|
|
do {
|
|
|
|
group.add(iterator.current);
|
|
|
|
} while ((iterator.current is Combinator || group.last is Combinator) &&
|
|
|
|
iterator.moveNext());
|
|
|
|
groups.add(group);
|
|
|
|
}
|
|
|
|
return groups;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<List<ComplexSelectorComponent>> _trim(
|
2016-07-29 02:33:09 +02:00
|
|
|
List<List<List<ComplexSelectorComponent>>> lists) {
|
2016-07-29 00:45:29 +02:00
|
|
|
// Avoid truly horrific quadratic behavior.
|
|
|
|
//
|
|
|
|
// TODO(nweiz): I think there may be a way to get perfect trimming without
|
|
|
|
// going quadratic by building some sort of trie-like data structure that
|
|
|
|
// can be used to look up superselectors.
|
2016-08-13 00:24:27 +02:00
|
|
|
if (lists.length > 100) return lists.expand((selectors) => selectors);
|
2016-07-29 00:45:29 +02:00
|
|
|
|
|
|
|
// This is n² on the sequences, but only comparing between separate
|
|
|
|
// sequences should limit the quadratic behavior.
|
|
|
|
var result = <List<ComplexSelectorComponent>>[];
|
2016-07-29 02:33:09 +02:00
|
|
|
for (var i = 0; i < lists.length; i++) {
|
|
|
|
for (var complex1 in lists[i]) {
|
|
|
|
// The maximum specificity of the sources that caused [complex1] to be
|
2016-08-05 00:14:09 +02:00
|
|
|
// generated. In order for [complex1] to be removed, there must be
|
|
|
|
// another selector that's a superselector of it *and* that has
|
|
|
|
// specificity greater or equal to this.
|
|
|
|
var maxSpecificity = 0;
|
2016-08-13 00:24:27 +02:00
|
|
|
for (var component in complex1) {
|
2016-08-05 00:14:09 +02:00
|
|
|
if (component is CompoundSelector) {
|
|
|
|
for (var simple in component.components) {
|
|
|
|
var source = _sources[simple];
|
|
|
|
if (source == null) continue;
|
|
|
|
maxSpecificity = math.max(maxSpecificity, source.maxSpecificity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-07-29 00:45:29 +02:00
|
|
|
|
2016-07-29 02:33:09 +02:00
|
|
|
// Look in [result] rather than [lists] for selectors before [i]. This
|
2016-07-29 00:45:29 +02:00
|
|
|
// ensures that we aren't comparing against a selector that's already
|
|
|
|
// been trimmed, and thus that if there are two identical selectors only
|
|
|
|
// one is trimmed.
|
2016-07-29 02:33:09 +02:00
|
|
|
if (result.any((complex2) =>
|
2016-08-13 00:24:27 +02:00
|
|
|
_complexMinSpecificity(complex2) >= maxSpecificity &&
|
2016-08-05 02:04:37 +02:00
|
|
|
complexIsSuperselector(complex2, complex1))) {
|
2016-08-13 00:24:27 +02:00
|
|
|
continue;
|
2016-07-29 00:45:29 +02:00
|
|
|
}
|
|
|
|
|
2016-08-13 00:24:27 +02:00
|
|
|
// We intentionally don't compare [complex1] against other selectors in
|
|
|
|
// `lists[i]`, since they come from the same source.
|
|
|
|
if (lists.skip(i + 1).any((list) => list.any((complex2) =>
|
|
|
|
_complexMinSpecificity(complex2) >= maxSpecificity &&
|
|
|
|
complexIsSuperselector(complex2, complex1)))) {
|
|
|
|
continue;
|
2016-07-29 00:45:29 +02:00
|
|
|
}
|
|
|
|
|
2016-07-29 02:33:09 +02:00
|
|
|
result.add(complex1);
|
2016-07-29 00:45:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2016-07-29 02:17:39 +02:00
|
|
|
|
2016-08-13 00:24:27 +02:00
|
|
|
int _complexMinSpecificity(Iterable<ComplexSelectorComponent> complex) {
|
|
|
|
var result = 0;
|
|
|
|
for (var component in complex) {
|
|
|
|
if (component is CompoundSelector) {
|
|
|
|
result += component.minSpecificity;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool _hasRoot(CompoundSelector compound) =>
|
|
|
|
compound.components.any((simple) =>
|
|
|
|
simple is PseudoSelector &&
|
|
|
|
simple.type == PseudoType.klass &&
|
|
|
|
simple.normalizedName == 'root');
|
|
|
|
|
|
|
|
List<List<ComplexSelectorComponent>> _unifyComplex(
|
|
|
|
List<ComplexSelectorComponent> complex1,
|
|
|
|
List<ComplexSelectorComponent> complex2) {
|
|
|
|
var base1 = complex1.last;
|
|
|
|
var base2 = complex2.last;
|
2016-07-29 02:40:52 +02:00
|
|
|
if (base1 is CompoundSelector && base2 is CompoundSelector) {
|
|
|
|
var unified = _unifyCompound(base2.components, base1.components);
|
|
|
|
if (unified == null) return null;
|
|
|
|
|
2016-08-13 00:24:27 +02:00
|
|
|
return _weave([
|
|
|
|
complex1.take(complex1.length - 1).toList(),
|
|
|
|
complex2.take(complex2.length - 1).toList()..add(unified)
|
2016-07-29 02:40:52 +02:00
|
|
|
]);
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-29 00:04:48 +02:00
|
|
|
CompoundSelector _unifyCompound(
|
|
|
|
List<SimpleSelector> compound1, List<SimpleSelector> compound2) {
|
2016-07-29 02:17:39 +02:00
|
|
|
var result = compound2;
|
|
|
|
for (var simple in compound1) {
|
|
|
|
result = simple.unify(result);
|
|
|
|
if (result == null) return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new CompoundSelector(result);
|
|
|
|
}
|
2016-07-16 02:27:14 +02:00
|
|
|
}
|