mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-22 05:41:14 +01:00
parent
1d68c7a1c4
commit
69762ff0b0
@ -10,7 +10,7 @@ import '../ast/css.dart';
|
||||
import '../ast/selector.dart';
|
||||
import '../ast/sass.dart';
|
||||
import '../exception.dart';
|
||||
import 'source.dart';
|
||||
import 'state.dart';
|
||||
import 'functions.dart';
|
||||
|
||||
/// Tracks style rules and extensions, and applies the latter to the former.
|
||||
@ -23,7 +23,7 @@ class Extender {
|
||||
|
||||
/// A map from all extended simple selectors to the sources of those
|
||||
/// extensions.
|
||||
final _extensions = <SimpleSelector, Set<ExtendSource>>{};
|
||||
final _extensions = <SimpleSelector, Map<SelectorList, ExtendState>>{};
|
||||
|
||||
/// An expando from [SimpleSelector]s to integers.
|
||||
///
|
||||
@ -50,14 +50,18 @@ class Extender {
|
||||
/// the stylesheet.
|
||||
static SelectorList extend(
|
||||
SelectorList selector, SelectorList source, SimpleSelector target) =>
|
||||
new Extender()._extendList(
|
||||
selector, {target: new Set()..add(new ExtendSource(source, null))});
|
||||
new Extender()._extendList(selector, {
|
||||
target: {source: new ExtendState.optional()}
|
||||
});
|
||||
|
||||
/// Returns a copy of [selector] with [source] replaced by [target].
|
||||
static SelectorList replace(
|
||||
SelectorList selector, SelectorList source, SimpleSelector target) =>
|
||||
new Extender()._extendList(
|
||||
selector, {target: new Set()..add(new ExtendSource(source, null))},
|
||||
selector,
|
||||
{
|
||||
target: {source: new ExtendState.optional()}
|
||||
},
|
||||
replace: true);
|
||||
|
||||
/// Adds [selector] to this extender, associated with [span].
|
||||
@ -105,6 +109,21 @@ class Extender {
|
||||
/// provides the extend span and indicates whether the extension is optional.
|
||||
void addExtension(
|
||||
SelectorList extender, SimpleSelector target, ExtendRule extend) {
|
||||
var sources = _extensions.putIfAbsent(target, () => {});
|
||||
var existingState = sources[extender];
|
||||
if (existingState != 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.
|
||||
if (!extend.isOptional) existingState.makeMandatory(extend.span);
|
||||
return;
|
||||
}
|
||||
|
||||
var state = extend.isOptional
|
||||
? new ExtendState.optional()
|
||||
: new ExtendState.mandatory(extend.span);
|
||||
sources[extender] = state;
|
||||
|
||||
for (var complex in extender.components) {
|
||||
for (var component in complex.components) {
|
||||
if (component is CompoundSelector) {
|
||||
@ -115,13 +134,11 @@ class Extender {
|
||||
}
|
||||
}
|
||||
|
||||
var source = new ExtendSource(extender, extend.span);
|
||||
source.isUsed = extend.isOptional;
|
||||
_extensions.putIfAbsent(target, () => new Set()).add(source);
|
||||
|
||||
var rules = _selectors[target];
|
||||
if (rules == null) return;
|
||||
var extensions = {target: new Set<ExtendSource>()..add(source)};
|
||||
var extensions = {
|
||||
target: {extender: state}
|
||||
};
|
||||
for (var rule in rules.toList()) {
|
||||
rule.selector.value = _extendList(rule.selector.value, extensions);
|
||||
_registerSelector(rule.selector.value, rule);
|
||||
@ -132,21 +149,21 @@ class Extender {
|
||||
/// any selectors.
|
||||
void finalize() {
|
||||
_extensions.forEach((target, sources) {
|
||||
for (var source in sources) {
|
||||
if (source.isUsed) continue;
|
||||
sources.forEach((_, state) {
|
||||
if (state.isOptional || state.isUsed) return;
|
||||
throw new SassException(
|
||||
'The target selector was not found.\n'
|
||||
'Use "@extend $target !optional" to avoid this error.',
|
||||
source.span);
|
||||
}
|
||||
state.span);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Extends [list] using [extensions].
|
||||
///
|
||||
/// If [replace] is `true`, this doesn't preserve the original selectors.
|
||||
SelectorList _extendList(
|
||||
SelectorList list, Map<SimpleSelector, Set<ExtendSource>> extensions,
|
||||
SelectorList _extendList(SelectorList list,
|
||||
Map<SimpleSelector, Map<SelectorList, ExtendState>> extensions,
|
||||
{bool replace: false}) {
|
||||
// This could be written more simply using [List.map], but we want to avoid
|
||||
// any allocations in the common case where no extends apply.
|
||||
@ -172,7 +189,7 @@ class Extender {
|
||||
///
|
||||
/// If [replace] is `true`, this doesn't preserve the original selectors.
|
||||
List<List<ComplexSelector>> _extendComplex(ComplexSelector complex,
|
||||
Map<SimpleSelector, Set<ExtendSource>> extensions,
|
||||
Map<SimpleSelector, Map<SelectorList, ExtendState>> extensions,
|
||||
{bool replace: false}) {
|
||||
// This could be written more simply using [List.map], but we want to avoid
|
||||
// any allocations in the common case where no extends apply.
|
||||
@ -237,7 +254,7 @@ class Extender {
|
||||
///
|
||||
/// If [replace] is `true`, this doesn't preserve the original selectors.
|
||||
List<ComplexSelector> _extendCompound(CompoundSelector compound,
|
||||
Map<SimpleSelector, Set<ExtendSource>> extensions,
|
||||
Map<SimpleSelector, Map<SelectorList, ExtendState>> extensions,
|
||||
{bool replace: false}) {
|
||||
var original = compound;
|
||||
|
||||
@ -267,8 +284,8 @@ class Extender {
|
||||
compoundWithoutSimple.setRange(0, i, compound.components);
|
||||
compoundWithoutSimple.setRange(
|
||||
i, compound.components.length - 1, compound.components, i + 1);
|
||||
for (var source in sources) {
|
||||
for (var complex in source.extender.components) {
|
||||
sources.forEach((extender, state) {
|
||||
for (var complex in extender.components) {
|
||||
var extenderBase = complex.components.last as CompoundSelector;
|
||||
var unified = compoundWithoutSimple.isEmpty
|
||||
? extenderBase
|
||||
@ -291,9 +308,9 @@ class Extender {
|
||||
math.max(
|
||||
_sourceSpecificityFor(compound), complex.maxSpecificity));
|
||||
extended.add(newComplex);
|
||||
source.isUsed = true;
|
||||
state.isUsed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (extended == null) {
|
||||
@ -317,8 +334,8 @@ class Extender {
|
||||
/// pseudo selectors.
|
||||
///
|
||||
/// If [replace] is `true`, this doesn't preserve the original selectors.
|
||||
List<PseudoSelector> _extendPseudo(
|
||||
PseudoSelector pseudo, Map<SimpleSelector, Set<ExtendSource>> extensions,
|
||||
List<PseudoSelector> _extendPseudo(PseudoSelector pseudo,
|
||||
Map<SimpleSelector, Map<SelectorList, ExtendState>> extensions,
|
||||
{bool replace: false}) {
|
||||
var extended = _extendList(pseudo.selector, extensions, replace: replace);
|
||||
if (extended == null) return null;
|
||||
|
@ -1,26 +0,0 @@
|
||||
// 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:source_span/source_span.dart';
|
||||
|
||||
import '../ast/selector.dart';
|
||||
|
||||
/// The source of an `@extend` declaration.
|
||||
class ExtendSource {
|
||||
/// The selector for the style rule in which this `@extend` was declared.
|
||||
final SelectorList extender;
|
||||
|
||||
/// The span for the `@extend` rule that declared this extension.
|
||||
final FileSpan span;
|
||||
|
||||
/// Whether this extension matched a selector, or was marked optional.
|
||||
var isUsed = false;
|
||||
|
||||
ExtendSource(this.extender, this.span);
|
||||
|
||||
int get hashCode => extender.hashCode;
|
||||
|
||||
bool operator ==(Object other) =>
|
||||
other is ExtendSource && other.extender == extender;
|
||||
}
|
41
lib/src/extend/state.dart
Normal file
41
lib/src/extend/state.dart
Normal file
@ -0,0 +1,41 @@
|
||||
// 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:source_span/source_span.dart';
|
||||
|
||||
/// The state of an extension for a given source and target.
|
||||
///
|
||||
/// The source and target are represented externally, in the nested map that
|
||||
/// contains this state.
|
||||
class ExtendState {
|
||||
/// Whether this extension is optional.
|
||||
bool get isOptional => _span == null;
|
||||
|
||||
/// Whether this extension matched a selector.
|
||||
var isUsed = false;
|
||||
|
||||
/// The span for the `@extend` rule that should produce an error if this
|
||||
/// extension doesn't match anything.
|
||||
///
|
||||
/// This is `null` if and only if this extension is optional.
|
||||
FileSpan get span => _span;
|
||||
FileSpan _span;
|
||||
|
||||
/// Creates a new optional extend state.
|
||||
ExtendState.optional();
|
||||
|
||||
/// Creates a new mandatory extend state.
|
||||
///
|
||||
/// The [span] is used in the error that's thrown if this extension doesn't
|
||||
/// match anything.
|
||||
ExtendState.mandatory(this._span);
|
||||
|
||||
/// Marks this extension as mandatory.
|
||||
///
|
||||
/// The [span] is used in the error that's thrown if this extension doesn't
|
||||
/// match anything.
|
||||
void makeMandatory(FileSpan span) {
|
||||
_span = span;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user